funnycoderstar / blog Goto Github PK
View Code? Open in Web Editor NEWthis is my blog
Home Page: https://ionestar.cn/blog/
this is my blog
Home Page: https://ionestar.cn/blog/
但是最后在onShow的时候也遇到一些问题
tabBar页面A navigatorTo 到页面B,然后B switchTab 到A,这里A会执行onShow();
但是我再从A跳到B再switchTab回来,A就不会再执行onShow()了,
去开发者社区询问,得知此问题在下个版本会修复
function Observer(data) {
this.data = {};
for (const key of Object.keys(data)) {
Object.defineProperty(this.data, key, {
enumerable: true,
configurable: true,
get: function () {
console.log('你访问了' + key);
return data[key];
},
set: function (newVal) {
console.log('你设置了' + key);
console.log('新的' + key + '=' + newVal);
if (newVal === data[key]) {
return;
}
data[key] = newVal;
}
})
}
}
Observer.prototype.$watch = function (key, cd) {
let value = this[key];
Object.defineProperty(this, key, {
enumerable: true,
configurable: true,
get: function () {
console.log(`你访问了 ${key}`);
return value;
},
set: function (newValue) {
cd(newValue);
value = newValue;
return newValue;
},
})
}
const app1 = new Observer({
name: 'wangyaxing',
age: '24',
a: {
b: 1,
c: 2,
},
});
const app2 = new Observer({
city: 'beijing'
});
app1.$watch('age', function(newValue) {
console.log(`我年纪变了,我现在 ${newValue}岁了`);
});
app1.age = 100;
app1.age;
app1.age = 120;
app1.a.b;
app1.a.c = 10;
app1.a = {
e: 100,
}
TCP/IP协议族按层次分别为以下4层: 应用层, 传输层和数据链路层
决定了向用户提供应用服务时通信的活动;
TCP/IP协议族内预存了各类通用的应用服务.比如, FTP(File Transfer Protocol, 文件传输协议)和DNS(Domain Name System, 域名系统)服务就其中两类.
hTTP协议也处于该层;
传输层对上层应用层,提供处于网络连接中的两台计算机之间的数据传输;
在传输层中有两个性质不同的协议: TCP(Transmission Control Protocal, 传输控制协议)和UDP(User Data Protocal, 用户数据报协议);
网络层用来处理网络上流动的数据包.数据包是网络传输的最小数据单位.该层规定了通过怎样的路径(所谓的传输路线)到达对方计算机,并把数据包传送给对方;
与对象计算机之间通过多台计算机或网络设备进行传输,网络层所起的作用就是在众多的选项中选择一条传输路线;
用来处理连接网络的硬件部分.包括控制操作系统,硬件的设备驱动,NIC(Network Interface Card, 网络适配器,即网卡),及光纤等物理可见部分(还包括连接器等一切传输媒介).硬件上的范畴均在链路层的作用范围之内;
IP协议位于网络层, 作用是把各种数据包传送给对方
TCP位于传输层,提供可靠的字节流服务; TCP协议为了更容易传送打数据才把数据分割,而且TCP协议能够确认数据最终是否送达到对方;
为了准确无误地将数据送达到目标处,TCP协议采用了三次握手策略;用TCP协议把数据包送出去后,TCP不会对传送后的情况置之不理,它一定会向对方确认是否成功送达.握手过程中使用TCP的标志: SYN和ACK;
发送端首先发送一个带SYN标志的数据包给对方.接受端收到后,回传一个带有SYN/ACK标志的数据包以示传达确认信息.最后,发送端再回传一个带ACK标志的数据包,代表'握手'结束;
DNS(Domain Name System)服务是和HTTP协议一样位于应用层的协议.它提供了域名到IP地址之间的解析服务;
DNS协议提供通过域名查找IP地址,或逆向从IP地址反查域名的服务;
使用浏览器浏览一根 包含多张图片的HTML页面时,在发送请求访问HTML页面资源的同时,也会请求HTML页面里包含的其他资源,因此,每次的请求都会造成武威的TCP连接的建立和端来,增加通信量的开销;
持久连接的特点是: 只要任意一端没有明确提出断开连接,则保持TCP连接状态;
持久连接的好处在于减少了TCP连接的重复建立和断开所造成的额外开销,减轻了服务器端的负载.另外,减少开销的那部分时间,使HTTP请求和响应能够更早地技术,这样Web页面的显示速度也就响应提高了.
http协议是无状态的,增加了cookie来保存一些数据;
Cookie会根据从服务器端发送的响应报文内的一个叫做Set-Cookie的首部字段信息,通知客户端保存Cookie.在下次客户端再往改服务器发送请求时,客户端会自动在请求报文中加入Cookie值后发送出去;
服务器端发现客户端发送过来的Cookie后,会去检查究竟从哪一个客户端发来的连接请求,然后对比服务器上的记录,最后得到之前的状态信息;
WWW-Authenticate
WWW-Authenticate: Basic realm="Usagidesign Auth"
用于HTTP访问认证.它会告诉客户端适用于访问请求URI所指定资源的认证方案(Basic或是Digest)和带参数提示的质询(challenge).状态码401 Unauthorized响应中,肯定带有首部字段WWW-Authenticate;
realm字段的字符串是为了辨别请求URI指定资源所受到的保护策略
1.回话劫持
回话劫持(Session Hijack)是指攻击者通过某种手段拿到了用户的回话ID,并利用此回话ID伪装成用户,达到攻击的目的.
下面列举几种攻击者可获得回话ID的途径
1.密码破解攻击(Password Cracking)即算出密码,突破认证
有以下两种手段
Number.MAX_SAFE_INTEGER Number.MIN_SAFE_INTEGER
Number.MAX_VALUE Number.MIN_VALUE
最大的整数 2^53-1
Number.isInteger()
检测是否是安全的整数
Number.isSafeInteger()
虽然最大整数能达到53位,但是有的运算(数位操作)只适用于32位有符号整数,Math.pow(-2,31)~Math.pow(2, 31);
一些特殊值NaN和Infinity并不是32位安全范围的
// 特殊值之NaN 是js中唯一一个不等于自身的值
// NaN !== NaN true 他与任何一个值都不相等
// isNaN() 检测一个参数是否不是NaN,也不是一个数
window.isNaN('foo') // true 'foo'既不是NaN,也不是数字
window.isNaN(2/'foo') // true
ES6 Number.isNaN('foo') //false
// 特殊值之Infinity 无穷数
1/0 Infinity
-1/0 -Infinity
Infinity/Infinity NaN
特殊值之 -0
-0除了可用作常量外,也可以是某些数学运算的返回值
var a = 0 / -3 // -0
var b = 0 * -3 // -0
JSON.stringify(-0) // "0"
JSON.parse('-0') //-0
-0 == 0 // true
-0 === 0 // true
区分 -0 和 0
function isNegZero(n) {
n = Number(n);
return (n === 0) && (1 / n === Infinity);
}
// Object.is()判断两个值是否绝对相等
var a = 2 / 'foo';
var b = -3 * 0;
Object.is(a, NaN); // true
Object.is(b, -0); // true
Object.is(b, 0); // false
if(! Object.is) {
Object.is = function(v1, v2) {
// 判断是否是-0
if(v1 === 0 && v2 === 0) {
return 1 / v1 === 1 / v2;
}
// 判断是否是NaN
if(v1 !== v1) {
return v2 !== v2;
}
// 其他情况
return v1 === v2;
}
}
fs.readFile(filePath, function(err, data) {
if (err) {
//handle the error
}
// use the data
}
你可以有如下几个方法
在类Unix系统中你不应该尝试去监听80端口,因为这需要超级用户权限。 因此不推荐让你的应用直接监听这个端口。
如果你一定要让你的应用监听80端口的话,你可以有通过在Node应用的前方再增加一层反向代理 (例如nginx)来实现,如下图所示。否则,建议你直接监听大于1024的端口。
方向代理指的是以代理服务器来接收Internet上的连接请求,然后将请求转发给内部网络上的服务器, 并且将服务器返回的结果发送给客户端。
Node采用的是单线程的处理机制(所有的I/O请求都采用非阻塞的工作方式。而在底层,Node.js借助libuv来作为抽象封装层, 从而屏蔽不同操作系统的差异,Node可以借助livuv来来实现多线程。
Libuv库负责Node API的执行。它将不同的任务分配给不同的线程,形成一个事件循环, 以异步的方式将任务的执行结果返回给V8引擎。
代码整洁之道
感触比较深的几点
1.要敢于修改代码,好的代码是要不断修改的,害怕修改之后带来bug,只会让代码更加难以维护
2.有单元测试和集成测试很重要,这样在重构代码的时候,才能降低对其他模块的影响
3.要不断学习,没人敢找很久不看病的医生,不写代码的架构师不是一个好的架构师
4.每天花几分钟强迫自己,比如:盲打, 一些快捷键,看英文文档
webpack.config.js
中配置下面简单介绍一下五个经常使用的场景
mmodule.exports = {
//...
devServer: {
proxy: {
'/api': 'http://localhost:3000'
}
}
};
请求到 /api/xxx
现在会被代理到请求 http://localhost:3000/api/xxx
, 例如 /api/user
现在会被代理到请求 http://localhost:3000/api/user
如果你想要代码多个路径代理到同一个target下, 你可以使用由一个或多个「具有 context 属性的对象」构成的数组:
module.exports = {
//...
devServer: {
proxy: [{
context: ['/auth', '/api'],
target: 'http://localhost:3000',
}]
}
};
如果你不想始终传递 /api ,则需要重写路径:
module.exports = {
//...
devServer: {
proxy: {
'/api': {
target: 'http://localhost:3000',
pathRewrite: {'^/api' : ''}
}
}
}
};
请求到 /api/xxx 现在会被代理到请求 http://localhost:3000/xxx
, 例如 /api/user 现在会被代理到请求 http://localhost:3000/user
默认情况下,不接受运行在 HTTPS 上,且使用了无效证书的后端服务器。如果你想要接受,只要设置 secure: false
就行。修改配置如下:
module.exports = {
//...
devServer: {
proxy: {
'/api': {
target: 'https://other-server.example.com',
secure: false
}
}
}
};
有时你不想代理所有的请求。可以基于一个函数的返回值绕过代理。
在函数中你可以访问请求体、响应体和代理选项。必须返回 false 或路径,来跳过代理请求。
例如:对于浏览器请求,你想要提供一个 HTML 页面,但是对于 API 请求则保持代理。你可以这样做:
module.exports = {
//...
devServer: {
proxy: {
'/api': {
target: 'http://localhost:3000',
bypass: function(req, res, proxyOptions) {
if (req.headers.accept.indexOf('html') !== -1) {
console.log('Skipping proxy for browser request.');
return '/index.html';
}
}
}
}
}
};
上面的参数列表中有一个changeOrigin
参数, 是一个布尔值, 设置为true, 本地就会虚拟一个服务器接收你的请求并代你发送该请求,
module.exports = {
//...
devServer: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
}
}
}
};
修改 config/index.js
module.exports = {
dev: {
// 静态资源文件夹
assetsSubDirectory: 'static',
// 发布路径
assetsPublicPath: '/',
// 代理配置表,在这里可以配置特定的请求代理到对应的API接口
// 使用方法:https://vuejs-templates.github.io/webpack/proxy.html
proxyTable: {
// 例如将'localhost:8080/api/xxx'代理到'https://wangyaxing.cn/api/xxx'
'/api': {
target: 'https://wangyaxing.cn', // 接口的域名
secure: false, // 如果是https接口,需要配置这个参数
changeOrigin: true, // 如果接口跨域,需要进行这个参数配置
},
// 例如将'localhost:8080/img/xxx'代理到'https://cdn.wangyaxing.cn/xxx'
'/img': {
target: 'https://cdn.wangyaxing.cn', // 接口的域名
secure: false, // 如果是https接口,需要配置这个参数
changeOrigin: true, // 如果接口跨域,需要进行这个参数配置
pathRewrite: {'^/img': ''} // pathRewrite 来重写地址,将前缀 '/api' 转为 '/'。
}
},
// Various Dev Server settings
host: 'localhost', // can be overwritten by process.env.HOST
port: 4200, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
}
dev-serve
r 使用了非常强大的 http-proxy-middleware , http-proxy-middleware
基于 http-proxy
实现的,可以查看 http-proxy 的源码和文档:https://github.com/nodejitsu/node-http-proxy 。
target:要使用url模块解析的url字符串
forward:要使用url模块解析的url字符串
agent:要传递给http(s).request的对象(请参阅Node的https代理和http代理对象)
ssl:要传递给https.createServer()的对象
ws:true / false,是否代理websockets
xfwd:true / false,添加x-forward标头
secure:true / false,是否验证SSL Certs
toProxy:true / false,传递绝对URL作为路径(对代理代理很有用)
prependPath:true / false,默认值:true - 指定是否要将目标的路径添加到代理路径
ignorePath:true / false,默认值:false - 指定是否要忽略传入请求的代理路径(注意:如果需要,您必须附加/手动)。
localAddress:要为传出连接绑定的本地接口字符串
changeOrigin:true / false,默认值:false - 将主机标头的原点更改为目标URL
使用复合条件检测null的类型
var a = null;
console.log(!a && typeof a === 'object');
函数是'可调用的对象', 是object的一个'子类型', 它有一个内部属性[[Call]], 该属性使其可以被调用
�函数对象的length属性是其声明的参数个数
function a(b, c) {
}
console.log(a.length); // 2
变量是没有类型的,只有值才有;typeof 得到的结果时该变量持有值得类型,因为JavaScript中变量没有类型
typeof 运算符总是会返回一个字符串
console.log( typeof typeof 42); // 'string'
undefined(已在作用域中声明但是还没赋值的变量): 变量在未持有值得时候
undeclared(还没有在作用域中声明过的变量):
var a;
console.log(a); // undefined
console.log(b); // ReferenceError: b is not defined
typeof a; // undefined
typeof b; // undefined b是一个undeclared变量,没有报错,是因为typeof 有一个特殊的安全防范机制
undefined
和is not defined
是两码事
使用typeof 的安全防范机制(阻止报错)来检查undeclared变量
// 这样做会抛出错误 ReferenceError: b is not defined
if(b) {
}
// 这样是安全的
if(typeof b !== 'undefined') {
console.log(11);
}
var a = [1, '2', [3]];
console.log(a.length, a); // 3 [ 1, '2', [ 3 ] ]
delete a[0];
console.log(a.length, a); // 3 [ <1 empty item>, '2', [ 3 ] ]
使用delete运算符可以将单元才能够数组中删除,但是删除后,数组的length不会发生变化 ?
'稀疏'数组(含有空白或空缺单元的数组)
var a = [];
a[0] = 1;
a[2] = [3];
console.log(a.length, a[1]); // 3 undefined
a[1]的值是undefined, 这将与将其显式赋值为undefined(a[1] = undefined) 还是有区别的 ?
类数组
有时候需要将类数组(一组通过数字索引的值)转化为真正的数字;
一般通过 数组工具函数(如indexOf(..), concat(..), forEach(..)等)来实现
工具函数slice(..)经常被用于这类转换
function foo() {
var arr = Array.prototype.slice.call(arguments);
arr.push('bam');
console.log(arr); // [ 'bar', 'baz', 'bam' ]
};
foo('bar', 'baz')
es6的内置函数 Array.from(..)也能实现同样的功能
...
var arr = Array.from(arguments);
...
字符串经常被当做字符数组, 它们都是类数组,都有length属性以及 indexOf(..)和concat(..)方法;
JavaScript中字符串是不可变的,但是数组是可变的;
字符串不可变是指字符串的成员函数不会改变其原始值,而是创建并返回一个新的字符串.而数组的成员函数都是在其原始值上进行操作;
var a = 'foo';
c = a.toUpperCase();
console.log(a === c); // false
console.log(a, c); // foo FOO
许多数组函数用来处理字符串很方便,虽然字符串没有这些函数,但是可以通过'借用'数组的非变更方法来处理字符串
var a = 'foo';
var c = Array.prototype.join.call(a, '-');
console.log(c); // f-o-o
var d = Array.prototype.map.call(a, function(v) {
return v.toUpperCase() + '.';
}).join('');
console.log(d); // F.O.O.
另一个不通电在于字符串反转,数组有一个字符串没有的可变更成员函数reverse();
我们无法'借用'数组的可变成员函数,因为字符串是不可变的
一个变通(破解)的方法是先将字符串转换为数组,待处理完后再讲结果转换为字符串
var c = a
// 将a的值转换为字符数组
.split('')
// 将数组中的字符进行倒转
.reverse()
// 将数组中的字符拼接会字符串
.join('');
42.toFixed(2); // SyntaxError
下面的语法都有效
(42).toFixed(3);
0.42.toFixed(3);
42..toFixed(3);
42.toFixed(3)是无效语法,因为.被视为常量42.的一部分;42..toFixed(3)则没有问题,因为第一个.被视为number的一部分,第二个.是属性访问运算度;
Number.MAX_SAFE_INTEGER
Number.MIN_SAFE_INTEGER
有时JavaScript程序需要处理一些比较大的数字,如数据库的64位ID等,由于JavaScript的数字类型无法精确呈现64位数值,所以必须将他们保存转换为字符串
Number.isInteger();
Number.isSafeInteger();
null, undefined,
null是一个特殊关键字,不是标识符,我们不能讲其当做变量来使用和赋值.然而undefined却是一个标识符,非严格模式下可以被当做变量来使用和赋值
void运算符
undefined是一个内置标识符,它的值为undefined,通过viod运算符即可得到该值;
表达式viod没有返回值,因此返回结果是undefined,void并不改变表达式的结果,只是让表达式不返回值
var a = 42;
console.log(void a, a); // undefined 42
习惯上用void 0
来获得undefined
function doSomething() {
if(!App.ready) {
// 不让表达式返回结果
return void setTimeout(doSomething, 100);
}
var result;
return result;
}
if(doSomething()) {
//
}
类似于
if(!App.ready) {
setTimeout(doSomething, 100);
return;
}
总之,如果要将代码中的值(如表达式的返回值)设为undefined,就可以使用void
1.不是数字的数字NaN
如果数学运算的操作数不是数字类型,就无法返回一个有效的数字,这种情况下返回值为NaN;
NaN是一个特殊值,它和自身不相等,是唯一一个非自反(自反, 即 x===x不成立)的值, NaN != NaN 为true
isNaN(..)判断一个值是否为NaN
但是有个验证缺陷, 检查方式过于死板,就是'检查参数是否不是NaN, 也不是数字';
var a = 2 / 'foo';
var b = 'foo';
console.log(a);
console.log(window.isNaN(a), window.isNaN(b)); // true true
es6提供了 Number.isNaN();
实际还有一个更简单的方法,即利用NaN不等于自身这个特点.NaN是JavaScript中唯一一个不等于自身的值;
if(!Number.isNaN) {
Number.isNaN = function (n) {
return n !== n;
}
}
2.无穷数
var a = 1/0; // Infinity
var a = -1/0; // -Infinity
Infinity/Infinity; // NaN
javascript的运算结果有可能溢出, 此时的结果为 Infinity或-Infinity
3.零值
4.特殊等式
NaN和-0在相等比较时有些特别, NaN和自身不相等,使用Number.isNaN(..); -0等于0, 使用isNegZero(..)
Object.is(..)来判断两个值是否绝对相等;
能使用 == 或者 === 时就尽量不要使用Object.is(..);因为前者效率更高,更为通用,后者主要用来处理那些特殊的相等比较
简单标量基本类型值(字符串和数字)通过值复制来赋值/传递,而复合值(对象等)通过引用复制来赋值/传递;JavaScript中的引用和其他语言中的引用/指针不同,他们不能指向别的变量/引用,只能指向值
function foo(x) {
x.push(4); // x:[ 1, 2, 3, 4 ]
x = [4, 5, 6];
x.push(7);// x:[ 4, 5, 6, 7 ]
}
var a = [1, 2, 3];
foo(a); // a:[ 1, 2, 3, 4 ]
function foo(x) {
x.push(4); // x:[ 1, 2, 3, 4 ]
x = [4, 5, 6];
x.length = 0;
x.push(4, 5, 6, 7); //a:[ 4, 5, 6, 7 ]
}
var a = [1, 2, 3];
foo(a); // a:[ 4, 5, 6, 7 ]
x.length = 0和x.push(4, 5, 6, 7)并没有创建一个新的数组,而是更改了当前的数组,所以a的值变成了[ 4, 5, 6, 7 ]
如果通过值复制的方式来传递复合值
foo(a.slice());
slice(..)不带参数会返回当前数组的一个浅复本,由于传递给函数的是指向该复本的引用, 所以foo(..)中的操作不会影响a指向的数组
常用的原生函数
所有typeof返回值为'object'的对象(如数组)都包含一个内部属性[Class].这个属性无法直接访问,一般通过Object.prototype.toString(..)
来查看
Object.prototype.toString.call([1, 2, 3]); // "[object Array]"
var a = 'abc';
var b = new String(a);
var c = Object(a);
console.log(typeof a); // string
console.log(typeof b); // object
console.log(typeof c); // object
console.log(b instanceof String); // true
console.log(c instanceof String); // true
console.log(Object.prototype.toString.call(b)); // [object String]
console.log(Object.prototype.toString.call(c)); // [object String]
如果想要得到封装对象中的基本类型值,可以使用valueOf()函数
var a = new String('abc');
var b = new Number(42);
var c = new Boolean(true);
console.log(a.valueOf()); // abc
console.log(b.valueOf()); // 42
console.log(c.valueOf()); // true
隐式拆封(强制类型转换)
var a = new String('abc');
var b = a + '';
console.log(typeof a); // object
console.log(typeof b); // string
Array构造函数只带一个数字参数的时候,该参数就会被当做数组的预设长度(length), 而非只充当数组中的一个元素
var a = new Array(3);
var b = [undefined, undefined, undefined];
var c = [];
c.length = 3;
console.log(a.length, a); // 3 [ <3 empty items> ]
console.log(b); // [ undefined, undefined, undefined ]
console.log(c); // [ <3 empty items> ]
console.log(a.join('-')); // --
console.log(b.join('-')); // --
a.map(function(v, i) {
console.log(i);
return i;
});
b.map(function(v, i) {
console.log(i);
return i;
})
a.map(..)之所以执行失败,是因为数组中并不存在任何单元,所以map(..)无从遍历, 而join(..)却不一样,具体实现可参考下面代码:
function fakeJoin(arr, connector) {
var str = '';
for(var i = 0; i < arr.length; i++) {
if(i > 0) {
str += connector;
}
if(arr[i] !== undefined) {
str += arr[i];
}
}
return Storage;
}
var a = new Array(3);
console.log(fakeJoin(a, '-'));
join(..)首先假定数组不为空,然后通过length属性值来遍历其中的元素,而map(..)并不做这样的假定
var a = Array.apply(null, {length: 3});
console.log(a); // [ undefined, undefined, undefined ]
apply(..)是一个工具函数,适用于所有函数对象,它会以一种特殊的方式来调用传递给它的函数
第一个参数是this对象,第二个参数则必须是一个数组(或者类似数组的值, 也叫做类数组对象, array-like object), 其中的值被用作函数的参数;
除非万不得已,尽量不要使用Object(..), Function(..)和RegExp(..)
建议使用常量形式来定义正则表达式, 这样不仅语法简洁,执行效率也更高;因为JavaScipt引擎在代码执行前会对它们进行预编译和缓存,与其他构造函数不同,RegExp(..)还是很有用的,比如动态定义正则表达式
Date.now();
if(!Date.now) {
Date.now = function() {
return (new Date()).getTime();
}
}
Date()不带 new 关键字, 则会得到当前日期的字符串值
console.log(Date());
console.log(new Date());
// Wed May 30 2018 16:48:20 GMT+0800 (CST)
// 2018-05-30T08:48:20.089Z
其他构造函数的原型包含它们各自类型所持有的行为特征;比如Number#toFixed(..)
(将数字转换为指定长度的整数字符串)和Array#concat(..)
(合并数组), 所有的函数都可以调用Function.prototype中的apply(..), call(..)和bind(..)
1.Javascript为基本数据类型提供了封装对象,称为原生函数(如 String, Number, Boolean等).它们为基本数据类型提供了该子类型所持有的方法和属性(如: String#trim() 和 Array#concat())
2.对于简单标量基本类型之,比如'abc', 如果要访问它的length属性或者String.prototype方法,JavaScript引擎会自动对该值进行封装(即使用相应类型的封装对象来包装它)来实现对这些属性和方法的访问;
1.toString()
JSON字符串化
工具函数JSON.stringify(..)在将JSON对象序列化为字符串时也用到了toString();
console.log(JSON.stringify(undefined)); // undefined
console.log(JSON.stringify(function() {})); // undefined
console.log(JSON.stringify(
[1, undefined, function(){}, 4] // [1,null,null,4]
));
console.log(JSON.stringify(
{
a: 2,
b: function (){}, // {"a":2}
}
));
2.toNumber()
true转换为1, false转换为0, undefined转换为NaN, null转换为0;
对象(包括数组)会被首先转换为相应的基本类型值,如果返回的是非数字的基本类型值,则再遵循以上规则将其强制转换为数字;
为了将转换为相应的基本类型值,抽象操作ToPrimitive会首先(通过内部操作DefaultValue)检查该值是否有valueOf()方法,如果有并且返回基本类型值,就使用该值进行强制类型转换,如果没有就使用toString()的返回值(如果存在)就进行轻质类型转换;
如果valueOf()和toString()均不返回基本类型值,会产生TypeError错误
使用Object.create(null)创建的toString()方法,因此无法进行轻质类型转换
3.toBoolean
以下都是假值
假值列表之外的值就是真值
[], {}和function(){}, 都是真值;
字符串和数字之间的转换
String(..),遵循前面讲过的ToString规则,将值转换为字符串基本类型
Number(..)遵循前面讲过的ToNumber规则,将值转换为数字基本类型
var a = 42;
var b = a.toString();
var c = '3.14';
var d = +c;
b; // "42"
d; // 3.14
a.toString()
, 也可以转为为字符串,但是其中涉及隐式转换.因为toString()对42这样的基本类型值不适用,所以JavaScript引擎会自动为42创建一个封装对象,然后对该对象调用toString().这里显式转换中含有隐式转换;
一元运算+被普遍认为是显示强制类型转换;
1.日期显式转换为数字
一元运算+另一个常见用途是将日期(Date)对象强制类型转换为数字,返回Unix时间戳,以微秒为单位(从1970年1月1日 00:00:00 UTC到当前时间);
var d = new Date('Wed May 30 2018 16:48:20 GMT+0800 (CST)');
+d;
// 常用下面的方法来获得当前的时间戳
var temestamp = +new Date();
~运算符(即字位操作符'非');
字位操作符只适用于32位整数,运算符会强制操作数使用32位格式,这是通过抽象操作ToInt32实现的
ToInt32首先会执行ToString强制类型转换,比如'123'先被转换为123,然后再执行ToInt32
字位运算符(如| 和~)个某些特殊数字一起使用会产生类似强制类型转换的效果,返回另外一个数字
console.log(0|-0); // 0
console.log(0|NaN); // 0
console.log(0|Infinity); // 0
console.log(0|-Infinity); // 0
以上这些特殊数字无法以32位格式呈现,因此toInt32返回0
~首先将值强制类型转换为32位数字,然后再执行字位操作'非'(对每一个字位进行反转)
字位截除
~~截除数字值的小数部分,
建议使用 Boolean(a) 和 !!a来进行显式强制类型转换
var a = [1, 2];
var b = [3, 4];
console.log(a+b); // 1,23,4
某个操作数如果是字符串或者能够通过以下步骤转换为字符串的话, +将进行拼接操作,如果其中一个操作数是对象(包括数组),则首先对其调用ToPromitive抽象操作,该抽象操作再调用[[DefaultValue]], 以数字作为上下文;
因为数组的valueOf()操作无法得到简单基本类型值,于是她转而调用toString().因此上例中的两个数组变成了'1, 2' 和'3, 4'。+将它们拼接后返回'1,23, 4'
var a = '3.14';
var b = a - 0;
console.log(b); // 3.14
-是数字减法运算符,因此a-0会将a强制类型转换为数字,也可以使用a*1和a/1,因为这两个运算符也只适用于数字
[] == ![]; // true
2 == [2]; // true
'' == [null]; // true
== 右边的值[2]和[null]会进行ToPromitive强制类型转换,以便能和左边的基本类型值(2和'')进行比较,因为数组的valueOf()返回数组本身,所以强制类型转换过程中数组会进行字符串化
[2]会转换成'2',然后通过ToNumber()转换为2;
[null]会直接转换为'';
所以最后的结果就是2 == 2和'' == ''
0 == '\n';//true
'', '\n'(或者' ' 等其他空格组合)等空字符串被toNumber强制转换为0;
'0' == false; // true
false == 0; //true
false == ''; // true
false == []; // true
'' == 0; // true
'' == []; // true
0 == []; // true
其中4中情况涉及 == false,之前我们说过应该避免,
现在剩下3种
'' == 0; // true
'' == []; // true
0 == []; // true
我们要对 == 两边的值认真推敲,以下两个原则可以让我们有效地避免出错
1.比较双方首先会调用 ToPrimitive, 如果结果出现非字符串,就根据ToNumber规则将双方强制类型转为数字来比较
2.如果都是字符串,则按字母顺序来进行比较
3.<= 应该是 '小于或者等于', 实际上JavaScipt中 <= 是'不大于'的意思(!(a > b), 处理为!(b < a)).同理 a >= b 处理为 b <= a;
相等比较有严格相等,关系比较缺没有'严格关系比较', 如果要避免 a < b中发生隐式强制类型比较,我们只能确保 a和b为相同的类型,除此之外别无它法;
尽量使用 Number(a) < Number(b);
? : 是右关联
右关联不是指从右往左执行,而是指从右往左组合
a ? b : c ? d : e;
// 应该是以下组合顺序
a ? b : (c ? d : e);
某些情况, 返回的结果没有区别,但实际却有十分微妙的差别
var a = true, b = false, c = true, d = true, e = false;
a ? b : (c ? d : e); // false, 执行a和b
(a ? b : c) ? d : e; // false,执行a, b和e
另一个右关联的例子是 = 运算符
var a, b, c;
a = b = c = 42;
它首先执行 c = 42, 然后是b = .., 最后是a = .., 因为是右关联,所以它实际上是这样来处理的: a = (b = (c = 42));
来看一些复杂的例子
var a = 42;
var b = 'foo';
var c = false;
var d = a && b || c ? c || b ? a : c && b : a;
d; // 42
// 根据优先级和关联, 根据组合规则将上方代码分解如下
((a && b) || c) ? ((c || b) ? a : (c && b)) : a;
// 可以通过缩进显式让代码更容易理解
(
(a && b)
||
c
)
?
(
(c || b)
?
a
:
(c && b))
: a;
(1) (a && b) 结果为 'foo';
(2) 'foo' || c 结果为 'foo';
(3) 第一个 ? 中 'foo'为真值;
(4)(c || b)结果为'foo'
(5)第二个 ?中, 'foo'为真值
(6) a的值为42
最后结果为42
最后: 如果运算符优先级/关联规则能够令代码更为简洁,就使用运算符优先级/关联规则,如果( )有助于提高代码可读性,就使用( );
有时JavaScript会自动为代码补上缺失的分号,即自动分号插入(Automatic Semicolon Insertion ASI;
ASI只在换行符处起作用,而不会再代码行的中间插入分号;只有代码行末尾与韩航服之间除了空格和注释之外没有别的内容,它才会这样做
总结一下小程序的坑
function getStorage(key) {
return new Promise(function (resolve, reject) {
// 先判断本地数据存储有没有cookie
wx.getStorage({
key: key,
success: function (res) {
resolve(res.data);
},
fail: function (res) {
console.log(res);
resolve(null);
// if (res.errMsg == 'getStorage:fail' || res.errMsg == 'getStorage:fail data not found') {
<!--安卓返回 getStorage:fail, ios 返回 res.errMsg == 'getStorage:fail data not found'-->
// console.log('没有cookie');
// resolve(null);
// } else {
// console.log('这是一个问题');
// reject(res.errMsg);
// }
},
});
});
}
DNS解析: 域名到IP的对应
用于缓存
简单查找所需时间为线性时间o(n);
二分查找的速度更快,所需时间为对数时间 o(logn)
散列表中查找时间为常量时间 o(1)
1.散列表的填装因子
散列表包含的元素数/位置总数
填装因子越低,发生冲突的可能性越小,散列表的性能越高.一个不错的经验规则: 一旦填装因子大于0.7,就调整散列表的长度
2.散列函数
良好的散列函数让数组中的值程均匀分布
糟糕的散列函数让值扎堆,导致大量冲突
广度优先搜索让你能够找到两样东西之间的最短距离
图是什么
图由节点和边组成.一个节点可能与众多节点直接相连,这些节点被称为邻居
1.从节点A出发,有前往节点B的路径吗?
2.从节点A出发,前往节点B的哪条路径最短?
1.使用图来建立问题模型
2.使用广度优先搜索解决问题
只有按照顺序查找时, 广度优先搜索不仅查找A到B的路径,而且找到的是最短路径;
有一个可以实现这种目的的数据结构,就是队列(quene);
队列是一种先进先出(First In First Out, FIFO)的数据结构,栈是一种先进后出(Last In First Out, LIFO)的数据结构;
广度优先搜索的运行时间为O(人数+边数), O(V + E), 其中V(vertice)为顶点数, E为边数
使用广度优先搜索,找出的是段数最少的路径;
要找出最快的路径,使用狄克斯特拉算法
1.找出'最便宜'的节点,即可在最短时间内到达的节点
2.更新该节点的邻居的开销
3.重复这个过程,直到对图中的每个节点都这样做了
4.计算最终路径
狄克斯特拉算法用于每条边都有关联数字的饿图,这些数字称为权重;
带权重的图称为加权图;不带权重的图称为非加权图;
要计算非加权图的最短路径,可使用广度优先搜索,要计算加权图中的最短路径,可使用狄克斯特拉算法;
无向图意味着两个节点彼此指向对方,其实就是环!
在无向图中,每条边都是一个环;狄克斯特拉算法只适用于有向无环图(DAG)
不能将狄克斯特拉算法用于包含负权变得图;在包含负权边的图中,要找到最短路径,可使用另一种算法-贝尔曼-福德算法
节点的开销指的是从起点出发前往该节点需要多长时间;
你需要计算的所有解,并从中选出最小/最短的那个
1.如果两个字母不同,就选择上方和左右邻居中较大的那个
2.如果两个字母相同,就将当前单元格的值设置为左上方单元格的值加1
1.git diff, 指出两个文件的差异,也是使用动态规划实现的
2.生物学家根据最长公共序列来确定DNA链的相似性
3.字符串的相似程序,编辑距离指出了两个字符串的相似程度,也是通过动态规划计算得到的;编辑距离算法的用途很多,从拼写检查到判断用户上传的资料是否是盗版
本章内容
1.OCR(optical character recognition) 是光学子符识别,这以为着你可拍摄印刷页面的照片,计算机将自动识别出其中的文字
document.ready: ready事件是在DOM结构绘制完成之后就会执行
DOMContentLoaded: 当初始的 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,而无需等待样式表、图像和子框架的完成加载; 注意:DOMContentLoaded 事件必须等待其所属script之前的样式表加载解析完成才会触发。
window.onload: onload网页中所有内容(包括图片)全部加载完毕之后执行
资源加载和页面加载事件
iframe,如何在页面中改变另一个iframe的样式
document.getElementById('frame').contentWindow.document.getElementById('cc');
尽可能多的方法隐藏一个html元素
display: none;
visibility: hidden;
opacity: 0;
盒模型,IE盒模型和标准盒模型,如何改变
两者的区别在于content的不同, IE盒模型的content包括 border和 padding
box-sizing:border-box || content-box || inherit
content-box: 页面将采用标准盒模型模式来解析计算, 同时改模式为默认模式
border-box: 页面将采用怪异模式解析计算, 怪异模式也称为IE模式
选择器优先级(内联样式在何处)
!important > 内联样式(权重1000) > id选择器(权重100) > 类(class)选择器(权重10) > 标签选择器
less和scss的好处
都是css的预编译处理语言, 它们引入了mixins, 参数, 嵌套规则, 运算, 颜色, 名字空间, 作用域, javascript赋值
[1,2,3,4,1,2,2,2].findDuplicate(2) => [1,2]
[1,2,3,4,1,2,2,2].findDuplicate(5) => []
[1,2,3,4,1,2,2,2].findDuplicate(-1) => [1,2,3,4]
if (!Array.prototype.findDuplicate) {
Object.defineProperty(Array.prototype, 'findDuplicate', {
value: function (n) {
if (this === void 0 || this === null) {
throw new TypeError('this is null or not defined');
};
var t = Object(this);
var len = t.length >>> 0;
var obj = {};
var result = [];
for(let i = 0; i < len; i++) {
obj[t[i]] = (obj[t[i]] || 0)+1;
if(obj[t[i]] == n) {
result.push(t[i]);
}
}
return result;
}
});
}
箭头函数的好处
简洁, 简化回调函数, 可以与变量解构结合使用
判断数组
// 1.使用 instanceof
[] instanceof Array;
// 2.使用constructor
var a = new Array;
a.constructor === Array;
// 或
function isArray(arr){
return typeof arr === "object" && arr.constructor === Array;
}
// 3.Object.ProtoType.toString()
Object.prototype.toString.call([]); // "[object Array]"
可能返回的值如下
注意: typeof 的能力有限,其对于null, Date、RegExp类型返回的都是"object"
typeof null // 'object'
typeof {}; // "object"
typeof []; // "object"
typeof new Date(); // "object"
使用场景:区分对象和原始类型,要区分一种对象类型和另一种对象类型,可以使用: instanceof运算符或对象contructor属性
用法: 左边的运算数是一个object,右边运算数是对象类的名字或者构造函数;返回true或false
[] instanceof Array; // true
[] instanceof Object; // true
[] instanceof RegExp; // false
new Date instanceof Date; // true
如果object是class或者构造函数的实例,则返回true,如果不是或者是null也返回false
instanceof运算符判断是否为数组类型
function isArray(arr){
return arr instanceof Array;
}
new和instanceof的内部机制
for...in迭代和for...of有什么区别?
for in 用来遍历对象
for of 用来遍历数组
原型链中prototype和__proto__分别指什么
require和import的区别:
require是CommonJS模块, import是Es6模块,CommonJS是运行时加载, es6模块是编译时加载
class实现原理:
class是一个语法糖,很多都可以用es5的语法实现
async和await实现原理:
基于generator实现的,generator又是基于promise实现的
说一下你对generator的了解?
说一下macrotask 和 microtask?
node中事件队列模型
fetch api
HTTP和HTTPS的区别,如何升级成HTTPS
Http请求中的keep-alive有了解吗。
如何让网页离线后还能访问
强缓存和协商缓存
vue中用到的设计模式
webpack常见的loader和plugin,loader和plugin的区别
babel-polyfill
有一个资源加载性能分析工具- PageSpeed,它可以分析网页加载过程中出现的各种问题,并给出各种建议帮助开发者去除掉这些影响性能的问题;
原型链,类,实例, 类属性,类方法, 实例属性,实例方法
class A {
static b = '1'
static classMethod() {
return 'hello'
}
}
const a = new A();
a.c = 5;
a.sayHello = function() {
console.log('welcome')
}
A: 类
b: 类属性
classMethod: 类方法
a: 实例
a.c: 实例属性
static:静态属性指的是 Class 本身的属性,即Class.propName,而不是定义在实例对象(this)上的属性。
要想自己实现一个Promise,我们首先要对Promise的用法有所了解;
Promise 是一个构造函数,用来生成 Promise 实例,接受一个函数作为参数,这个函数有两个参数 resolve 和 reject,是两个函数。resolve 是异步操作成功时调用,并将异步操作的结果作为参数传递出去。reject 是异步操作失败时调用,并将异步操作报出的错误作为参数传递出去。
静态属性或方法:是存在类上的属性或方法,实例上无法访问,只用通过 Promise.xxx 访问
要实现 xxx,首先要明白这个函数是干什么的,那又从那个方面来分析么呢?
以下方法都是接受数组作为参数,数组中的某项如果不是Promise实例,会先调用Promise.resolve方法,将参数转为 Promise实例,再做处理
接收一组 Promise 实例作为参数,只有等到所有的参数实例都返回结果才会返回
场景:不关心异步操作的结果,只关心是否都结束了。比如多张图片选择后一起上传时,需要判断多张图片上传的结果,成功了几张,失败了几张
参数:数组,数组中的某项如果不是Promise实例,会先调用Promise.resolve方法,将参数转为 Promise实例,再做处理
返回值:
[
{
status: 'fulfilled',
value: 1,
},
{
status: 'rejected',
reason: 1,
},
];
接收一组 Promise 实例作为参数,只要有一个成功就返回成功的结果,如果所有结果都是失败的,就返回所有失败的结果
class Promise {
constructor(fn) {
/**
* 三种状态
* pending:进行中
* fulfilled:已成功
* rejected: 已失败
*/
this.status = 'pending';
this.resoveList = []; // 成功后回调函数
this.rejectList = []; // 失败后的回调函数
fn(this.resolve.bind(this), this.reject.bind(this));
}
then(scb, fcb) {
if (scb) {
this.resoveList.push(scb);
}
if(fcb) {
this.rejectList.push(fcb);
}
return this;
}
catch(cb) {
if (cb) {
this.rejectList.push(cb);
}
return this;
}
resolve(data) {
if (this.status !== 'pending') return;
this.status = 'fulfilled';
setTimeout(() => {
this.resoveList.forEach(s => {
data = s(data);
})
})
}
reject(err) {
if (this.status !== 'pending') return;
this.status = 'rejected';
setTimeout(() => {
this.rejectList.forEach(f => {
err = f(err);
})
})
}
/**
* 实现Promise.resolve
* 1.参数是一个 Promise 实例, 那么Promise.resolve将不做任何修改、原封不动地返回这个实例。
* 2.如果参数是一个原始值,或者是一个不具有then方法的对象,则Promise.resolve方法返回一个新的 Promise 对象,状态为resolved。
*/
static resolve(data) {
if (data instanceof Promise) {
return data;
} else {
return new Promise((resolve, reject) => {
resolve(data);
})
}
}
// 实现Promise.reject
static reject(err) {
if (err instanceof Promise) {
return err;
} else {
return new Promise((resolve, reject) => {
reject(err);
})
}
}
/**
* 实现Promise.all
* 1. Promise.all方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。
* 2. 返回值组成一个数组
*/
static all(promises) {
return new Promise((resolve, reject) => {
let promiseCount = 0;
let promisesLength = promises.length;
let result = [];
for(let i = 0; i < promises.length; i++) {
// promises[i]可能不是Promise类型,可能不存在then方法,中间如果出错,直接返回错误
Promise.resolve(promises[i])
.then(res => {
promiseCount++;
// 注意这是赋值应该用下标去赋值而不是用push,因为毕竟是异步的,哪个promise先完成还不一定
result[i] = res;
if(promiseCount === promisesLength) {
return resolve(result);
}
},(err) => {
return reject(err);
}
)
}
})
}
/**
* 实现Promise.race
* 1. Promise.race方法的参数与Promise.all方法一样,如果不是 Promise 实例,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。
* 2. 返回那个率先改变的 Promise 实例的返回值
*/
static race(promises) {
return new Promise((resolve, reject) => {
for(let i = 0; i < promises.length; i++) {
Promise.resolve(promises[i])
.then(res => {
return resolve(res);
},(err) =>{
return reject(err);
}
)
}
})
}
/**
* Promise.any()
* - 参数:数组,数组中的某项如果不是Promise实例,会先调用Promise.resolve方法,将参数转为 Promise实例,再做处理
- 返回值:
- 一个promise实例:只要有一个成功时返回一个 promise 实例的结果
- 数组:所有都失败时,返回数组
*/
static any(promises) {
return new Promise((resolve, reject) => {
const promisesLen = promises.length;
let promiseCount = 0;
let errorResult = [];
for (let i = 0; i < promisesLen; i++) {
Promise.resolve(promises[i])
.then((value) => {
return resolve(value);
},(err) => {
promiseCount++;
errorResult[i] = err;
if (promiseCount === promisesLen) {
return reject(errorResult);
}
},
);
}
});
}
/**
* Promise.allSettled()
* - 参数:数组,数组中的某项如果不是Promise实例,会先调用Promise.resolve方法,将参数转为 Promise实例,再做处理
* - 返回值:数组
*/
static allSettled(promises) {
return new Promise((resolve, reject) => {
const promisesLen = promises.length;
let promiseCount = 0;
const result = [];
for (let i = 0; i < promisesLen; i++) {
Promise.resolve(promises[i])
.then((value) => {
promiseCount++;
result[i] = {
status: 'fulfilled',
value,
};
if (promiseCount === promisesLen) {
return resolve(result);
}
},(err) => {
promiseCount++;
result[i] = {
status: 'rejected',
reason: err,
};
if (promiseCount === promisesLen) {
return resolve(result);
}
},
);
}
});
}
}
const p = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('resolve');
resolve(222);
}, 1000)
})
p.then(data => {
setTimeout(() => {
console.log('data', data);
})
return 3333;
}).then(data2 => {
console.log('data2', data2);
}).catch(err => {
console.error('err', err);
});
const p1 = Promise.reject('出错了');
p1.then(null, function (s) {
console.log(s); // 出错了
});
const q1 = new Promise((resolve, reject) => {
resolve('hello')
});
const q2 = new Promise((resolve, reject) => {
reject('world')
});
Promise.all([q1, q2])
.then((res) => {
console.log(res);
}, (err) => {
console.log(err); // world
});
Promise.race([q1, q2]).then(res => {
console.log(res); // hello
});
Promise.any([q1, q2]).then(res => {
console.log(res); // hello
});
Promise.allSettled([q1, q2]).then(res => {
console.log(res);
/**
[
{ status: 'fulfilled', value: 'hello' },
{ status: 'rejected', reason: 'world' }
]
*/
});
function test() {
console.log(this);
}
test(); //window
node.js中
console.log(this); // {}
function test() {
console.log(this);
}
test(); //global
var obj = {
say: function () {
setTimeout(() => {
console.log(this)
});
}
}
obj.say(); // obj,此时this指的是定义他的obj
var obj = {
name: 'objName',
say() {
console.log(this.name);
}
read: () => {
console.log(this);
console.log(this.name);
}
}
obj.say() // objName
obj.read() // {}, undefined
window.val = 1;
var obj = {
val: 2,
dbl: function() {
this.val *= 2;
val *= 2;
console.log(val);
console.log(this.val);
}
}
obj.dbl(); // 2 4
var func = obj.dbl;
func(); // 8 8
obj.dbl();执行这行代码时,this指的是obj,所以this.val === obj.val*=2,最后结果为4,val*=2 === window.val *= 2,最后结果是2
func(),执行这行代码时,func()没有任何前缀,this指的是window.func();所以此时this值得是window,this.val === window.val *= 2,此时window.val 为4,val*=2 === window.val *2,最后结果为8,最后console.log(this.val),与console.log(val),指的都是window.val,最后结果都是8
nginx location 详解
location [=|~|~*|^~] /uri/ { … }
匹配顺序
一个具体的请求path过来之后, nginx的具体匹配过程可以分为几步
在location后面接的表达式中的斜杠, 可有可无, 并没有影响;
而对于URL中的 / 则是当有/时表示目录, 没有时则表示文件.当有 /是服务器会自动去对应 目录下找默认文件, 而如果没有 / 则会优先去匹配文件. 如果找不到文件才会重定向到目录, 查默认文件
root指令会将location中的部分附加到root定义的末尾形成一个完整的路径;
alias则不会包含location中定义的部分
比如:
location /static {
root /var/www/app/static/;
autoindex off;
}
那么当 Nginx 寻找路径时会是:
/var/www/app/static/static/
如果这个在 static 目录的 static 目录不存在则显而易见会产生 404 错误。这是因为 location 中的 static 部分被附加到了 root 指定的路径后面,因此正确的做法是:
location /static {
root /var/www/app/;
autoindex off;
}
而对于 alias 正确的做法则是:
location /static {
alias /var/www/app/static/;
autoindex off;
}
这种定位最大的好处就是可以针对任何标签来定位
给一个a标签一个href=“#锚点”
,然后要跳的锚点给个id=“锚点”
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>锚点</title>
<style>
#div1 {
margin-top: 2000px;
}
</style>
</head>
<body>
<h2>
<a href="#div1">to div1</a>
</h2>
<div id="div1">div1</div>
</body>
</html>
使用 name 定位只用于 <a>
标签,其他的标签就不管用了。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>锚点</title>
<style>
a[name="a"]{
margin-top: 2000px;
display: block;
}
</style>
</head>
<body>
<h2>
<a href="#a">定位到A</a>
</h2>
<a name="a">A</a>
</body>
</html>
element.scrollIntoView()方法让当前元素滚动到浏览器窗口的可视区域内(有浏览器的兼容问题)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>锚点</title>
<style>
#a {
margin-top: 2000px;
}
</style>
</head>
<body>
<div onclick="javascript:document.getElementById('a').scrollIntoView()">
定位到A
</div>
<div id="a">
A
</div>
</body>
</html>
console.log(process.argv);
在终端输入 node shell.js
[ '/Users/wangyaxing/.nvm/versions/node/v10.7.0/bin/node',
'/Users/wangyaxing/temp/vue-project/shell.js' ]
在终端输入 node shell.js abc
[ '/Users/wangyaxing/.nvm/versions/node/v10.7.0/bin/node',
'/Users/wangyaxing/temp/vue-project/shell.js',
'abc' ]
process.argv的用法是第一个是node文件, 第二个是脚本文件, 第三个是参数
npm 允许在package.json文件里面,使用scripts字段定义脚本命令。
{
// ...
"scripts": {
"build": "node build.js"
}
}
上面代码是package.json文件的一个片段,里面的scripts字段是一个对象。它的每一个属性,对应一段脚本。比如,build命令对应的脚本是node build.js。
执行npm run ,会自动创建一个shell, 在这个shell里面执行指定的脚本命令。
比较特别的是, 这个shell 会自动将当前目录下的node_modules/.bin子目录加入PATH
,执行结束, 再将PATH
变量恢复原样
由于 npm 脚本就是 Shell 脚本,因为可以使用 Shell 通配符。
"lint": "jshint *.js"
"lint": "jshint **/*.js"
上面代码中,*表示任意文件名,**表示任意一层子目录。
如果要将通配符传入原始命令,防止被 Shell 转义,要将星号转义。
"test": "tap test/\*.js"
将命令行参数发送到npm脚本:
npm run [command] [-- <args>]
注意必要的--
,需要将参数传递到npm命令本身,并将其传递给脚本。
plugins: [
new webpack.DefinePlugin({
'domain': process.argv[2]
}),
}
redux的三个概念: action reducer store
function action(data) {
return {
type: 'Action1',
data,
};
}
function reducer(state = { data: 1 }, action) {
switch (action.type) {
case 'action1': {
const newState = JSON.parse(JSON.stringify(state));
return newState;
}
case 'action2': {
const newState = JSON.parse(JSON.stringify(state));
return newState;
}
default: {
return state;
}
}
}
import { createStore } from 'redux';
let store = createStore(reducer);
console.log(store.getState())
store.dispatch(action(0))
console.log(store.getState())
注意! 更新数据需要dispatch对应的action
用于将redux于react结合
import { Provider } from 'react-redux';
ReactDom.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('app'),
);
import { connect } from 'react-redux';
class App extends Component {
componentDidMount() {
this.props.dispatch({
type: 'action1',
data: 1,
});
}
render() {
// this.props.func1();
// this.props.func2();
return (
<div>
</div >
);
}
}
export default connect(
state => ({
data: state.data,
obj: state.obj,
}),
dispatch => ({ dispatch }),
)(App);
特殊的object(MAP)/array(LIST)
上述的reducer可以用下面的代码优化
function reducer(state = immutable.fromJS({ data: 1, obj: { aa: 1 } }), action) {
switch (action.type) {
case 'action1': {
return state.set('data', action.data * 10);
}
case 'action2': {
return state.setIn(['obj', 'aa'], action.data);
}
default: {
return state;
}
}
}
自动创建shouldComponentUpdate的工具, 需要配置babel decorator特性才能使用
function foo(x) {
return g(x);
}
尾调用不一定出现在函数尾部,只要是最后一步操作就行
function f(x) {
if (x > 0) {
return m(x)
}
return n(x);
}
函数m和n都属于尾调用,他们都是函数的最后一步操作
函数调用会在内存形成一个’调用记录‘,保存调用位置和内部变量等信息,如果在A函数中调用B函数,在A函数的调用记录上还会形成一个B的调用记录,等到B函数运行结束后,把结果返回到A,B的调用记录才会消失。如果函数B内部还调用C函数,那就是还有一个C的调用记录栈,以此类推,所有的调用记录,就会形成一个’调用栈‘。
尾调用在于它特殊的调用位置。由于是函数最后一步操作,所以不需要保留外层函数的调用记录,因为调用位置和内部变量都不会再用到了。
’尾调用优化‘,只调用内层函数的调用记录。如果所有函数都是尾调用,那么完全可以做到每次执行时,调用记录只有一项,这将大大节省内存。
浏览器与服务器通信的方式为应答模式, 即是:
浏览器发起HTTP请求-服务器响应请求, 那么浏览器怎么确定一个资源该不该请求缓存呢?
浏览器第一次向服务器发起该请求后拿到请求结果, 将请求结果和缓存标识存入浏览器缓存, 浏览器对于缓存的处理是根据第一次请求资源时返回的响应头来确定的
强制缓存优先于协商缓存, 强制缓存(expires和Cache-control)生效则直接使用强制缓存, 若不生效则进行协商缓存(Last-Modified/If-Modified-Since和Etag/If-None-Match), 协商缓存由服务器决定是否使用缓存, 如果协商缓存失效, 那么代表该请求的的缓存失效, 返回200,重新返回资源的缓存标识, 再存入浏览器缓存中; 生效则返回304, 继续使用缓存;
Cache-control优先级高于expires
Etag/If-None-Match优先级高于Last-Modified/If-Modified-Since
区别
不会向服务器请求资源, 直接从缓存中读取资源, 在chrome控制台的netWork选项中可以看到该请求返回200的状态码, size显示 from disk cache和from memory cache;
协商缓存是无法减少请求数的开销的, 但是可以减少返回的正文大小;
1.地址栏访问, 链接跳转是正常用户的行为, 将会触发浏览器缓存机制
2.F5刷新, 浏览器会设置max-age=0, 跳过强制缓存判断, 会进行协商缓存判断
3.ctrl+F5刷新, 会跳过强制缓存和协商缓存, 直接从服务器拉取资源
javascript是一门编译语言,不是提前编译,边编译边执行;'动态'或解释执行
编译
1、分词/词法分析
2.解析/语法分析
AST(抽象语法树): Abstract Syntax Tree ?
3.代码生成
AST转换成可执行代码
引擎?,编译器 ?,作用域
LHS
RHS
LHS: 赋值操作的目标;
a = 2
RHS: 谁是赋值操作的源头; 得到某某的值
console.log(a);
去找到a的值,并把它给我
function foo(a) {
console.log(a);
}
foo(2);
a = 2; LHS查询, 2会被分配给参数a
对a的RHS引用,并且经得到的值传给 console.log(..);console.log(...)本身也需要一个引用才能执行,因此会对console对象进行RHS查询,并且检查得到的值中是否有一个叫log的方法
在变量还未声明的情况下,两种查询的行为是不一样的;
RHS查询不到,引擎报 ReferenceError异常
LHS查询不到,(非严格模式下)全局作用域就会创建一个具有该名称的变量,并将其返还给引擎
RHS查询找到, 尝试对变量进行不合理的操作,试图
1.对一个非函数类型的值进行函数调用;
2.引用null和undefined类型的值中的属性,引擎会抛出 TypeError
ReferenceError同作用域判别失败有关,TypeError代表作用域判别失败了,但是岁结果的操作是非法或不合理的
如果查找的目的是对变量进行赋值,就会使用LHS操作,如果目的是获取变量的值,就会使用RHS查询
赋值操作符会导致LHS查询. = 操作符或调用函数时传入的参数操作都会导致关联作用域的赋值操作
function foo(a) {
var b = a;
return a + b;
}
var c = foo(2);
3处LHS查询
c = ..; a = 2(隐式变量分配); b = ..
4处RHS查询
foo(2.., = a; a.. ..b
词法作用域以为这作用域是由书写代码函数声明的位置来决定的;编译的词法分析阶段基本能够知道全部标识在哪里以及是如何声明的,从而能够预测在执行过程中如何对它们进行查找;
javascript中有两个机制可以'欺骗'词法作用域: eval(..)和with;前者可以对一段包含或多个声明'代码'字符串进行演算,并借此来修改已经存在的的词法作用域(运行时);后者本质上是通过讲一个对象的引用当做作用域来处理,将对象的属性当做作用域中的标识来处理,从而创建了一个新的词法作用域(同样是在运行时);
这两个机制的副作用是引擎无法再编译时对作用域查找进行优化;因此引擎只能谨慎地认为这样的优化是无效的.使用这其中任何一个机制都将导致代码运行变慢,不要使用它们;
function是声明的第一个词,就是一个函数声明,否则就是函数表达式;
函数表达式可以是匿名的(匿名函数表达式),函数声明则不可以省略函数名
(function foo() { .. })(); => IIFE 立即执行函数表达式
第一个()将函数变成表达式,第二个()执行了这个函数;
(function foo() { .. }());
引擎会在解释javascript代码之前首先对其进行编译,编译阶段中的一部分工作就是找到所有的声明,并用合适的作用域将他们关联起来
包含变量和函数在内的所有声明都会在任何代码前首先被处理;可以形象的想象成所有的声明(变量和函数)都会被'移动'到各自作用域的最顶端,这个过程被称为提升
var a = 2;
javascript实际上会将其看成两个声明: var a; 和a = 2;
第一个定义声明是在编译阶段进行的;第二个赋值声明会被留在原地等待执行阶段
a = 2;
var a;
console.log(a);
其中一部分是编译,而第二部分是执行;
console.log(a);
var a = 2;
实际按照以下流程处理
var a;
console.log(a);
a = 2;
只有变量声明和函数声明会被提升,但是函数表达式却不会被提升
foo(); // 不是ReferenceError, 而是TypeError
bar(); // ReferenceError
var foo = function bar() {
}
变量标识符foo()被提升并分配给所在作用域(在这里是全局作用域),因此foo()不会导致ReferenceError;
但是foo此时并没有赋值(如果它是一个函数声明而不是函数表达式,name就会赋值). foo()由于对undefined值进行函数调用而导致非法操作,因此跑出TypeError
函数声明和变量声明都会被提升,但是有一个值得注意的细节(这个细节可以出现在有多个'重复'声明的代码中)但是函数会首先被提升,然后才是变量,但是出现在后面的函数声明还是可以覆盖前面的
当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使是在当期词法作用域之外执行的
function foo() {
var a = 2;
function bar() {
console.log(a);
}
return bar;
}
var baz = foo();
baz();
函数bar()的作用域能够访问foo()的内部作用于,然后我们将bar()函数本身当做一个值类型进行传递;在这个例子中,我们将bar所引用的函数对象本身当做返回值;
在foo()执行后,其返回值(也就是内部bar()函数)赋值给变量baz并调用baz();实际上只是通过不同的标识符引用调用了内部函数bar();
?
function wait(message) {
setTimeout(function timer() {
console.log(message);
}, 1000)
};
wait('hello, closure')
将一个内部函数(timer)传递给setTimeout(..), timer具有涵盖wait(..)作用域的闭包,因此还保有对变量message的引用
?
function makeAdder(x) {
function add(y) {
return y + x;
}
return add;
}
var plusOne = makeAdder(1);
var plusTen = makeAdder(10);
console.log(plusOne(3)); // 4
console.log(plusTen(41)); // 51
console.log(plusTen(13)); // 23
本质上无论何时何地,如果将函数(访问它们各自的词法作用域)当做第一级的值类型并到处传递, 你就会看到闭包在这些函数中的应用;
在定时器,事件监听器,ajax请求, 跨窗口通信, Web Workers或者任何其他的异步(或者同步)任务中,只要使用了回调函数,实际上就是使用了闭包
for(var i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i);
}, i*1000)
}
运行时会以每秒一次的频率输出5次6
为什么?
1.6是哪里来的,这个循环的终止条件是i不再<=5;条件首次成立时i的值是6; 因此,输出显示的是循环结束时i的终值
2.延时函数的回调会在循环结束时才执行
我们试图假设循环中的每个迭代在运行时都会给自己'捕获'一个i的副本,但是根据作用域的工作原理,实际情况是尽管循环中的五个函数是在个个迭代中分别定义的,但是他们都被封闭在一个共享的全局作用域,因此实际上只有一个i
for(var i = 1; i <= 5; i++) {
(function (j) {
setTimeout(function timer() {
console.log(j);
}, j*1000)
})(i)
}
在迭代内使用IIFE会为每个迭代都生成一个新的作用域,使得延迟函数的回调可以将新的作用域封闭在每个迭代内部,每个迭代中都会含有一个具有正确值的变量供我们访问;
function CoolModule() {
var something = 'cool';
var another = [1, 2, 3];
function doSomething() {
console.log(something);
}
function doAnother() {
console.log(another.join("!"));
}
return {
doSomething: doSomething,
doAnother: doAnother,
}
}
var foo = CoolModule();
foo.doSomething();
foo.doAnother();
模块模式需要具备两个必要条件
1.必须有外部的封闭函数,该函数必须至少被调用一次(每次调用都会创建一个新的模块实例);
2.封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,而且可以访问或者修改私有的状态
var MyModules = (function Manager() {
var modules = {};
function define(name, deps, impl) {
for(var i = 0; i < deps.length;i++) {
deps[i] = modules[deps[i]];
}
modules[name] = impl.apply(impl, deps);
}
function get(name) {
return modules[name];
}
return {
define: define,
get: get,
}
})();
MyModules.define('bar', [], function() {
return hello(who) {
return 'let me introduce: ' + who;
}
return {
hello: hello,
}
});
MyModules.define('foo', ['bar'], function(bar) {
var hunhry = 'hippo';
function awesome() {
console.log(bar.hello(hungry).toUpperCase());
}
return {
awesome: awesome
}
});
var bar = MyModules.get('bar');
var foo = MyModules.get('foo');
console.log(bar.hello('hippo'));
foo.awesome();
function foo() {
console.log(a); // 2
}
function bar() {
var a = 3;
foo();
}
var a = 2;
bar();
我们写css的时候经常会遇到要清楚一些默认的样式,其实每次做项目中需要清除的样式就经常是那么几个,最常见的比如
input
,select
,textarea
的默认样式,所以就总结了一下,持续更新中...,也请大家多多贡献更多的常见的需要清除默认样式的方法😊
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
line-height: 1.3rem;
max-height: 2.6rem;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
CSS3中隐藏滚动条但仍能继续滚动
::-webkit-scrollbar {
width: 0px;
height: 0px;
}
清除浏览器默认的滚动条
::-webkit-scrollbar, ::-webkit-scrollbar-track, ::-webkit-scrollbar-thumb, ::-webkit-scrollbar-corner {
display: none;
}
-webkit-overflow-scrolling : touch;
input
,textarea
等的默认样式input, button, select, textarea {
outline: none;
-webkit-appearance: none;
border-radius: 0;
border:0;
}
textarea{
resize:none;
}
outline: none;
去掉chrome浏览器自带的点击input框出现边框情况-webkit-appearance: button;
使元素标签看起来像个按钮样式,意思定义了按钮样式-webkit-appearance: none;
去掉按钮样式border-radius: 0;
去掉圆角border:0;
去掉bordertextarea{resize:none}
取消chrome下textarea可拖动放大:项目前端目录及文件构建(vue-cli)
npm install -g vue-cli
vue init webpack my-project
cd my-project
npm install
npm run dev
slice方法返回一个从开始到结束(不包括结束)选择的一部分浅拷贝到另一个新数组的对象,且原始数组不会被修改
伪数组
也可以使用数组的各种方法;// 按照新的ES6的行为方式实现
function foo(...args) {
// args已经是一个真正的数组
// 丢弃args中的第一个元素
args.shift();
// 将整个args作为参数传给console.log(..)
console.log(...args);
}
// 按照前ES6的老派行为方式实现
function bar() {
// 把arguments转换为一个真正的数组
var args = Array.prototype.slice.call(arguments);
// 在尾端添加几个元素
args.push(4, 5);
// 过滤掉奇数
args = args.filter(function(v) {
return v % 2 == 0;
})
// 把整个args作为参数传给foo(..)
foo.apply(null, args);
}
bar(0, 1, 2, 3); // 2, 4
函数foo(..)声明的...args收集参数,console.log(..)调用中的...args将其展开;
Array.prototype.slice.call()方法详解
Array.prototype.slice() MDN
####1,小程序接口异步问题
const wx = {
fun1: () => console.log(1),
fun2: ({a, b, success, fail}) => {
if (a + b > 10) {
fail('error: a + b > 10');
return;
}
success(a + b);
},
};
function promiseify(func) {
return (args) => {
return new Promise((resolve, reject) => {
func(Object.assign(args, {
success: resolve,
fail: reject,
}));
})
}
}
for (const key in wx) {
if (Object.prototype.hasOwnProperty.call(wx, key) && typeof wx[key] === 'function') {
wx[`_${key}`] = promiseify(wx[key]);
}
}
wx._fun2({a: 3, b: 4}).then(sum => {
console.log(sum);
}).catch(err => {
console.log(err);
});
wx._fun2({a: 5, b: 6}).then(sum => {
console.log(sum);
}).catch(err => {
console.log(err);
});
程序语言的编码风格指南对于一个长期维护的软件而言是非常重要的;
团队合作需要制定一些代码规范还有利用一些工具来强制要求团队代码的风格统一.毕竟很多情况下以后不一定是由写一手代码的人来维护代码,所以有一个统一的代码风格很重要!!!
最近看了一下编写可维护的JavaScript和编写高质量代码:Web前端开发修炼之道,根据书中提倡的一些写法,同时结合我个人的经验和喜好做了一些改动,大致整理了如下JavaScript编码风格
每一行的层级由4个空格组成,避免使用制表符(Tab)进行缩进
if (true) {
doSomething();
}
每行长度不应该超过80个字符.如果一行多于80个字符,应当在一个运算符(逗号,加好等)后换行.下一级应当增加两级缩进(8个字符).
// 好的写法
doSomething(arg1, arg2, arg3, arg4,
arg5);
// 不好的写法: 第二行只有4个空格的缩进
doSomething(arg1, arg2, arg3, arg4,
arg5);
// 不好的写法: 在运算符之前换行
doSomething(arg1, arg2, arg3, arg4
,arg5);
特殊值null除了下述情况应当避免使用
// 好的做法
var person = null;
判断一个变量是否定义应当使用 typeof 操作符
// 好的写法
if (typeof variable == 'undefined') {
// do something
}
// 不好的写法
if (variable == 'undefined') {
// do something
}
二元运算符前后必须使用一个空格来保持表达式的整洁.操作符包括赋值运算符和逻辑运算符
// 好的写法
var found = (values[i] === item);
// 不好的写法: 丢失了空格
var found = (values[i]===item);
// 好的写法
if (found && (count > 10)) {
doSomething();
}
// 不好的写法: 丢失了空格
if (found&&(count>10)) {
doSomething();
}
// 好的写法
for(let i = 0; i < count; i++) {
process(i);
}
// 不好的写法: 丢失了空格
for(let i=0; i<count; i++) {
process(i);
}
当使用括号时,紧接左括号之后和紧接右括号之前不应该有空格
// 好的写法
var found = (values[i] === item);
// 不好的写法: 左括号之后有额外的空格
var found = ( values[i] === item);
// 好的写法
if (found && (count > 10)) {
doSomething();
}
// 不好的写法: 右括号之后有额外的空格
if (found && (count > 10) ) {
doSomething();
}
// 好的写法
for(let i = 0; i < count; i++) {
process(i);
}
// 不好的写法: 参数两边有额外的空格
for(let i = 0; i< count; i++) {
process( i );
}
对象直接量应当使用如下格式
// 好的写法
var object = {
key1: value1,
key2: value2,
func: function() {
},
key3: value3,
};
// 不好的写法: 不恰当的缩进
var object = {
key1: value1,
key2: value2,
};
// 不好的写法:函数体缺少空行
var object = {
key1: value1,
key2: value2,
func: function() {
},
key3: value3,
};
当对象字面量作为函数参数时,如果值是变量,起始花括号应当同函数名在同一行.所有其余先前列出的规则同样适用
// 好的写法
doSomething({
key1: value1,
key2: value2,
});
// 不好的写法
doSomething({ key1: value1, key2: value2 });
频繁地适用注释有助于他人理解你的代码.如下情况应当使用注释
使用单行注释当用来说明一行代码或者一组代码.单行注释可能有三种使用方式
// 好的写法
if (condition) {
// 如果代码执行到这里,则说明通过了所有的安全性检测
allowed();
}
// 不好的写法:注释之前没有空行
if (condition) {
// 如果代码执行到这里,则说明通过了所有的安全性检测
allowed();
}
// 不好的写法: 错误的缩进
if (condition) {
// 如果代码执行到这里,则说明通过了所有的安全性检测
allowed();
}
// 不好的写法: 这里应当用多行注释
// 接下来的这段代码非常难, 那么,让我详细的解释一下
// 1. xxxx
// 2. xxxx
if (condition) {
// 如果代码执行到这里,则说明通过了所有的安全性检测
allowed();
}
对于代码行尾单行注释的情况,应确保代码结尾同注释之间至少一个缩进
// 好的写法
var result = something + somethingElse; // somethingElse will never be null
// 不好的写法: 代码和注释间没有足够的空格
var result = something + somethingElse;// somethingElse will never be null
注释一个代码块时在连续多行使用单行注释是唯一可以接受的情况.多行注释不应当在这种情况下使用
// 好的写法
// if(condition) {
// doSomething();
// }
多行注释应当在代码需要更多文字去解释的时候使用.每个多行注释都至少有如下三行.
1.首行仅仅包括 /* 注释开始.该行不应当有其他文字
2.接下来的行以 * 开头并保持左对齐.这些行可以由文字描述
3.最后一行以 */开头并同先前行保持对齐.也不应当有其他文字
多行注释的首行应当保持同它描述代码的相同层次的缩进.后续的每行应当有同样层次的缩进并附加一个空格(为了适当保持 * 字符的对齐).每一个多行代码之前应当预留一个空格
// 好的写法
if (condition) {
/*
* 如果代码执行到这里
* 说明通过了所有的安全性检测
*/
allowed();
}
// 不好的写法: 注释之前无空行
if (condition) {
/*
* 如果代码执行到这里
* 说明通过了所有的安全性检测
*/
allowed();
}
// 不好的写法: 星号后没有空格
if (condition) {
/*
*如果代码执行到这里
*说明通过了所有的安全性检测
*/
allowed();
}
// 不好的写法: 错误的缩进
if (condition) {
/*
* 如果代码执行到这里
* 说明通过了所有的安全性检测
*/
allowed();
}
// 不好的写法: 代码尾部注释不要用多行注释格式
var result = something + somethingElse; /* somethingElse 不应当取值为null */
注释有时候可以用来给一段代码声明额外的信息.这些声明的格式以单个单词打头并紧跟一个双引号.可使用的声明如下
变量命名应当采用驼峰命名格式,首字母小写,每个单词首字母大写.变量名的第一个单词应当是一个名词(而非动词)比避免同函数混淆.不要在变量名中使用下划线
// 好的写法
var myName = 'Jack';
// 不好的写法: 大写字母开头
var MyName = 'Jack';
// 不好的写法: 动词开头
var getMyName = 'Jack';
// 不好的写法: 使用下划线
var my_name = 'Jack';
函数命名应当采用驼峰命名格式.函数名的第一个单词应当是动词(而非名词)来避免同变量混淆.函数名中最好不要使用下划线.
// 好的写法
function doSomething() {
// 代码
}
// 不好的写法: 大写字母开头
function DoSomething() {
// 代码
}
// 不好的写法: 名词开头
function car() {
// 代码
}
// 不好的写法: 使用下划线
function do_something() {
// 代码
}
构造函数--通过new元素安抚创建新对象的函数--也应使用驼峰合适命名,首先首字母大写.构造函数命名应当以非动词开头,因为new代表着创建一个对象实例的操作
// 好的写法
function MyObject() {
}
// 不好的写法: 小写字母开头
function myObject() {
}
// 不好的写法: 使用下划线
function My_Object() {
}
// 不好的写法: 动词开头
function getMyObject() {
}
常量(不会被改变的变量)的命名应当是所有字母大写,不同单词之间用单个下划线隔开
// 好的写法
var TOTAL_COUNT = 10;
// 不好的写法
var totalCount = 10;
// 不好的写法: 混合模式
var total_COUNT = 10;
对象的属性同变量的命名规范相同.对象的方法同函数的命名规则相同.如果属性或者方法是私有的,应当在之前加一个下划线
// 好的写法
var object = {
_count: 10,
_getCount: function() {
return this._count;
}
}
当给变量赋值时,如果右侧是含有比较语句的表达式,需要用圆括号包裹
// 好的写法
var flag = (i < count);
// 不好的写法:遗漏圆括号
var flag = i < count;
使用 === (严格相等) 和 !==(严格不相等)代替 ==(相等) 和 !=(不等) 来避免弱类型转换错误
// 好的写法
var same = (a === b);
// 不好的写法: 使用 ==
var same = (a == b);
三元运算符应当仅仅用在条件赋值语句中,而不要作为if语句的替代品.
// 好的写法
var value = condition ? value1 : value2;
// 不好的写法: 没有赋值,应当使用 if 表达式
condition ? doSomething() : doSomethingElse();
每一行最多只包含一条语句.所有简单的语句都应该以分号(;)结束.
// 好的写法
var a = 1;
var b = 2;
var c = a + b;
// 不好的写法: 多个表达式写在一行
var a = 1;var b = 2;var c = a + b;
返回语句当返回一个值的时候不应当使用圆括号包裹,除非在某些情况下这么做可以让返回值更容易理解.例如:
return;
return collection.size();
return (size > 0 ? size : defaultSize)
复合语句是大括号括起来的语句列表;
if (condition) {
statements
} else if (condition) {
statements
} else {
statements
}
绝不允许在if语句中省略花括号
// 好的写法
if (condition) {
doSomething();
}
// 不好的写法: 不恰当的空格
if(condition){
doSomething();
}
// 不好的写法: 遗漏花括号
if (condition)
doSomething();
// 不好的写法: 所有代码写在一行
if (condition) { doSomething(); }
// 不好的写法: 所有代码写在一行且没有花括号
if (condition) doSomething();
for (initialization; condition; update) {
statements
}
for (variable in object) {
statements
}
当使用 for-in 语句时,记得使用 hasOwnProperty() 进行双重检查来过滤出对象的成员
while (condition) {
statements
}
do {
statements
} while (condition)
switch (expression) {
case expression:
statements
default:
statements
}
switch下的每一个case都叮当保持一个缩进.除第一个之外包括default在内的每一个case都应当在之前保持一个空行
每一组语句(除了default)都应当以break, return, throw结尾,或者用一行注释表示跳过
// 好的写法
switch (value) {
case 1:
/* falls through */
case 2:
doSomething();
break;
case 3:
return true;
default:
throw new Error('this should not happen');
}
try {
statements
} catch (variable) {
statements
} finally {
statements
}
严格模式应当仅限在函数内部使用,千万不要在全局使用.
ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict";。
所有的变量在使用前都应事先定义.变量定义应放在函数开头.
变量定义前应当初始化,并且赋值操作符应当保持一致的缩进.初始化的变量应当在未初始化变量之前.
函数声明应当在使用前提前定义.
一个不是作为方法的函数(也就是没有作为一个对象的属性)应当使用函数定义的格式(不是函数表达式和Function构造器格式).
函数名和开始圆括号之前不应当有空格.结束的圆括号和右边的花括号之间应该留一个空格.右侧的花括号应当同function关键字保持同一行.开始和结束括号之间不应该有空格.参数名之间应当在逗号之后保留一个空格.函数体应当保持一级缩进
// 好的写法
function doSomething(arg1, agr2) {
return arg1 + arg2;
}
// 不好的写法: 第一行不恰当的空格
function doSomething (arg1, agr2) {
return arg1 + arg2;
}
// 不好的写法:
var doSomething = function doSomething(arg1, agr2) {
return arg1 + arg2;
}
// 不好的写法: 左侧的花括号位置不对
function doSomething(arg1, agr2)
{
return arg1 + arg2;
}
// 错误的写法: 使用Function构造器
var doSomething = new Function('arg1', 'agr2', 'return arg1 + arg2');
在逻辑相关的代码块之间添加空行可以提高代码的可读性
两行空行权限在如下情况使用
单行空行权限在如下情况使用
空格应当在如下情况中使用
eslint规则在.eslintrc.js中定义,觉得不合理的可以禁掉某条规则,或者有好的建议的也可以添加;
主要注意一下几条:
还有一些情况是不需要检测的,例如第3方的库, 框架、组件、ui库等等,可以将这些文件放在.eslintignore文件中,可以忽略eslint的检测
在文件顶部加上下面这行,可以禁掉整个文件的eslint规则
/* eslint-disable */
代码提交之前会强制code-review,不符合规范的不允许提交代码
使用方法
1.在命令行安装
npm i --save-dev pre-commit
2.在package.json中配置
{
"scripts": {
"eslint": "eslint ./ --ext js,vue --ignore-pattern .eslintignore --cache --fix",
"lint-message": "echo '开始 eslint 检查, 存在 error 则会拒绝提交'"
},
"pre-commit": [
"lint-message",
"eslint" // 进行eslint检查并自动修复一些简单的格式错误
],
}
代码提交之前会强制code-review,不符合规范的不允许提交代码
如果项目实在没时间去改的话,可以 git commit -m 'XXX' --no-verify 或 git commit -n 'xxx'
强制提交
vscode安装eslint插件,在配置中配置如下
{
"eslint.autoFixOnSave": true,
"eslint.enable": true,
"eslint.options": {
"extensions": [".js", ".vue", ".jsx"]
},
"eslint.validate": [
{
"language": "vue",
"autoFix": true
},
{
"language": "javascript",
"autoFix": true
},
{
"language": "javascriptreact",
"autoFix": true
}
],
}
通过工具'摇'我么的JS文件,将其中用不到的代码'摇'掉;
具体来说, 在webpack项目中,有一个入口文件,相当于一棵树的主干,m入口文件有很多依赖模块,相当于树枝.实际情况中,虽然依赖了某个模块,但其实只使用其中的某些功能,通过tree-,shaking,将没有使用的模块摇掉,这样来达到删除无用代码的目的;
支持tree-shaking的构建工具
Tree-shaking的本质是消除无用的js代码. 无用代码消除在广泛存在于传统的编程语言编辑器;编译器可以判断出哪些代码根本不影响输出,然后消除这些代码,这个称之为DCE(dead code elimination);
Tree-shaking是DCE的一种新的实现,JavaScript同传统的编译语言不同的是,javascript绝大多数情况需要通过网络进行加载,然后执行,加载的文件大小越小,整体执行时间更短,所以去除无用代码以减少文件体积,对JavaScript来说更有意义
Tree-shaking和传统的DCE方法又不太一样,传统的DCE消灭不可能执行的代码,而Tree-shaking更关注与消除没有用到的代码
编译型语言:程序执行之前,有个单独的编译过程,将程序翻译成机器语言,以后执行这个程序的时候,就不用再进行翻译了;
解释性语言: 是在运行的时候将程序翻译成机器语言,所以运行速度相对于编译型语言要慢
https://zhidao.baidu.com/question/193596037.html
传统编译型的语言中,都是由编译器将Dead Code从AST(抽象语法树)中删除,那Javascript中由谁来做DCE呢
首先肯定不是浏览器做DCE,因为当我们的代码送到浏览器,那还谈什么消除无法执行的代码来优化,所以肯定是送到浏览器之前的步骤进行优化
主要关注与无用模块的消除
消除原理依赖ES6的模块特性
ES6 module特点
ES6模块依赖关系是确定的,和运行时的状态无关,可以进行可靠度的静态分析,这是Tree-shaking的基础
所谓静态分析就是不执行代码,从字面量上对代码进行分析,ES6之前的模块化,如果动态require一个模块,只有执行后才知道引用的什么模块,不能通过静态分析去优化;
设置cookie => cookie被自动添加到request header中 => 服务端接收到cookie
当网页要发送http请求时, 浏览器会先检查是否有相应的cookie, 有则自动添加request header字段中.这些事浏览器自动帮我们做的, 而且每一次http请求浏览器都会自动帮我们做;
每个域名下的cookie的大小最大为4kb, 每个域的cookie数量最多为20个
cookie既可以由服务端设置, 也可以由客户端来设置
不管你是请求一个资源文件(如html/js/css/图片), 还是发送一个ajax请求, 服务端都会返回response.而response header中有一项叫set-cookie, 是服务端专门用来设置cookie的;
set-cookie
document.cookie = "name=xiaoming; age=12 "
a.com默认的domain是a.com, test.a.com和test1.a.com访问都会有跨域问题, 如果想一个cookie所有以"a.com"结尾的域名中都可以访问, 则需要设置domain为".a.com";
domain可以访问该Cookie的域名。如果设置为“.baidu.com”,则所有以“baidu.com”结尾的域名都可以访问该Cookie;第一个字符必须为“.”
cookie保存在客户端, session保存在服务端
常用的是sessionId存在cookie中
浏览器提供了两个原生属性
存在一下的问题:
TouchEvent是一类描述手指在触摸平面(触摸屏, 触摸板等)的状态变化事件.这类事件用于描述一个或多个触电, 使开发者可以检测触点的移动, 触点的增加和减少等
每个touch对象代表一个触点:每个触点都由位置, 大小, 形状, 压力大小和目录element描述.TouchList对象代表多个触点的一个列表
除了常见的DOM属性以外, 触摸事件还包含下列用于跟踪触摸的属性
每个touch对象包含下列属性
屏幕
的位置屏幕
的位置的位置(Y坐标)浏览器窗口
的位置(X坐标)浏览器窗口
的位置页面
的位置(X坐标)页面
的位置(Y坐标)非标准的事件;
当两个手指触摸屏幕时就会产生手势
null
console.log(typeof null); // object
原理: 不同的对象在底层都表示为二进制,而javascript二进制前三位都为0的话会被判断为object类型,null的二进制表示是全0,自然前三位也是0, 所以执行typeof 时会返回object
函数值对象的一个子类型(从技术角度来说就是'可调用的对象')
var strObject = new String('I am a string');
Object.prototype.toString.call(strObject); // [Object String]
原始值I am a string
并不是一个对象,它只是一个字面量,而且是一个不可变的值,如果要在这个字面量上执行一些操作,比如获取
长度,访问其中某个字符,那就需要将其转换为String对象;
var strObject = 'I am a string';
console.log(strObject.length);
console.log(strObject.charAt(3));
引擎自动将字面量转换成String对象,所以可以访问属性和方法;类似 1.2345.toFixed(2)的方法,引擎会把1.2345转换为new Number(1.2345);
null和undefined没有对象的构造形式,只有文字形式;Date只有构造形式,没有文字形式;
Object,Array,Function, RegExp,无论使用文字形式还是构造形式,他们都是对象,不是字面量;
.操作符和[]操作符;.a语法为'属性访问', ['a]语法是'键访问';区别['..']可以接受任意UTF-8/Unicode字符串作为属性名;
在对象中,属性名永远都是字符串
1.可计算属性名
[]包裹一个表达式来当做属性名
in操作符会检查属性是否在对象及其[[prototype]]原型链中, hasOwnProperty(..)只会检查属性是否在myObject对象中,不会检查[[protoType]]链;
普通对象都可以对于 Object.prototype的委托来访问 hasOwnProperty(..),但是有的对象可能没有连接到 Obeject.prototype(Object.create(null)),这种可以使用 Object.prototype.hasOwnProperty.call(myObject, 'a')
in操作符是检查容器内是否有某个值,实际上检查的是某个属性名是否存在,对于数组来说这个区别很重要, 4 in [2, 4 ,6]的结果并不是你所期待的true, 因为[2, 4, 6]这个数组中包含的属性名为 0, 1, 2, 没有4
for in 遍历对象是无法获取属性值的,它实际遍历的是对象中所有可枚举属性,需要手动获取属性值;
且遍历对象属性时的顺序是不确定的;
for of 循环首先会向被访问对象请求一个迭代器对象,然后通过调用迭代器对象的next()方法来遍历所有值;
遍历数组
var myObject = {
a: 2,
b: 3,
};
Object.defineProperty(myObject, Symbol.iterator, {
enumerable: false,
writable: false,
configurable: true,
value: function () {
var o = this;
var idx = 0;
var ks = Object.keys(o);
return {
next: function() {
return {
value: o[ks[idx++]],
done: (idx > ks.length),
}
}
}
}
})
var it = myObject[Symbol.iterator]();
console.log(it.next());
console.log(it.next());
console.log(it.next());
/*
{ value: 2, done: false }
{ value: 3, done: false }
{ value: undefined, done: true }
*/
var arr = [[1, 2, ,3], 4, -1, [11, 22, 33, 45, [7, 123, 1]], 98, [[123]]];
function maxOfArr() {
var targetArr = [];
// forEach为es5的数组迭代方法
// 展开多维数组为一维数组的函数,递归调用
var fnExpandArray = function (arr) {
arr.forEach(function(item) {
if (Array.isArray(item)) {
fnExpandArray(item);
} else {
targetArr.push(item);
}
});
}
fnExpandArray(arr);
// console.log(targetArr);
// 求出最大值得函数,假设第一个值为最大值,并且与后面的值进行比较
var fnMax = function (arr) {
var max = arr[0];
arr.forEach( function (item) {
if(item > max) {
max = item;
}
})
return max;
}
console.log('最大值是' + fnMax(targetArr));
}
maxOfArr();
function maxAndMin(arr) {
return {
max: Math.max.apply(null, arr.join(',').split(',')),
min: Math.min.apply(null, arr.join(',').split(',')),
}
}
console.log(maxAndMin([[1, 2, ,3], 4, -1, [11, 22, 33, 45, [7, 123, 1]], 98, [[123]]]));
使用css自定义属性实现一键换肤功能
css 的自定义属性功能(var), 在css中使用变量,和less及scss不同的是,在js中可以直接修改变量值;
它们使用自定义属性符号(例如--main-color: black;
)进行设置,并使用var()函数。
例如: color: var(--main-color)
;
:root {
--main-color: brown;
}
.one {
color: white;
background-color: var(--main-color);
margin: 10px;
width: 50px;
height: 50px;
display: inline-block;
}
可以在javascript中设置
// get variable from inline style
element.style.getPropertyValue("--my-var");
// get variable from wherever
getComputedStyle(element).getPropertyValue("--my-var");
// set variable on inline style
element.style.setProperty("--my-var", 'red');
补充polyfill 的简单解释
polyfill用于表示根据新特性的定义,创建一段与之行为等价但能够在旧的javascript环境中运行的代码
引用 wiki 里的一句话解释
In web development, a polyfill is code that implements a feature on web browsers that do not support the feature. Most often, it refers to a JavaScript library that implements an HTML5web standard, either an established standard (supported by some browsers) on older browsers, or a proposed standard (not supported by any browsers) on existing browsers. Formally, "a polyfill is a shim for a browser API".
可以这样简单的理解一下:
css自定义变量在IE浏览器中无法使用,但是使用了css-vars-polyfill ,就可以让我们在IE9+中也可以使用该属性;
还记得 babel-polyfill 么, babel允许我们使用es6的语法,它主要是把es6的语法转成大多数浏览器可是支持的es5语法, 可以使用 工具 来看一下平常所写的es6语法对应到es5是什么样的, 例如
但是像 Array.from , Object.assign, Array.prototype.includes, promise, async ,await 这些都是es5语法中没有的,我们还是想使用,但是很多浏览器不支持怎么办, babel-polyfill就是帮我们解决这个问题的
加快资源加载速度
没有开启压缩的时候, 资源加载时间为: 获取资源时间 + 资源下载时间
开启压缩的时候, 资源加载时间为: 压缩资源时间 + 获取压缩后资源时间 + 下载压缩后的资源时间 + 解压缩资源时间
咋一看开启压缩后的时候比没开启压缩时间要多一个压缩和解压缩的时间, 举个例子, 获取一个js文件比如没开始压缩的时候, 所用时间为 5ms + 5ms = 10ms; 开启压缩的时间为 1ms + 2ms+ 2ms + 1ms = 6ms, 这样算下来, 开启压缩就会加快资源加载速度.
但是并不是什么资源开启压缩之后都会加快资源加载速度, 像图片资源这种就不需要开启压缩, 因为图片这种不会压缩很多, 因为它们已经内置了压缩
# 开启gzip
gzip on;
# 启用gzip压缩的最小文件,小于设置值的文件将不会压缩
gzip_min_length 1k;
# gzip 压缩级别,1-10,数字越大压缩的越好,也越占用CPU时间,后面会有详细说明
gzip_comp_level 2;
# 进行压缩的文件类型。javascript有多种形式。其中的值可以在 mime.types 文件中找到。
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript;
在response header中看有没有Content-Encoding: gzip
在Chrome和FireFox上,onload会在脚本加载完之后触发,在回调函数中脚本的内容已经加载完全。而这两个浏览器的script标签是不支持onreadystatechange事件的。
在IE上比较有趣, 在onreadystatechange事件中还要判断script.readyState属性
判断引入js文件是否加载完毕后执行之后的代码(参考jquey的源码):
var script = document.createElement('script');
script.src="xx.js";
script.onload = script.onreadystatechange = function(){
if( ! this.readyState //这是FF的判断语句,因为ff下没有readyState这人值,IE的readyState肯定有值
|| this.readyState=='loaded' || this.readyState=='complete' // 这是IE的判断语句
){
console.log('loaded');
}
};
1.IE8和IE8以下浏览器中,script标签并不支持onload事件,但是支持onreadystatechange事件。
2.IE8以上浏览器、谷歌浏览器和火狐浏览器支持onload事件。
3.readyState是onreadystatechange事件的一个状态,当值为loaded或者complete的时候,都表示已经加载完毕。
4.if(!this.readyState||this.readyState=='loaded'||this.readyState=='complete'),!this.readyState表示不是不是IE11以下浏览器(IE11以下浏览器也是支持onreadystatechange事件的)
使用语义化的标签
table布局的网页特点
为了保证网页去样式后的可读性,而且又符合Web标准,我们应注意一下几点:
如何组织CSS
base.css + common.css + page.css
base层:会被所有页面引用,是页面样式所依赖的最底层.这一层与具体的CSS reset功能和粒度最小的通用类-原子类.不同设计风格的网站可以使用同一个base层.
base层相对稳定,基本上部需要维护.
common层: 提供组件级的CSS类;common层是网站级的,不同的网站有不同的common层,同一个网站只有一个common层.common层是放在一个common.css里,还是按照功能划分放在诸如common_form.css,common_imagelist.css的多个文件里,需要根据网站规模来决定.
在团队合作中,common层最好由一个人负责,统一管理.
page层: 网站中高度重用的模块,我们把他们视为组件,放在common层;非高度重用的模块,可以把他们放在page层.page层是页面级的,每个页面都可能会有自己的page层CSS
position: absolute会完全脱离文档流,不在z-index:0层保留占位符,其left, top, right, bottom值是相对于自己最近的一个设置了position: relative或position: absolute的祖先元素的,如果祖先元素全部都没有设置position: relative或position: absolute,那么就相对于bosy元素;
float: 也能改变文档流,不同的是,float属性不会让元素'上浮'到另一个z-index层,它仍然让元素在z-index:0层排列,float不像position: relative和position:absolute那样,它不能通过left, top,right, bottom属性精确地控制元素的坐标,它只能通过float: left和float: right来控制元素在同层里'右浮'和'左浮'.float会改变正常的文档流排列,影响到周围元素
避免全局变量.使用命名空间
CSS放在页头,JavaScript放在页尾.在载入HTML元素之前,先载入他们的样式,这样可以避免HTML出现无样式状态;将JavaScript放在页尾,先将网页呈现给用户,再来加载页面内的脚本,避免JavaScript阻塞网页的呈现,减少页面空白的时间
base, common, page
base分为以下三块
common层
common层本身依赖base层提供的接口,所以要使用common层的组件,必须先加载base层的代码
common层和page层都是供page层调用的,二者而区别在于common层提供的不是简单的接口,而是相对更庞大的组件,例如cookie,原生JavaScript对设置和读取cookie的方法显的非常笨拙;
除了Cookie,common层常用的组件还有用于异步通信的Ajax, 用于拖曳的Drag,用于拖拉元素大小的Resize,用于动画的Animation, 用于标签切换的Tab,用于树型目录的Tree,用于模拟弹出窗口的Msg,用于拾色器的ColorPicker, 用于日历的Calendar, 用于富文本编辑器的RicnTextEditor等.
一个易用性,重用性和可扩展性都是非常好的组件,代码量往往偏高,所以common层的JS需要按功能分成一个个单独的文件,例如common_cookie.js, common_drag.js, common_tab.js等,按需加载
page层
base层和common层都属于框架级,page层是属于应用层的,它可以调用base层的接口和common层的组件
获取DOM节点的几种方法
将复杂类型的数据转换成字符串,称为数据的序列化,其逆操作叫做数据的反序列化;
任务队列: 挂在事件循环队列的每个tick之后的一个队列,在事件樽还的每个tick中,可能出现的异步动作不会导致一个完整的新事件添加到事件循环对垒中,而会在当前tick的任务队列末尾添加一个项目(一个任务)
JavaScript程序至少分成两个块: 第一块现在运行,下一块将来运行,已响应某个事件;
一旦有事情需要执行,事件循环就会运行,直到队列清空.事件循环的每一轮称为一个tick.用户交互, IO和定时器会向事件队列中加入事件;
并发是指两个或多个事件链随时间发展交替执行;
存在一下问题
var p3 = new Promise(function(resolve, reject) {
resolve('b');
});
var p1 = new Promise(function(resolve, reject) {
resolve(p3);
});
var p2 = new Promise(function(resolve, reject) {
resolve('a');
});
p1.then(function(v) {
console.log(v);
});
p2.then(function(v) {
console.log(v);
})
a,
b
p1不是立即值而是用另一个promise p3决议,后者本身决议为值'b'
Promise.resolve()
Promise.reject()
then()和catch()
Promise.all([..]): 只有传入的所有promise都完成,返回promise才能完成,如果有任何promise拒绝, 就立刻会被拒绝(抛弃任何其他promise的结果);如果完成的话,会得到一个数组,包含传入所有promise的完成值;对于拒绝的情况,你只会得到第一个拒绝promise的拒绝理由值;
Promise.race([..]): 只有第一个决议promise(完成或拒绝)取胜,并且其决议结果成为返回promise的决议;
若向Promise.all([..])传入空数组,他会立即完成,但是Promise.race([..])会挂住,且永远不会决议;
1.顺序错误处理
2.单一值
es6引入了一种新的函数类型,它并不符合这种运行到结束的特性,这类新的函数被称为生成器
function *foo(x) {
var y = x * (yield);
return y;
}
var it = foo(6);
it.next();
var res = it.next(7);
console.log(res);
//{ value: 42, done: true }
创建了一个迭代器对象,并把它赋给一个变量it,用于控制生成器 *foo();然后调用next(),指示生成器 *foo()从当前位置继续运行,停在下一个yield处或者直到生成器结束;
一般来说,需要的next()调用要比yield语句多一个,前面的代码片段有一个yield和两个next()调用;
因为第一个next(..)总是启动一个生成器,并运行到第一个yield处;不过,是第二个next(..)调用完成第一个被暂停的yield表达式,第三个next(..)调用完成第二个yield,一次类推;
只有暂停yield才能接受这样一个通过next(..)传递的值,二生成器的起始处我们调用第一个next()时,还没有暂停的yield来接受这样的一个值;
启动生成器时一定要用不带参数的next();
function *foo() {
var x = yield 2;
z++;
var y = yield(x*z);
console.log(x, y, z);
}
var z = 1;
var it1 = foo();
var it2 = foo();
var val1 = it1.next().value; // 2 <--yield 2
var val2 = it2.next().value; // 2 <--yield 2
val1 = it1.next(val2 * 10) .value; // 40 <--x:20,z:2
val2 = it2.next(val1 * 5) .value; // 600 <--x:200,z:3
it1.next(val2 /2); // y:300 20 300 3
it2.next(val1/4); // y:10 200 10 3
生成器的一种有趣用法是作为一种产生值的方式;
假定要产生一系列值,其中每个值都与前面一个有特定的关系.要实现这一点,需要有一个有状态的生产者能够记住其生成的最后一个值;
生成任意数字序列?
可以使用迭代器,迭代器用于从一个生产者一步步得到一系列值;就是没次想要从生产者得到下一个值得时候 调用next();
var something = (function() {
var nextVal;
return {
[Symbol.iterator]: function() {
return this;
},
next: function() {
if(nextVal === undefined) {
nextVal = 1;
} else {
nextVal = (3 * nextVal) + 6;
}
return {
done: false,
value: nextVal
}
}
}
})();
something.next().value; // 1
something.next().value; // 9
something.next().value; // 33
something.next().value; // 105
[..]被称为计算属性名:指定一个表达式并用这个表达式的结果作为属性的名称;
Symbol.iterator是ES6预定义的特殊Symbol值之一;
next()调用返回一个对象,这个对象有两个属性,done是一个boolean值,标识迭代器的完成状态,value中放置迭代器
除了构造自己的迭代器,许多JavaScript的内建数据结构,比如Array,也有默认的迭代器
如果想迭代对象所有属性(不需要保证特定的顺序),可以使用Object.keys(..),返回一个Array,类似于
for (var k of Object.keys(obj)){}
这样使用,这样在一个对象的键值上使用 for ... of循环于for ..in 循环类似;除了Object.keys()并不包含来自于[[Prototype]]链上的属性,而 for ..in则包含
前面例子中的something对象叫做迭代器,因为它的接口中有一个next()方法;而于其紧密相关的术语iterable(可迭代),即指一个包含可以在其指上迭代的迭代器对象;
ES6开始,从一个iterable中提取迭代器的方法是: iterable必须支持一个函数,其名称是专门的ES6符号值Symbol.iterable.调用这个函数时,它会返回一个迭代器,通常每次调用都会返回一个全新的迭代器;
for ..of循环自动调用它的 Symbol.iterator函数来构建一个迭代器
可以把生成器看做一个值的生产者,我们通过迭代器接口的next()调用一次提取出一个值
当你执行一个生成器,就得到了一个迭代器
for ..of 循环'异常结束'(也就是'提前终止'),通常由break,return 或者未捕获异常引起,会向生成器的迭代器发送一个信号使其终止;
异步迭代生成器 ?
同步错误处理
Array.prototype.slice(), slice方法返回一个从开始到结束(不包括结束)选择的一部分浅拷贝到另一个新数组的对象,且原始数组不会被修改
[].slice.call()或者Array.prototype.slice.call(), 就是截取(更重要的是获取,slice是得到的子数组的)函数的参数, 然后让arguments等'伪数组'也可以使用数组的各种方法;
ES6的Array.from()也是类似的用法;不单单可以转换arguments,元素集合,还可以转化类数组对象;
Array.prototype.slice.call()方法详解
function run(gen) {
var args = [].slice.call(arguments, 1), it;
// 在当前上下文初始化生成器
it = gen.apply(this, args);
// 返回一个promise用于生成器完成
return Promise.resolve()
.then(function handleNext(value) {
// 对下一个yield出的值运行
var next = it.next(value);
return (function handleResult(next) {
// 生成器运行完毕了吗?
if(next.done) {
return next.value;
}
// 否则继续运行
else {
return Promise.resolve(next.value)
.then(
// 成功就恢复异步循环,把决议的值发回生成器
handleNext,
// 如果value是被拒绝的promise
// 就把错误传回生成器进行出错处理
function handleError(err) {
return Promise.resolve(
it.throw(err);
)
.then(handleResult);
}
)
}
})(next)
})
}
function *main() {
//
}
run(main);
运行run(..)的方式,它会自动异步运行你传给它的生成器,知道结束
我们定义的run(..)返回一个promise,一旦生成器完成,这个promise就会决议,或者收到一个生成器没有处理的未捕获异常
前面的模式--生成器yield出Promise,然后其控制生成器的迭代器来执行它,直到结束;如果我们无需库工具的辅助函数run()就能实现就好了
async/await
它现在是一类新的函数: async函数,我们不再yield出Promise,而是用await等待它决议;
如果你await了一个promise,async函数就会自动获知要做什么,它会暂停这个函数(就像生成器一样),直到Promise决议
function *foo() {
console.log('*foo() starting');
yield 3;
yield 4;
console.log('*foo finished');
}
function *bar() {
yield 1;
yield 2;
yield *foo();
yield 5;
}
var it = bar();
console.log(it.next().value);
console.log(it.next().value);
console.log(it.next().value);
console.log(it.next().value);
console.log(it.next().value);
/*
1
2
*foo() starting
3
4
*foo finished
5
*/
yield委托的主要目的是代码组织,以达到与普通函数调用的对称;
function *foo() {
console.log('inside *foo():', yield 'B');
console.log('inside *foo():', yield 'C');
return 'D';
}
function *bar() {
console.log('inside *bar():', yield 'A');
console.log('inside *bar():', yield *foo());
console.log('inside *bar():', yield 'E');
return 'F';
}
var it = bar();
console.log('outside:', it.next().value);
// outside: A
console.log('outside:', it.next(1).value);
// inside *bar(): 1
// outside: B
console.log('outside:', it.next(2).value);
//inside *foo(): 2
// outside: C
console.log('outside:', it.next(3).value);
// inside *foo(): 3
// inside *bar(): D
// outside: E
console.log('outside:', it.next(4).value);
// inside *bar(): 4
// outside: F
function *bar() {
console.log('inside *bar():', yield 'A');
console.log('inside *bar():', yield *['B', 'C', 'D']);
console.log('inside *bar():', yield 'E');
return 'F';
}
var it = bar();
console.log('outside:', it.next().value);
// outside: A
console.log('outside:', it.next(1).value);
// inside *bar(): 1
// outside: B
console.log('outside:', it.next(2).value);
// outside: C
console.log('outside:', it.next(3).value);
// outside: D
console.log('outside:', it.next(4).value);
// inside *bar(): undefined
// outside: E
console.log('outside:', it.next(5).value);
// inside *bar(): 5
// outside: F
默认的数组迭代器并不关心通过next(..)调用发送的任何消息,所以值 2, 3和4根本就被忽略了,还有,因为迭代器没有显式的返回值(和前面使用的*foo()不同),所以yield *表达式完成后得到的是一个undefined
异常也可以被委托
function *foo() {
try {
yield 'B';
}
catch(err) {
console.log('error caught inside *foo():', err);
}
yield 'C';
throw 'D';
}
function *bar() {
yield 'A';
try {
yield *foo();
}
catch(err) {
console.log('error caught inside *bar():', err);
}
yield 'E';
yield *baz();
yield 'G';
}
function *baz() {
throw 'F';
}
var it = bar();
console.log('outside:', it.next().value);
// outside: A
console.log('outside:', it.next(1).value);
// outside: B
console.log('outside:', it.throw(2).value);
// error caught inside *foo(): 2
// outside: C
console.log('outside:', it.next(3).value);
// error caught inside *bar(): D
// outside: E
try {
console.log('outside:', it.next(4).value);
}
catch(err) {
console.log('error catch outside:', err);
}
// error catch outside: F
function *foo(val) {
if(val > 1) {
// 生成器递归
val = yield *foo(val -1);
}
return yield request('http://some.url/?v=' + val)
}
function *bar() {
var r1 = yield *foo(3);
console.log(r1);
}
run(bar);
通信顺序进程 ?
runAll(
function *(data) {
data.res = [];
var url1 = yield 'https://some.url.2';
var p1 = request(url1);
yield;
data.res.push(yield p1);
},
function *(data) {
var url2 = yield 'https://some.url.2';
var p2 = request(url2);
yield;
data.res.push(yield p2);
},
)
形实转换程序(thunk): 一个用于调用另外一个函数的函数,没有任何参数;
用一个函数定义封装函数调用,包括需要的任何参数,来定义这个调用的执行,那么这个封装函数就是一个形实转换程序;
function foo(x, y) {
return x + y;
}
function fooThunk(){
return foo(3, 4);
}
console.log(fooThunk()); // 7
异步的thunk
function foo(x, y, cb) {
setTimeout(function() {
cb(x+y);
}, 1000);
}
function fooThunk(cb) {
foo(3, 4, cb);
}
fooThunk(function(sum) {
console.log(sum);
})
写一个工具来封装
function foo(x, y, cb) {
setTimeout(function() {
cb(x+y);
}, 1000);
}
function thunkify(fn) {
var args = [].slice.call(arguments, 1);
return function(cb) {
args.push(cb);
return fn.apply(null, args);
}
}
var fooThunk = thunkify(foo, 3, 4);
fooThunk(function(sum) {
console.log(sum); // 7
})
前面thunkify(..)的实现接收foo(..)函数引用以及它需要的任意参数,并返回thunk本身(fooThunk(..));
典型的方法
function thunkify(fn) {
return function() {
var args = [].slice.call(arguments);
return function(cb) {
args.push(cb);
return fn.apply(null, args);
}
}
}
var whatIsThis = thunkify(foo);
var fooThunk = whatIsThis(3, 4);
fooThunk(function(sum) {
console.log(sum);
})
thunkify(..)工具产生一个生成thunk的函数
thunkify(..)生成一个thunkory,然后thunkory生成thunk;
var fooThunkory = thunkify(foo);
var fooThunk1 = fooThunkory(3, 4);
var fooThunk2 = fooThunkory(5, 6);
// 而不是
var fooThunk1 = thunkify(foo, 3, 4);
var fooThunk2 = thunkify(foo, 5, 6);
尾调用(TCO)就是一个出现在另一个函数'结尾'处的函数调用;这个调用结束后就没有其余的事情要做了(除了可能要返回结果值)
function foo(x) {
return x;
}
function bar(y) {
return foo(y + 1); // 尾调用
}
function baz() {
return 1 + bar(40); // 非尾调用
}
baz(); // 42
调用一个新函数需要额外的一块预留内存来管理调用栈,称为栈帧
支持TCO的引擎能够意识到foo(y+1)调用位于尾部,这就意味着bar(..)基本上已经完成了,那么在调用foo(..)时,它就不需要创建一个新的栈帧,而是可以重用已有的bar(..)的栈帧。这样不仅速度快,也更节省内存;
有了TCO,尾调用的递归函数本质上就可以任意运行,因为再也不需要额外的内存
ECMAScript, DOM, BOM
<script>
标签, 6个属性如果放在<head>
元素中,意味着必须等到全部JavaScript都被下载,解析和执行完成以后才呈现页面的内容(浏览器遇到<body
标签时才开始呈现内容).这无疑会导致浏览器在呈现页面时出现明显的延迟,而延迟期间的浏览器窗口中将是一片空白;
为了避免这个问题,一般把全部JavaScript引用放在<body>
中,放在页面的内容后面;这样,在解析包含JavaScript代码之前,页面的内容将完全呈现浏览器中.而用户也会因为浏览器显示空白页面的时间缩短而感到打开页面速度加快了;
defer属性,脚本会被延迟到整个页面都解析完毕后再运行;
相当于告诉浏览器立即下载,但是立即执行
async 只适用于外部脚本文件,并告诉浏览器立即下载文件;但是并不保证按照它们的先后顺序执行;
�标识符: 变量,函数,属性的名字和函数的参数
逗号操作符:用于声明多个变量
基本类型和引用类型的值
引用对象的值是保存在内存中的对象,JavaScript不允许直接访问内存中的位置,也就是说不能直接操作对象的内存空间.在操作对象时,实际上是在操作对象的引用而不是实际的对象;
JavaScript内存分为栈,堆,池, 栈存放变量,堆存放复杂对象,池存放常量
JS中的基础数据类型,这些值都有固定的大小,往往都保存在栈内存中(闭包除外),由系统自动分配存储空间。我们可以直接操作保存在栈内存空间的值,因此基础数据类型都是按值访问 数据在栈内存中的存储与使用方式类似于数据结构中的堆栈数据结构,遵循后进先出的原则
JavaScript中所有函数的参数都是按值传递的;
在向参数传递基本类型的值时,被传递的值会被复制给一个局部变量(即命名参数);
function setName(obj) {
obj.name = 'Nicholas';
obj = new Object();
obj.name = 'Greg';
}
var person = new Object();
setName(person);
console.log(person.name); // Nicholas
实际上,当在函数内部重写obj时,这个变量引用的就是一个局部对象了,而这个局部对象会在函数执行完毕后立即被销毁;
JavaScript中,所需内存的分配以及无用内存的回收完全实现了自动管理;
垃圾收集机制原理: 找出那些不再继续使用的变量,然后自动释放其占有的内存.垃圾收集器按照固定的时间间隔(或代码执行中预定的收集时间),周期性地执行这一操作;
函数中局部变量的正常生命周期: 局部变量只在函数执行过程中存在;在这个过程中,会为局部变量在堆(或栈)内存上分配相应的空间,以便存储它们的值.然后再函数中使用这些变量,直至函数执行结束.此时,局部变量就没有存在的必要了,因此可以释放它们的内存以供将来使用;
1.标记清除
JavaScript中最常用的垃圾收集方式是标记清除.
垃圾收集器在运行的时候会给储存在内存中的所有变量都加上标记(当然,可以使用任何标记方式).然后,它会去掉环境中的变量以及被环境中的变量引用的变量的标记.而在此之后再加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了.最后垃圾收集器完成内存清除工作,销毁哪些带标记的值并回收他们所占用的内存空间;
2.引用计数
另一种不太常见的垃圾收集策略叫做引用计数.引用计数的含义是跟踪记录每个值被引用的次数;
当声明了一个变量并将一个引用类型的值赋给该变量时,则这个值的引用次数就是1;如果同一个值又被赋给另一个变量,则该值得引用次数加1;相反,如果包含对这个值引用的变量又取得了另一个值,则这个值的引用次数减1;当这个值的引用次数变成0时,则说明没有办法再访问这个值了,因而就可以将其占有的内存空间回收回来;
但是遇到一个严重的问题: 循环引用; 循环引用指的是对象A中包含一个指向对象B的指针,而对象B中也包含了一个指向对象A的引用;
funtion problem() {
var objectA = new Object();
var objectB = new Object();
objectA.someOtherObject = objectB;
objectB.anotherObject = objectA;
}
objectA和ObjectB通过各自的属性相互引用;也就是说,这两个对象的引用次数都是2;在采用标记清除策略的实现中,由于函数执行之后,这两个对象都离开了作用域,因此这种问题相互引用不是个问题;但是在采用引用计数策略的实现中,当函数执行完毕后,objectA和objectB还将继续存在,因为它们的引用计数永远不会是0;加入这个函数被重复多次调用,就会导致大量内存得不到回收;
class Main extends React.Component {
constructor(props) {
super(props);
this.state = {
productList: [],
params: '',
}
}
}
两种写法的功能实现的功能一样但是原理却不同,es6的class类可以看作是构造函数的语法糖,可以把他当做构造函数来看,extends实现了类之间的继承 -- 定义一个类Main继承React.Component所有的属性和方法,组件的生命周期函数就是从这儿继承来的.constructor是构造器,在实例化对象时调用,super调用父类的constructor创造了父类的实例对象this,然后用子类的构造函数进行修改
当我们使用组件< Main />时,其实是对Main类的实例化——new Main,只不过react对这个过程进行了封装,让它看起来更像是一个标签。
有三点值得注意:1、定义类名字的首字母必须大写 2、因为class变成了关键字,类选择器需要用className来代替。 3、类和模块内部默认使用严格模式,所以不需要用use strict指定运行模式。
组件在初始化时会触发5个钩子函数
1,getDefaultProps() 设置默认的props,也可以用defaultProps设置组件的默认属性
2,getInitialState() 使用es6的class语法时是没有这个钩子函数的,可以直接在constructor中定义this.state。此时可以直接访问this,props。
3,componentWillMount() 组件初始化时调用,以后组件更新不调用,整个生命周期只调用一次,此时可以修改state。
4,render() react中最重要的步骤,创建虚拟dom,进行diff算法,更新dom树都在此进行,此时就不能更改state了。
5,componentDidMount() 组件渲染之后调用,可以通过this.getDOMNode()获取和操作dom节点,只调用一次
更新时也会触发5个钩子函数
6,componentWillReceiveProps(nextProps) 组件初始化时不调用,组件接受新的props时调用。
7,shouldComponentUpdate(nextProps,nextState) react性能优化非常重要的一环。组件接受新的state或者props时调用,我们可以设置在此对比前后两个props和state是否相同,如果相同返回false组织更新。不过调用this.forceUpdate会跳过此步骤
8,componentWillUpdate(nextProps, nextState) 组件初始化时不调用,只在组件将要更新时才调用,此时可以修改state
9,render()
10,componentDidUpdate() 组件初始化时不调用,组件更新完成后调用,此时可以获取dom节点
还有一个卸载钩子函数
11,componentWillUnMount() 组件将要卸载时调用,一些事件监听和定时器需要在此时清除
以上可以看出来react共有10个周期函数(render重复一次),这10个函数可以满足我们所有对组件操作的需求,利用的好可以提高开发效率和组件性能
根据你支持的环境自动决定适合你的babel插件;
useBuiltIns是配合polyfill使用的, 此选项配置如何@babel/preset-env处理polyfill。
false: 默认为false, 意思是babel-polyfill全量引入
entry:
是按照目标环境(即oreset-env的targets属性)去polyfill的,不关心代码中是否使用,可以保证在目标环境一定可用
usage(实验性):
会分析代码调用,但是对于原型链上的方法仅仅按照名字去匹配,可以得到更小的polyfill体积;
因为很难去分析代码中是否调用了,例如以下情况就分析不了,会导致出错
无法分析一个对象是什么类型的
data.split(); 它会认为你用的是String方法的split,但是有可能data是个数组, split是我自己定义的方法,此时polyfill就不准确了
无法分析第三方包是否用到了
include中配置了一个第三方包, 第三包中用到了ES6的 Promise,但是你自己的代码中没有用到Promise, 此时就会判断你的代码中不需要对Promise对象polyfill,然后运行的时候代码就报错了
综上所述,最优的方案是配置 useBuiltIns为entry, 然后配置targets.browsers, 根据环境去进行polyfill
编译的时候进行polyfill, 运行的时候判断是否需要用到polyfill
Babel默认只转换新的JavaScript句法(比如箭头函数), 而不转换新的API, 比如Iterator, Generator, Set, Maps, Proxy, Reflect, Symbol, Promise等全局对象, 以及定义在全局对象上的方法, 比如(Object.assign)都不会转码;
可以使用 babel-polyfill 支持全新的全局变量, 例如Promise, 新的原生方法如String.padStart(), 它使用了core-js和regenerator';
安装
npm install --save babel-polyfill
使用它时需要在你应用程序的入口订单或打包配置中引入;
import 'babel-polyfill';
// 或者
require('babel-polyfill');
外部引用辅助函数和内置函数,自动填充代码而不会污染全局变量;
思考:babel-runtime 为什么适合 JavaScript 库和工具包的实现?
{
"presets": [],
"plugins": []
}
presets: 规定转码规则
# ES2015转码规则
$ npm install --save-dev babel-preset-es2015
# react转码规则
$ npm install --save-dev babel-preset-react
# ES7不同阶段语法提案的转码规则(共有4个阶段),选装一个
$ npm install --save-dev babel-preset-stage-0
$ npm install --save-dev babel-preset-stage-1
$ npm install --save-dev babel-preset-stage-2
$ npm install --save-dev babel-preset-stage-3
大家都知道会很快, 但是具体为什么会快一些呢?
原因是把静态资源放到CDN服务器, CDN服务器会向全国各地(国际的CDN会向世界各地)的服务区分发资源, 当用户访问完整时, 会去请求离用户最近的服务器.这样时间会缩短很多.
但是有一点需要注意, html是不能放CDN的, 因为html会有缓存,而且缓存的时候会很长, 这意味着如果你更新页面, 可能会很久很久线上才会更新最新的代码.
但是你可能会问, 为什么静态资源文件, 比如css, js等可以放CDN. 难道它们不存在缓存么, 答案是它们也会存在缓存, 但是我们每次更新css和js文件的时候,文件路径都会带有不同的hash值, 有的是时间戳, 通过修改路径的不同强制更新资源.但是html文件不同, 你访问html文件, 它的路径一般是固定的, 比如网站的首页路径为, http://xxx.com/index.html, 这个路径是不会修改的, 所以html的会有缓存, 除非你在路径上加一个参数或者时间戳, 但是你总不能让用户每次访问网站的时候都在路径地址上加一个时间戳吧.
所以最好的办法是, 静态资源(css, js)放CDN, html文件放在服务器上
data () {
return {
scrolled: false
};
},
methods: {
handleScroll () {
this.scrolled = window.scrollY > 0;
}
},
created () {
window.addEventListener('scroll', this.handleScroll);
},
destroyed () {
window.removeEventListener('scroll', this.handleScroll);
}
var React = require("react");
var Greeting = React.createClass({
propTypes: {
name: React.PropTypes.string //属性校验
},
getDefaultProps: function() {
return {
name: 'Mary' //默认属性值
};
},
getInitialState: function() {
return {count: this.props.initialCount}; //初始化state
},
handleClick: function() {
//用户点击事件的处理函数
},
render: function() {
return <h1>Hello, {this.props.name}</h1>;
}
});
module.exports = Greeting;
在createClass中,React对属性中的所有函数都进行了this绑定,也就是上面的handleClick其实相当于handleClick.bind(this);
import React from 'react';
class Greeting extends React.Component {
constructor(props) {
super(props); //调用父类的构造函数
this.state = {count: props.initialCount};
this.handleClick = this.handleClick.bind(this);
}
//static defaultProps = {
// name: 'Mary' //定义defaultprops的另一种方式
//}
//static propTypes = {
//name: React.PropTypes.string
//}
handleClick() {
//点击事件的处理函数
}
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
Greeting.propTypes = {
name: React.PropTypes.string
};
Greeting.defaultProps = {
name: 'Mary'
};
export default Greating;
我们知道,当组件的props或者state发生变化时,React就会对当前的props和state分别与nextProps和nextState进行比较,当发生变化时,就会对当前组件或子组件进行重新渲染,否则就不渲染,有时候为了避免租金进行不必要的重新渲染,我们通过shouldComponentUpdate来优化性能
class CounterButton extends React.Component {
constructor(props) {
super(props);
this.state = {count: 1};
}
shouldComponentUpdate(nextProps, nextState) {
if (this.props.color !== nextProps.color) {
return true;
}
if (this.state.count !== nextState.count) {
return true;
}
return false;
}
render() {
return (
<button
color={this.props.color}
onClick={() => this.setState(state => ({count: state.count + 1}))}>
Count: {this.state.count}
</button>
);
}
}
shouldComponentUpdate通过判断props.color和state.count是否发生变化来重新渲染组件,React提供了PureComponent来自动帮我们做这件事,
class CounterButton extends React.PureComponent {
constructor(props) {
super(props);
this.state = {count: 1};
}
render() {
return (
<button
color={this.props.color}
onClick={() => this.setState(state => ({count: state.count + 1}))}>
Count: {this.state.count}
</button>
);
}
}
上面提到的是用来创建包含状态和用户交互的复杂组件,当组件本身只是用来展示,所有数据都是通过props传入的时候,我们便可以使用Stateless Functional Component来快速创建组件
import React from 'react';
const Button = ({
day,
increment
}) => {
return (
<div>
<button onClick={increment}>Today is {day}</button>
</div>
)
}
Button.propTypes = {
day: PropTypes.string.isRequired,
increment: PropTypes.func.isRequired,
}
createClass, 除非你确实对ES6的语法一窍不通,不然的话就不要再使用这种方式定义组件。
Stateless Functional Component, 对于不需要内部状态,且用不到生命周期函数的组件,我们可以使用这种方式定义组件,比如展示性的列表组件,可以将列表项定义为Stateless Functional Component。
PureComponent/Component,对于拥有内部state,使用生命周期的函数的组件,我们可以使用二者之一,但是大部分情况下,我更推荐使用PureComponent,因为它提供了更好的性能,同时强制你使用不可变的对象,保持良好的编程习惯。
类, 继承,实例化
举例:
'汽车'可以被看作'交通工具'的一种特例,后者是更广泛的类
Vehicle类, Car类
Vehicle可能包含: 推进器(如引擎),载人能力
定义Car类,只要声明它继承(或者扩展)了Vehicle的基础; Car定义就是对通用Vehicle定义的特殊化
隋坦Vehicle和Car会定义相同的方法,但是实例中的数据可能是不同的(如, 梅辆车都有独一无二的车牌号);
类实例是由一个特殊的类方法构造的,这个方法名通常和类名相同,被称为构造函数.这个方法的任务就是初始化实例需要的所有信息
class Vehicle {
}
先定义一个类,然后定义一个继承前者的类; 后者为'子类',前者这称为父类;
比喻: 父类和子类称为父类DNA和子类DNA,我们需要根据这些DNA来创建(或者说实例化)一个人, 然后才能和他进行沟通
在传统的面向类的语言中
super: '超类'(superclass), 表示当前类的父类/祖先类; 还有一个功能: 就是从子类的构造函数中通过super可以直接调用父类的构造函数;
因为对于真正的类来说,构造函数时属于类的,但是js中'类'是属于构造函数的(类似Foo.prototype..这样的类型引用), 由于js中父类和子类只存在于两者构造函数对象的.prototype对象中.
用来模拟类的复制行为
function mixin(sourceObj, targetObj) {
for(var key in sourceObj) {
if(!(key in targetObj)) {
targetObj[key] = sourceObj[key];
}
}
return targetObj;
}
var Vehicle = {
engines: 1,
ignition: function() {
console.log('Turning on my engine');
},
drive: function() {
this.ignition();
console.log('Steering and moving forward!');
}
}
var Car = mixin(Vehicle, {
wheels: 4,
drive: function () {
Vehicle.drive.call(this);
console.log('Rolling on all' + this.wheels + 'wheels');
}
})
js对象有一个特殊的[[Prototype]]内置属性, 其实就是对于其他对象的引用,几乎所有对象在创建时[[prototyp]]属性都会被赋予一个非空的值
var myObject = {
a: 2,
}
console.log(myObject.a);
对于默认的[[Get]]操作来说,如果无法再对象本身找到需要的属性,就会继续访问对象的[[prototype]]链
var anotherObject = {
a: 2,
}
var myObject = Object.create(anotherObject);
console.log(myObject.a);
Object.create(..), 会创建一个对象并把这个对象的[[prototype]]关联到指定的对象
myObject对象的[[Prototype]]关联到了anotherObject, 现在myObject.a并不存在,但是尽管如此,属性访问仍然成功地找到了值2
但是,如果anotherObject中也找不到a并且[[Prototype]]链不为空的话,就会继续查找下去
这个过程会持续到找到匹配的属性名或者查找完整条[[[Prototype]]]链,如果是后者的话, [[Get]] 操作的返回值是undefined
for...in 遍历对象的原理和查找[[Prototype]]链类似,任何可以通过原型来呢访问到(并且是enumerable)的属性都会被枚举;
in 操作符来检查属性在对象中是否存在时,同样会查找对象的整条原型链(无论属性是否可枚举);
当你通过各种语法进行属性查找时都会查找[[Prototype]]链,直到找到属性或者查找完整条原型链
到哪里是[[Prototype]]的'尽头'呢?
所有普通的[[Prototype]]
链最终都会指向内置的Object.prototype
.
由于所欲的'普通'(内置,不是特定主机的拓展)对象都'源于'(或者说把[[Prototype]]链的顶端设置为)这个Object.prototype对象,所以它包含Javascript中许多通用的功能(比如.toString()和.valueOf(), .hasOwnProperty(..), isPrototypeOf(..))
给一个对象设置属性并不仅仅是添加一个新属性或者修改已知的属性值,现在完整的讲解一个这个过程
myObject.foo = 'bar';
如果myObject对象中包含名为foo的普通数据访问属性,这条赋值语句只会修改已有的属性;
如果foo不是直接存在于myObject中, [[Prototype]]链就会被遍历, 类似[[Get]]操作,如果原型链上找不到foo,foo就会被直接添加到myObject上;
如果属性名即出现在myObject中也出现在myObject的[[Prototype]]链上层,那么就会发生屏蔽, myObject中包含的foo属性会屏蔽原型链上层的所有foo属性,因为myObject.foo总是会选择原型链中最底层的foo属性;
foo不直接存在于myObject中而不是存在于原型链上层时myObject.foo = 'bar'会出现的三种情况:
1.如果在[[ProtoType]]链上层存在名为foo的普通数据访问属性, 并且没有被标记为只读(writable: false), 那就会直接在myObject中添加一个名为foo的新属性,他是屏蔽属性;
2.如果在[[ProtoType]]链上层存在名为foo的普通数据访问属性, 但是它被标记为只读(writable: false), 那么无法修改已有属性或者在myObject上创建屏蔽属性,如果运行在严格模式下,代码会抛出一个错误,否则,这条赋值语句会被忽略,总之,不会发生屏蔽
只读属性会阻止[[Prototype]]链下层隐式创建(屏蔽)同名属性, myObject对象竟然会因为其他对象中有一个只读foo就不能包含foo属性,但是这个限制只存在
=
赋值中, 使用Object.defineProperty(..)
并不会收到影响
3.如果在[[ProtoType]]链上层存在名为foo并且它是一个setter, 那就一定会调用这个setter, foo不会被添加到(或者说屏蔽于) myObject, 也不会重新定义foo这个setter ?
如果你希望在第二种和第三种情况下也屏蔽foo, 那就不能使用=
操作符来赋值, 而是使用Object.defineProperty(..)
来向myObject添加foo
一个隐式屏蔽的例子
var anotherObject = {
a: 2,
}
var myObject = Object.create(anotherObject);
console.log(anotherObject.a); // 2
console.log(myObject.a); // 2
anotherObject.hasOwnProperty('a'); // true
myObject.hasOwnProperty('a'); // false
myObject.a++;
console.log(myObject.a); // 3
console.log(anotherObject.a); // 2
myObject.hasOwnProperty('a'); // true!
尽管myObject.a++ 看起来应该是(通过委托)查找并增加anotherObject.a属性,但是别忘了++
操作相当与myObject.a = myobject.a + 1, ++
操作首先会通过[[Ptototype]]查找属性a并从anotherObject.a获取当前属性值2,然后给这个值加1, 接着用 [[put]]将值3赋给myObject中新建的屏蔽属性a;天呐!
修改委托属性时一定要小心.如果想让anotherObject.a的值增加,唯一的办法是 anotherObject.a++;
为什么一个对象需要关联到另一个对象?这样做有什么好处?
javascript没有类,只有对象
模仿类的行为利用了函数的一种特殊特性: 所有函数more都会拥有一个名为prototype的公有并且不可枚举的属性,它会执行另一个对象
function Foo(){
};
console.log(Foo.prototype); // {}
这个对象通常被称为 Foo的原型,因为我们铜鼓Foo.prototype的属性引用来访问它
function Foo(){
};
var a = new Foo();
console.log(Object.getPrototypeOf(a) === Foo.prototype); // true
console.log(Foo.prototype); // {}
调用new Foo()
时会创建a, 其中的一步就是给a一个内部的[[Prototype]]链接,关联到Foo.prototype指向的那个对象;
js中不能创建一个类的多个实例,只能创建多个对象,他们[[Prototype]]关联的是同一个对象,但是在默认情况下并不会进行复制,因此这些对象之前并不会完全失去联系,他们是相互关联的;
关于名称
继承意味着赋值操作,javascript(默认)并不会复制对象属性.相反,Javascript会在两个对象之前创建一个关联,这样一个对象就可以通过委托访问另一个对象的属性和函数, 委托更准确的描述了Javascript中对象的关联机制;
function Foo(){
};
console.log(Foo.prototype.constructor === Foo); // true
var a = new Foo();
console.log(a.constructor === Foo); // true
Foo.prototype默认有一个公有并且不可枚举的属性.constructor
,这个属性引用的是对象关联的函数;
通过'构造函数'调用 new Foo()
创建的对象也有一个.constructor
属性,指向'创建这个对象的函数';
实际上本身并没有
.constructor
属性, 而且,虽然a.constructor
确实指向Foo函数,但是这个属性并不是表示 a由Foo构造;
实际上.constructor
被委托给Foo.prototype, 而Foo.prototype.constructor默认指向Foo;
把.constructor
属性指向Foo看作是a对象由Foo构造非常容易理解,但这只不过是一种虚假的安全感, a.contructor只是通过默认的[[Prototype]]委托指向Foo, 这和'构造'毫无关系;
Foo.prototype的.contructor属性只是Foo函数在声明时的默认属性.如果你创建了一个新对象替换了默认的.prototype对象引用,那么新对象并不会自动获得 .constructor属性
function Foo() {
};
Foo.prototype = {
};
var a1 = new Foo();
console.log(a1.constructor === Foo); // false
console.log(a1.constructor === Object); // true
Object(..)并没有构造a1, 看起来应该是Foo()
构造了它; 如果真的是这样,那么a1.constructor应该是Foo, 但是不是Foo, 到底是怎么回事?
a1并没有.constructor属性,但是它委托[[Prototype]]链上的Foo.prototype, 但是这个对象也没有.constructor属性(不过默认的Foo.prototype对象有这个属性), 所以它会继续委托,这次会委托给委托链顶端的Object.prototype.这个对象有.constructor属性,指向内置的Object(..)函数
function Foo() {
};
Foo.prototype = {
};
Object.defineProperty(Foo.prototype, 'constructor', {
enumerable: false,
writable: true,
configurable: true,
value: Foo
})
var a1 = new Foo();
console.log(a1.constructor === Foo); // true
console.log(a1.constructor === Object); // false
.constructor属性是一个不可枚举,可写(可以被修改); 你可以给任意[[Prototype]]链中的任意对象添加一个名为constructor属性或者对其进行修改,你可以任意对其赋值;
a.constructor是一个非常不可靠并且不安全的引用,尽量避免;
function Foo(name) {
this.name = name;
};
Foo.prototype.myName = function() {
return this.name;
}
function Bar(name, label){
Foo.call(this, name);
this.label = label;
}
// 创建一个新的Bar.prototype对象并关联到Foo.prototype
Bar.prototype = Object.create(Foo.prototype);
// 注意! 现在没有Bar.prototype.constructor了
// 如果你需要这个属性的话可能需要手动修复它
Bar.prototype.myLabel = function() {
return this.label;
}
var a = new Bar('a', 'obj.a');
console.log(a.myName());
console.log(a.myLabel());
Bar.prototype = Object.create(Foo.prototype);
调用 Object.create(..)会并空创建一个'新'对象并把新对象内部的[[prototype]]关联到你指定的对象, 直接把原始的关联对象抛弃掉;
es6的Object.setPrototypeOf(..)
对比一下两种把Bar.prototype关联到 Foo.prototype的方法
// es6之前需要抛弃默认的Bar.prototype
Bar.prototype = Object.create(Foo.prototype);
// es6开始可以直接修改现有的Bar.prototype
Object.setPrototypeOf( Bar.prototype, Foo.prototype)
function Foo() {
}
Foo.prototype.blah = 'aa';
var a = new Foo();
console.log(a instanceof Foo); // true
instanceof 操作符的左操作数是一个普通的对象,右操作数是一个函数,instanceof回答的问题是: 在a的整条[[prototype]]链中是否有指向 Foo.prototype的对象?
这个方法只能处理对象(a)和函数(带.prototype引用的Foo)之间的关系, 如果想判断两个对象(例如a和b)之间是否通过[[Prototype]]链关联,只用instanceof 无法实现
function isRelatedTo(o1, o2) {
function F() {};
F.prototype = o2;
return o1 instanceof F;
}
var a = {};
var b = Object.create(a);
console.log(isRelatedTo(b, a)); // true
isPrototypeOf()
function Foo() {
}
Foo.prototype.blah = 'aa';
var a = new Foo();
console.log(Foo.prototype.isPrototypeOf(a)); // true
isPrototypeOf(..)
回答的问题是: 在a的整条[[Prototype]]链中是否会出现Foo.prototype?
判断两个对象之前的关系
b.isPrototypeOf(c);
Object.getPrototypeOf(a), 直接获取一个对象的[[Prototype]]链
function Foo() {
}
Foo.prototype.blah = 'aa';
var a = new Foo();
console.log(Object.getPrototypeOf(a));
console.log(Object.getPrototypeOf(a) === Foo.prototype); // true
console.log(Foo.prototype.isPrototypeOf(a)); // true
绝大多数(不是所有!)浏览器也支持一种非标准的方法来访问内部[[Prototype]]属性
a.__proto__ === Foo.prototype; // true
.constructor, .__ptoto__实际并不存在你正在使用的对象中, 它和其他的常用函数(.toString(..), .isPrototypeOf(..), 等等)一样, 存在于内置的Object.prototype中(他们是不可枚举的)
Object.defineProperty(Object.prototype, '__proto__', {
get: function() {
return Object.getPrototypeOf(this);
},
set: function(o) {
Object.getPrototypeOf(this, o);
return 0;
}
})
[[Prototype]]机制就是存在于对象中的一个内部链接,它会引用其他对象;
这个链接作用是: 如果在对象上没有找到需要的属性或者方法引用,引擎就会继续在[[Prototype]]关联的对象上进行查找,同理,如果在后者中也没有找到需要的引用就会继续查找它的[[Prototype]], 以此类推,这一系列对象的链接被称为'原型链';
[[Prototype]]机制的意义是什么?
Object.create(..)会创建一个新对象,并把它关联到我们制定的对象;这样就可以充分发挥[[Prototype]]机制的威力(委托),并且可以避免不必要的麻烦(比如使用new的构造函数调用会生成.prototype和.constructor引用)
Object.create(null)会创建一个空的(或者说null)[[Prototype]]链接的对象,这个对象无法进行委托,由于这个对象没有原型链,所有instanceof 操作符无法进行判断, 因此总是会返回 false,这些特殊的空[[Prototype]]对象通常被称作'字典', 它们完全不会受到原型链的干扰,因此非常适合用来存储数据;
if(! Object.create) {
Object.create = function (o) {
function F() {};
F.prototype = o;
return new F();
}
}
使用了一个一次性函数F,我们呢通过改写它的.prototype属性使其指向想要关联的对象,然后再使用 new F()来构造一个新对象进行关联;
内置的Object.create(...)函数还提供了一系列附加功能;
var anotherObject = {
a:2,
}
var myObject = Object.create(anotherObject, {
b: {
enumerable: false,
writable: true,
configurable: false,
value: 3,
},
c: {
enumerable: true,
writable: false,
configurable: false,
value: 4,
},
})
console.log(myObject.hasOwnProperty('a')); // false
console.log(myObject.hasOwnProperty('b')); // true
console.log(myObject.hasOwnProperty('c')); // true
console.log(myObject.a); // 2
console.log(myObject.b); // 3
console.log(myObject.c); // 4
第二个参数指定了需要添加到新对象的属性名以及这些属性的属性描述符
class ColorPoint extends Point {
}
// 等同于
class ColorPoint extends Point {
constructor(...args) {
super(...args);
}
}
如果子类没有constructor
方法,这个方法会被默认添加,
在子类的构造函数中,只有调用了super
之后,才可以使用this
关键字,否则会报错;这是因为子类实例的构建,是基于对父类实例加工,只有super方法才能返回父类实例
super关键字
super这个关键字,既可以当做函数使用,也可以当做对象使用
第一种情况, super作为函数调用时,代表父类的构造函数, es6要求,h子类的构造函数必须执行一次super函数
class A {}
class B extends A {
constructor() {
super();
}
}
上面代码中,子类B的构造函数之中的super, 代表调用弗雷的构造函数,这是必须的,否则Javascrip引擎会报错
super虽然代表了父类的构造函数,但是返回的是子类B的实例,t即super内部的this指向的是B, 因此super()在这里相当于A.prototype.call(this);
作为函数时, super()只能用在子类的构造函数之中, 用在其他地方就会报错
function foo(num) {
console.log("foo: " + num);
this.count++;
}
foo.count = 0;
var i;
for(i = 0;i <10; i++) {
if(i > 5) {
foo(i);
}
}
console.log(foo.count);
foo: 6
foo: 7
foo: 8
foo: 9
0
执行foo.count = 0,的确向函数随想foo添加了一个属性count,但是函数内部代码this.count中的this并不是指向那个函数对象;虽然属性相同,但是根对象却并不相同
无意中创建了一个全局变量count,它的值我NaN
this是在运行时进行绑定的,而不是在编写时进行绑定,它的上下文取决于函数调用时的各种条件,this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式;
当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文).这个记录会包含函数在哪里被调用(调用栈),函数的调用方法,传入的参数等信息.this就是记录的其中一个属性,会在函数执行过程中用到
调用栈是解析器(如浏览器中的javascript解析器)的一种机制,可以在脚本调用多个函数时,跟踪每个函数在完成执行时应该返回控制的点(如什么函数正在执行,什么函数被这个函数调用, 下一个调用的函数是谁)
当脚本要调用一个函数时,解析器把该函数添加到栈中并且执行这个函数。
任何被这个函数调用的函数会进一步添加到调用栈中,并且运行到它们被上个程序调用的位置。
当函数运行结束后,解释器将它从堆栈中取出,并在主代码列表中继续执行代码。
如果栈占用的空间比分配给它的空间还大,那么则会导致“堆栈溢出”错误。
调用栈
function foo() {
console.log(this.a);
}
var obj = {
a: 2,
foo: foo,
};
var bar = obj.foo;
var a = 'oops, global';
bar(); // oops, global
虽然bar是obj.foo的一个引用,但是实际上,它引用的是foo函数本身,因为此时的bar()其实是一个不带任何参数的函数调用,因此应用了默认绑定;
function foo() {
console.log(this.a);
}
function doFoo(fn) {
fn();
}
var obj = {
a: 2,
foo: foo,
};
var a = 'oops, global';
doFoo(obj.foo); // oops, global
参数传递其实就是一种隐式赋值,因为我们传入函数时也会被隐式赋值
function foo() {
console.log(this.a);
}
function doFoo(fn) {
fn();
}
var obj = {
a: 2,
foo: foo,
};
var a = 'oops, global';
setTimeout(obj.foo, 100)
JavaScript环境内置的setTimeout()函数实现和下面的伪代码类似
function setTimeout(fn, delay) {
// 等待delay毫秒
fn(); // <-- 调用位置
}
回调函数丢失this是非常常见的;
调用回调函数的函数可能会修改this;一些流行的javascript库中事件处理器常会把回调函数的this强制绑定到触发事件的DOM元素
你无法控制回调函数的执行方式,因此就没有办法控制会影响绑定的调用位置;如何通过固定this来修复
函数的call(..)和apply(..)方法
它们的第一个参数是一个对象,它们会把这个对象绑定到this,接着在调用函数时指定这个this,因此你可以直接指定this的绑定对象
如果你传入一个原始值(字符串类型, 布尔类型或者数字类型)来当做this的绑定对象,这个原始值会被转换成它的对象形式(也就是 new String(..), new Boolean(..) 或者 new Number(..));
function foo() {
console.log(this.a);
}
var obj = {
a: 2,
};
var bar = function () {
foo.call(obj);
};
bar(); // 2
setTimeout(bar, 100); //2
bar.call(window); //2
bar()内部手动调用foo.call(obj), 因此强制把foo的this绑定到了obj, 无论之后怎么调用函数bar,它总会手动在obj上调用foo
如果你把null或者undefined作为this的绑定对象传入call, apply或者bind,这些值在调用时会被忽略,实际应用的是默认绑定规则;
什么情况下你会传入 null 呢
一种非常常见的做法是使用apply(..)来'展开'一个数组,并当做参数传入一个函数,类似的,bind(..)可以对参数进行柯里化
function foo(a, b) {
console.log("a:" + a + ", b:" + b);
}
// 将数组展开成参数
foo.apply(null, [2, 3]);
// 使用bind(..)进行柯里化
var bar = foo.bind(null, 2);
bar(3);
这两种都需要传入一个参数当做this的绑定对象,如果函数不关心this的话,你仍然需要传入一个占位符,这时null 可能是个不错的选择
但是使用null来忽略this绑定可能禅城一些副作用,如果某个函数确实使用了this(比如第三方库中的一个函数),那默认绑定规则会把this绑定到全局对象(在浏览器中这个对象是window),这将导致不可预计的后果(比如修改全局对象);
Object.create(null)
和{}
很像,但是并不会创建Object.prototype
这个委托,所以它比{}
更空
var ø = Object.create(null);
foo.bind(ø, 2);
最容易在赋值时发生
function foo() {
console.log(this.a);
}
var a = 2;
var o = {
a: 3,
foo: foo,
};
var p = {
a: 4,
};
o.foo(); // 3
(p.foo = o.foo)(); // 2
赋值表达式p.foo = o.foo
的返回值是目标函数的引用,因此调用位置是foo()而不是p.foo()或者o.foo();这里会应用默认绑定
对于默认绑定来说,决定this绑定对象的并不是调用位置是否处于严格模式,而是函数体是否属于严格模式,如果函数体处于严格模式,this被绑定到undefined,否则this会被绑定到全局对象
function foo() {
return (a) => {
// this继承自foo
console.log(this.a);
}
}
var obj1 = {
a: 2,
}
var obj2 = {
a: 3,
}
var bar = foo.call(obj1);
bar.call(obj2); // 2,不是3!
foo()内部创建的箭头函数会捕获调用时 foo()的this.由于foo()的this绑定到obj1, bar(引用箭头函数),的this也会绑定到obj1,
箭头函数的绑定无法被修改(new也不行)
现在有如下需求
对于需求1,刚开始想的解决方法是直接在js中判断User-Agent, 如果是手机浏览器, 则直接通过window.location跳转到 m站的地址链接, 但是这样会有一个问题: 用户在手机上输入pc的地址。 首先会把C端所有的资源都请求下来, 之后才会进入User-Agent的js判断逻辑, 之后会重新下载M站的相关资源。所以这种方式会下载没有必要的pc站的资源
最好的是在nginx上判断User-Agent直接重定向到M站
if ( $http_user_agent ~* "(Android|iPhone|Windows Phone|UC|Kindle)" ){
rewrite ^/(.*)$ http://a.com/m redirect;
}
对于需求2, 可以理解为改动点有两个
处理时间,获得昨天,明天,后天...以及对应的星期
const arr = [ '周日', '周一', '周二', '周三', '周四', '周五', '周六', ];
function getDateStr(count) {
if (count === 0) {
return '今天';
}
const date = new Date();
date.setDate(date.getDate() + count);
const index = date.getDay(); // 获取周几,0为周日
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
return year + '-' + month + '-' + day + '(' + arr[index] + ')';
}
for (let i = 0; i < this.startDayItems.length; i++) {
this.startDayItems[i] = getDateStr(i);
}
// 得出一个数组
startDayItems: [ '今天', '2017-04-17(周二)', '2017-04-18(周三)', '2017-04-19(周四)', '2017-04-20(周五)', '2017-04-21(周六)', '2017-04-22(周日)', ],
时间戳转换成 2017.03
const date = new Date();
const Y = date.getFullYear() + '.';
const M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1);
this.todayTime = Y + M;
时间戳装换成 yyyy-MM-dd hh:mm:ss
2017-04-21 08:19:00
time(value) {
const date = new Date(value * 1000);
const Y = date.getFullYear() + '-';
const M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-';
const D = date.getDate() + ' ';
const h = (date.getHours() < 10 ? '0' + date.getHours() : date.getHours()) + ':';
const m = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes() + ':';
const s = date.getSeconds();
return (Y + M + D + h + m + s);
},
日期格式转换为 时间戳
// 也很简单
const strtime = '2017-04-20 20:55:49:123';
const date = new Date(strtime); //传入一个时间格式,如果不传入就是获取现在的时间了,这样做不兼容火狐。
// 可以这样做
const date = new Date(strtime.replace(/-/g, '/'));
// 有三种方式获取,在后面会讲到三种方式的区别
time1 = date.getTime();
time2 = date.valueOf();
time3 = Date.parse(date);
/*
三种获取的区别:
第一、第二种:会精确到毫秒
第三种:只能精确到秒,毫秒将用0来代替
比如上面代码输出的结果(一眼就能看出区别):
1398250549123
1398250549123
1398250549000
*/
Date()的参数
new Date("month dd,yyyy hh:mm:ss");
new Date("month dd,yyyy");
new Date("yyyy/MM/dd hh:mm:ss");
new Date("yyyy/MM/dd");
new Date(yyyy,mth,dd,hh,mm,ss);
new Date(yyyy,mth,dd);
new Date(ms);
比如
new Date("September 16,2016 14:15:05");
new Date("September 16,2016");
new Date("2016/09/16 14:15:05");
new Date("2016/09/16");
new Date(2016,8,16,14,15,5); // 月份从0~11
new Date(2016,8,16);
new Date(1474006780);
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.