es6和js笔记(192)

变量命令

var和let

  • let只在其块级作用域内有效,而var却全局有效。
  • 区块中有let和const,则这个区块暂时性死区;即区块会对这些命令声明的变量一开始就形成封闭作用域,只要在声明前使用这些变量,就会报错(相反var不会)

const命令只保证变量指向地址不变,不能保证数据不变。

1
2
3
4
5
6
7
8
9
10
11
12
13
const foo = [];
foo.prop = 123;//这是被允许的
foo = {};//这是不被允许的
//要完全冻结,参考如下代码
var constantize = (obj) => {
Object.freeze(obj);
Object.key(obj).forEach((key, value) => {
if (typeof obj[key] === 'object') {
constantize(obj[key]);
}
});
};

字符

字符遍历。(好像用[]也可以遍历)

es5: for (var i=0; i
es6: for (let i=0 of str) {alert(i);}

字符函数

es6多了:

includes:返回布尔,表示十分找到参数字符串。第二个参数,表示开始搜索的位置
startsWith:返回布尔,表示参数字符串是否在源字符串的头部。第二个参数,表示开始搜索的位置
endsWith:返回布尔,表示参数字符串是否在源字符串的尾部。第二个参数,表示开始搜索的位置
repeat:返回一个新字符串,表示将原字符串重复n次

模板字符串,使用反引号(`这个符号)

普通字符: dd'\n'asdf 这里有换行
多行字符: 无需连接符,所有的空格、回车和缩进都会被保留在输出中。
变量嵌入:hello ${name} are ${time}? name和time是定义的变量。如果括号里面的不是字符串,则按一般规则转换为字符,如调用toString方法
函数调用:function tag(s, v, d); taghello ${a+b} ${a};
String的raw函数

正则

es5中String对象的方法,search、match、replace、split支持JS正则

类型扩展

数值

Number增加成员isFinite,isNaN.
es6把es5中的全局函数parseInt,parseFloat移植到了Number对象上。
Number成员,isSafeInteger范围在2的53次方

isFinite()isNaN()在es5是全局,es6在Math上又加了相同的函数;
区别在于:全局的方法先调用Number把非数值转换成数值,再进行判断。
而新方法只对数值有效,非数值一律false

Math对象,es6新增17个方法。它的方法都是静态方法:Math.abs(s)

Math.trunc(4.2)//除去一个数的小数部分,返回整数

Math.sign();//判断一个数是正数(re:+1)、负数(re:-1)、还是零(re:0)、其它(re:NaN)
Math.cbrt();//计算一个数的立方根
对数、指数、平方等就不写了。

Array数组

  • from函数;伪组数转换:

    let arraylike = {

    0: 'a',
    1: 'b',
    length: 2//这个属性必须有
    

    };//注意这是个对象,并不是数组

es5中转换数组:[].slice.call(arraylike);或这么写Array.prototype.slice.call(arraylike);//[‘a’, ‘b’];
es6中转换数组:Array.from(arraylike);
//只要部署了Iterator接口的数据结构,和ES6的Set、Map。都可以转换。

//如果参数是数组,则返回新数组

//from还可以接受第二个参数,用来对每个元素进行处理,将处理后的值放入返回的数组中

function test(a,b,c,d) 
{ 
    var arg = Array.prototype.slice.call(arguments,1); 
    alert(arg); 
} 
test("a","b","c","d"); //b,c,d
Array.of

Array.of(2, 3, 4)//[2, 3, 4]

find返回的是第一个符合条件的元素: [1, 4, -5, 10].find((n) => n < 0)//-5 [1, 5].find(function(value, index, arr) { return value > 9;})//10

fill填充数组

entries()//返回键值对遍历器,keys()//返回值遍历器,values()//返回键遍历器

for (let index of ['a', 'b'].keys()) {}
for (let [index, elem] of ['a', 'b'].entries()) {}
//es6有遍历器对象

includes()//返回一个布尔值,表示某个数组是否包含给定值。 [1, 2, 3].includes(2);//true

函数(函数参数默认压栈顺序,从左到右)

函数参数的默认值

es5

function log (x, y) { y = y || 'world';}

es6

function log(x, y = 'world') {}
  • 解构和默认参数

    function foo({x, y = 5}) { console.log(x, y);}
    foo({})//undefined, 5
    foo({x:1})//1, 5
    foo({x:1, y:2})//1, 2
    
    function m1({x=0, y=0} = {}) {}
    function m2({x, y} = {x:0, y:0}) {}
    //上面俩个函数,在m({x:3})、m({})这种类型时,就不同
    //它们顺序是先赋值参数,再执行构里面的默认值
    
    //默认参数位置不用在尾部。但是调用要用undefined参数,如: f(undefined, 1)
    
    //使用默认参数会相应减少length属性的值。(function(a, b, c = 5){}).length //2
    

rest参数和扩展运算符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function add(...values) {
let sum = 0;
for (var val of values) {
sum += val;
}
return sum;
}
//有了rest后,就可以不使用arguments;只是注意rest参数是数组,且后面不能再有其它参数
function push(array, ...items) {
items.forEach(function(item) {
array.push(item);
});
}
var a = [];
push(a, 1, 2, 3, 4);
扩展运算符(…)
  1. 当作参数声明时,它如同c的自定义长度参数,允许多个参数传入,而它就是个数组
  2. 当作为参数使用,定义时,它就是个数组的解构符。

扩展运算符也是加三个点(…),好比rest参数的逆运算,将一个数组转为用逗号分隔的参数序列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
**数组使用**
//es5
function f(x, w, v, h) {}
var args = [0, 1, 2, 3];
var arge = [4, 5];
f.apply(null, args);
Math.max.apply(null, [14, 3, 77]);
Array.prototype.push.apply(args, arge);
args.concat(arge, arge);
**连接数组**
//es6
function f(x, w, v, h) {}
var args = [0, 1];
f (-1, ...args, ...[7]);
Math.max(...[14, 3, 77]);
args.push(...arge);
[...args, ...arge, ...arge];

function有name属性(浏览器支持,es6才标准)

箭头函数

箭头函数内部没有自己的this,导致内部的this就是外层代码块的this。

除此外,arguments、super、new.target也是指向外层函数对应的。

尾调用优化(严格模式生效)

js的函数调用会形成”调用帧”,以记录每个调用函数的信息和内部变量。

1
2
3
4
function f() {
let m = 1; let n = 3;
return g(m+n);
}

如上,这种当执行到return,就会销毁f的调用帧,只使用g的调用帧。这种”尾调用”大大的节省了内存
注意:内层函数用到外层函数内部变量,还是无法尾调用(如: g函数内部使用f的m变量)

尾调用的尾递归(严格模式生效)

递归耗内存是因为保存太多的调用帧,尾调用就只有一个调用帧,所以永远不会发生”栈溢出”错误

对象扩展

属性简洁写法

1
2
3
4
5
6
7
8
9
10
11
let x = 1;
let y = 2;
let z = {x, y}//{x: 1, y: 2} 这样以后取个好名字就ok拉,好方便
var obj = {
class() {}//es6不同,它允许简洁写法,自己补充属性名为class
}
var obj = {//es5要么字符,要么自己定义属性
'class': function(){}
}

属性(属性多了 set get描述符,可以像c#那样使用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//es5
var foo = 'bar';
var baz = {foo};
var o = {
'class': function {}//此处,class对语法解析器来说是个关键字,为了让语法解析器解析为属性函数,必须加单引号
method: function() {
return 'he';
}
}
//es6
var baz = {foo: 'bar'};
var o = {
class() {}
method() {
return 'he';
}
}

属性名表达式(使用[]来实现)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//对属性的定义有俩种(es5只能用方法一)
obj.foo = 123;
obj['f' + 'oo'] = 123;
//当多个定义的时候
var na = 4;
var hh = 'wq';
let obj = {
'pp': 2,
['a' + na]: 3,
[wq]: 4,
['a' + wq]: 5
};
//另外(表达式虽然可以使用,但是表达式计算的结果是全数字的话(数字开头也算),访问属性时会混乱出错)
var a = {};
a.obj = 3;
a[obj];//error
a['obj'];//ok 因为点运算符后面总是字符串
1、对js来说属性名一定是字符串,不可能是其它对象
2、[]运算出来的只能是字符串,
3、[]里面的只能是对象,`['obj']`是生产一个`obj`的字符串,再转换
name的俩种特殊情况:
1
2
3
4
5
6
(new Function()).name // "anonymous"
var doSomething = function() {
// ...
};
doSomething.bind().name // "bound doSomething"

Object.is()

除了个别(NaN、+0、-0)和===不一样,其它都一致。

Object。assign()

它会从左向右,依次把元素叠加到第一个参数上,并返回第一个参数(注意assign只是一级属性复制,比浅拷贝多深拷贝了一层而已)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var target = { a: 1, b: 1 };
var source1 = { b: 2, c: 2 };
var source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
Object(true) // {[[PrimitiveValue]]: true}
Object(10) // {[[PrimitiveValue]]: 10}
Object('abc') // {0: "a", 1: "b", 2: "c", length: 3, [[PrimitiveValue]]: "abc"}
//如果组合的话,只进字符。
//Object.assign拷贝的属性是有限制的,只拷贝源对象的自身属性(不拷贝继承属性)
//也不拷贝不可枚举的属性(enumerable: false)。
//如果要保持继承链
function clone(origin) {
let originProto = Object.getPrototypeOf(origin);
return Object.assign(Object.create(originProto), origin);
}

属性的可枚举

es5有三个操作会忽略enumerablefalse的属性。

  • for...in循环:只遍历对象自身的和继承的可枚举的属性
  • Object.keys():返回对象自身的所有可枚举的属性的键名
  • JSON.stringify():只串行化对象自身的可枚举的属性
    es6多个assign

proto

书上建议使用
Object.setPrototypeOf()(写操作)、
Object.getPrototypeOf()(读操作)、
Object.create()(生成操作)代替 __proto__的直接操作,因为只有浏览器广泛支持,其它环境不一定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
setPrototypeOf(obj, proto) {
obj.__proto__ = proto;
return obj;
}//setPrototypeOf的原型和它差不多,也就意味着这是浅拷贝。
let proto = {};
let obj = { x: 10 };
Object.setPrototypeOf(obj, proto);
proto.y = 20;
proto.z = 40;
obj.x // 10
obj.y // 20
obj.z // 40

Object.keys 和values相对

1
2
3
var obj = { foo: "bar", baz: 42 };
Object.keys(obj)
// ["foo", "baz"]

Object.values 和keys相对

1
2
3
var obj = { foo: "bar", baz: 42 };
Object.values(obj)
// ["bar", 42]

Object.entries

1
2
3
var obj = { foo: 'bar', baz: 42 };
Object.entries(obj)
// [ ["foo", "bar"], ["baz", 42] ]

Symbol

从字面上理解,是为了属性引入新方法,同时避免重名的情况;保证属性名独一无二。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var mySymbol = Symbol();//它接收字符的,Symbol('a'),这样多了个字符串标识
var a = {[mySymbol]: 'H!'};//由于Symbol是函数,所以必须用属性名表达式
//还有一种使用方式是定义常量,感觉蛮实用的
const COLOR_RED = Symbol();
const COLOR_GREEN = Symbol();
function getComplement(color) {
switch (color) {
case COLOR_RED:
return COLOR_GREEN;
case COLOR_GREEN:
return COLOR_RED;
default:
throw new Error('Undefined color');
}
}
Symbol属性函数

Object.getOwnPropertySymbols方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。

Symbol.for()接受一个字符串作为参数,然后搜索有没有以该参数作为名称的Symbol值

1
2
3
var s1 = Symbol.for('ff');
var s2 = Symbol.for('ff');
s1 === s2//true

Symbol.keyFor()返回一个已登记的Symbol类型的key;
换句话说,它对Symbol.for返回成功的是能返回的。
对新建的Symbol(<string>)类型是无法返回的。

Proxy(属于元编程)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
var obj = new Proxy({}, {
get: function (target, key, receiver) {
console.log(`getting ${key}!`);
return Reflect.get(target, key, receiver);
},
set: function (target, key, value, receiver) {
console.log(`setting ${key}!`);
return Reflect.set(target, key, value, receiver);
}
});
obj.count = 1
// setting count!
++obj.count
// getting count!
// setting count!
// 2
//同一个拦截器,可以设置多个操作
var handler = {
get: function(target, name) {
if (name === 'prototype') {
return Object.prototype;
}
return 'Hello, ' + name;
},
apply: function(target, thisBinding, args) {
return args[0];
},
construct: function(target, args) {
return {value: args[1]};
}
};
var fproxy = new Proxy(function(x, y) {
return x + y;
}, handler);
fproxy(1, 2) // 1
new fproxy(1,2) // {value: 2}
fproxy.prototype === Object.prototype // true
fproxy.foo // "Hello, foo"

总的来说有点像c++的操作符函数,但比操作符函数高级
支持一下特性:

  1. get(target, propKey, receiver)拦截对象属性读取
  2. set(target, propKey, value, receiver)拦截对象属性的设置 返回一个布尔值。
  3. has(target, propKey)拦截propKey in proxy的操作,以及对象的hasOwnProperty方法 返回一个布尔值。
  4. deleteProperty(target, propKey)拦截delete proxy[propKey]的操作 返回一个布尔值。
  5. ownKeys(target)拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy),返回一个数组
  6. getOwnPropertyDescriptor(target, propKey)拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
  7. defineProperty(target, propKey, propDesc)拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一个布尔值。
  8. preventExtensions(target)拦截Object.preventExtensions(proxy),返回一个布尔值。
  9. getPrototypeOf(target)拦截Object.getPrototypeOf(proxy),返回一个对象。
  10. isExtensible(target)拦截Object.isExtensible(proxy),返回一个布尔值。
  11. setPrototypeOf(target, proto)拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。
  12. apply(target, object, args)拦截 Proxy 实例作为函数调用的操作,比如proxy(…args)、proxy.call(object, …args)、proxy.apply(…)。
  13. construct(target, args)拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(…args)。

这个确实太多了,也没咋记,具体看书吧!

二进制数组

网络传输和图片视频文件解析等
var type = new ArrayBuffer(32);开辟二进制内存

访问二进制内存,不同数据有多种读取方式,叫”视图”
有俩种视图:

  1. TypeArray视图, 整体是同一数据类型Int8Array Uint8Array....16 32
  2. Dataview视图

    TypeArray视图

    构造函数参数有3个:
    第一个必需,接收ArrayBuffer
    第二个可选,视图开始的字节序号,必需保证字节合理性不然异常
    第三个可选,视图包含的数据个数,必需保证字节合理性不然异常

    成员BYTES_PER_ELEMENT表示类型所占字节数
    成员byteLength获取占内存长度
    成员length获取数组含多少个成员

    1
    2
    var b = new ArrayBuffer(8);
    var v1 = new Int32Array(b);

    DataView视图

    get方式一轮次获
    set方式设置
    俩函数都有字节序的设置

Set/Map

和stl不同,他们并不会排序
map的键支持对象

1
2
3
4
let map = new Map([["d", "a"], ["t", "x"]]);
map
.set(k, 111);
.set(k, 222);

和Array连用

1
2
3
4
let set = new Set([1,2,3]);
set = new Set([...set].map(x=>x*2));
let map = new Map([...map].filter(([k, v]) => k<3))

Iterator接口

主要用于es6的for...of语句
此语句的遍历依靠next() 返回一个对象{value:…, done:…} false表示遍历结束

es6中只要部署了Symbol.iterator接口,就可以遍历(数组默认部署)

1
2
3
let ar = ['a', 'b', 'c'];
let it = ar[Symbol.iterator]();//访问ar这个数组部署的遍历函数,并得到这个表达式
it.next()//{value:'a', done:false}

当对象也要使用for of语句遍历时,加个接口就好(接口是个函数,返回带有next的对象)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function obj(v) {
this.v = v;
this.n = null;
}
obj.prototype[Symbol.iterator] = function() {//定义这个对象有个此类接口,并定义代码
var it = {next: next}
var cur = this;
function next() {
if (cur) {
var va = cur.v;
var do = cur == null;
cur = cur.next;
return {done: do, value: va}
} else {
return {done: true}
}
}
return it;//返回带有next 函数的对象
}
var one = new obj(1);
var two = new obj(2);
var three = new obj(3);
one.next = two;
two.next = three;
for (var i of one)//如此遍历就可以了

还可以用于解构
由于...运算符也是用Iterator接口,所以

1
let four = [...one]//[1, 2, 3] 这个样子是完全可以的

字符串也可以用next遍历

js原有的for…in只能读取属性值,后面的for…of才能读取键值如同forEach

Generator函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function* he() {
yield 'aa';
yield 'dd';
return 'ss';
}
let hw = he();//g函数 调用并不会立即执行,只会返回个函数指针
hw.next();//{value:'aa', done:false} 使用next才会真正执行,内部的yield只是给个执行暂停的操作
hw.next();//{value:'dd', done:false}
hw.next();//{value:'ss', done:true}
hw.next();//{value:undefined, done:true}
//由此可以写个暂缓函数
function* f() {...}
var gen = f();
setTimmeout(function() {gen.next();}, 200);

yield不能用于一般函数即使临时函数也不行

next方法参数

next可以带一个参数,该参数会被作为上一条yield语句的返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
function *foo(x) {
var y = 2*(yeild (x+1));
var z= yeild (y/3);
return (x+y+z);
}
var a = foo(5);
a.next()//{value:6,done:false} 走到第一个yeild停止
a.next()//{value:NaN, done:false} 由于传的参数为定义,所以作为返回值y被赋值是undefined
a.next()//{value:NaN, done:false} 由于传的参数为定义,所以作为返回值z被赋值是undefined
var b = foo(5);
b.next()//{value:6,done:false} 走到第一个yeild停止
b.next(12)//{value:8,done:true} yeild返回12,乘2除3为8

配合Iterator接口

以前要封装接口,必须让函数返回带next函数标准返回对象的对象
现在如下便可

1
2
3
4
5
6
7
8
9
let j = {ff: 33, ss: 55}
function* obj() {
let key = Object.keys(this);
for (let pro of key) {
yield [pro, this[pro]];//它会自动弹出标准对象
}
}
j[Symbol.iterator] = obj;
for (let t of j) {...}

Generator.prototype.return()强制返回,不继续执行

yield*的使用

Generator内部不能调用Generator函数

1
2
3
4
5
6
7
8
9
fucction* foo() {
yield 'a';
yield 'b';
}
function* bar() {
yield 'x';
yield* foo();
yield 'y';
}

Genrator中的return

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function *foo() {
yield 2;
return "foo";
}
function *bar() {
yield 1;
var v = yield* foo();
console.log("v:" + v);
yield 4;
}
var it = bar();
it.next()//{value:1, done:false}
it.next()//{value:2, done:false}
it.next()//{value:3, done:false}
it.next()//"v:foo" {value:1,done:false}
it.next()//{value:undefined, done:true}

Generator中的this

和普通函数的this差不多,但是不能和new使用,当构造函数使用得变通

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function* F() {
this.a = 1;
yield this.b = 2;
}
var obj = {};
var f = F.call(obj);
f.next();//...2
f.next();//...3
f.next();//...undefined
obj.a//1
obj.b//2
obj.c//3
function Gen() {
return F.call(F.prototype);
}
var f = new Gen();
f.next();//...2
f.next();//...3
f.next();//...undefined
f.a//1
f.b//2
f.c//3

Generator使用Trick

以前切换状态必须要个高层变量来保存,切换时就false,true的换。
现在可以直接:

1
2
3
4
5
6
7
8
9
10
var cl = function* () {
while(true) {
yield true;
yield false;
}
}
let t = cl();
t.next()//...true
t.next()//...false
t.next()//...true

Promise

状态对象,用来传递异步消息

1
2
3
4
function ati(s) {
return new Promise((r, j) => {setTimeout(r, s, 'wangqiu')})//执行r函数是让此对象状态变为resolve;执行j函数是让此对象变成reject状态
}
ati(20).then((v) => {console.log(v)}, (j)=>{console.log(j)})//先执行函数,并定义then;当状态是resolve则执行第一个参数的函数,当状态是rejeect则执行第二个参数函数

Promise.then

由于then返回的是一个promise对象,所以可以连续then

1
2
3
4
5
getJson("1.json").then(function(post) {
return post.comment;
}).then(function(comment) {
...
})

Promise.catch和普通一样

Promise.all

all参数是个Promise数组,返回一个Promise
只有当数组里面的Promise全改变状态,才会改变此Promise状态

1
Promise.all(promises).then(function(){...});

Promise.race同上调用,数组中一个改变,就跟着改变

Class

类的方法之间不需要逗号分隔,加了会报错
typeof ClassNamefunction即类数据类型是函数
构造函数也只是prototype.constructor的子函数

类内部所有定义的属性都是不可枚举的

let d = new class {...}()这样写法是允许的

继承

子类构造函数必须调用super
子类没有自己的this对象,是继承父类的this,所以必须supper构造父类
因此super之前的this调用都是错误的

1
2
3
4
5
class ColorPoint extends Point{
constructor() {
super();
}
}

给类添加私有方法Trick

通过symbol导致第三方获取不到函数名,所以称为私有方法

1
2
3
4
5
6
7
8
9
10
const bar = Symbol('bar');
const snaf = Symbol('snaf');
class myclass {
foo(baz) {
this[bar](baz);
}
[bar](baz) {//利用方括号访问制造变量属性,这样外部获取不到变量就无法访问
return this[snaf] = baz;
}
}

set和get属性拦截

class和generator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Foo{
constructor(..args) {
this.args = args;
}
*[Symbol.iterator]() {//如果自己写要index记录next遍历位置,要返回next属性函数返回指定类型
for (let arg of this.args) {
yield arg;
}
}
}
for (let x of new Foo('hello', 'world')) {
console.log(x);
}
//hello
//world

静态函数

这个和es5直接定义函数的属性相似,区别在于父类的静态方法可以被子类继承
es6规定static只能静态方法,不能静态变量属性

1
2
3
4
5
6
class Foo {
static classMethod() {
return 'hello';
}
}
Foo.classMethod();

new.target

这个属性专门用在构造函数中(es5的函数构造,es6的constructor函数里面使用)
它用来判断new的对象名(这样就算是继承也能知道new的对象是哪个)

1
2
3
4
5
6
7
function Person(name) {
if (new.target === Person) {
...
}
}
var per = new Person('1');
var nper= Person.call(person, '2');

Class的继承

子类允许使用super关键字来访问父类函数

尽量不用super访问变量,我不知道为何会访问到prototype上去,赋值就可以,读却不行

Module

模块加载

CommonJS加载方式
let {stat, exists} = require('fs');

es6方式:
import {stat, exists} from 'fs'

前者整体加载fs模块(加载所有的fs方法),然后在使用时用到3个方法。即”运行时加载”
后者通过命令显式指定输出代码,输入时也用静态命令方式。实际只加载了fs的3个方法,其它未加载。即”编译时加载”
后者效率高

导入导出命令

exportimport对接的接口名必须相同
如果不同,可以用此来import {last as surn} from './profile'

上面是逐一加载方式,接下来是整体加载方式
import * as pro from './profile' pro.last

默认导出

由于上面的介绍必须知道导入和导出的接口名才能继续。所以有了default

1
2
3
4
5
6
//导出文件 export.js
export deault fuction dd() {...}
//导入文件
impport custom from './export'
custom();

导出继承

export * from 'circle'从circle导出所有

加载

浏览器加载

1
2
<script src="path/to/myModule.js" defer></script>
<script src="path/to/myModule.js" async></script>

这俩个都是异步加载
deferasync的区别是:前者要等到整个页面正常渲染结束,才会执行;后者一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染。一句话,defer是“渲染完再执行”,async是“下载完就执行”。另外,如果有多个defer脚本,会按照它们在页面出现的顺序加载,而多个async脚本是不能保证加载顺序的。

es6模块和CommonJS模块差异

CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用
CommonJS 模块是运行时加载,ES6 模块是编译时输出接口

值拷贝:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// lib.js
var counter = 3;
function incCounter() {
counter++;
}
module.exports = {
counter: counter,
incCounter: incCounter,
};
// main.js
var mod = require('./lib');
console.log(mod.counter); // 3
mod.incCounter();
console.log(mod.counter); // 3
//除非写成函数,否则是值拷贝

Node加载

node有自己的commonjs模块格式,所以建议node和es6的加载方案各自分开

node使用es6加载时会依次寻找脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import './foo';
// 依次寻找
// ./foo.js
// ./foo/package.json
// ./foo/index.js
import 'baz';
// 依次寻找
// ./node_modules/baz.js
// ./node_modules/baz/package.json
// ./node_modules/baz/index.js
// 寻找上一级目录
// ../node_modules/baz.js
// ../node_modules/baz/package.json
// ../node_modules/baz/index.js
// 再上一级目录

循环加载

a脚本依赖b脚本,b脚本依赖c脚本,c脚本依赖a脚本;会造成递归加载使得程序无法进行

CommonJS的循环加载时,返回的是当前已经执行的部分的值,而不是代码全部执行后的值,两者可能会有差异。所以,输入变量的时候,必须非常小心。

ES6的循环加载时,返回的是引用,所以只有执行到了才会有数据。

代码风格

let

const优于let:提醒阅读/符合函数式编程思想/js编译器会进行优化

字符串

静态字符串一律使用单引号或反引号,不使用双引号。动态字符串使用反引号

1
2
3
4
5
6
7
8
9
10
11
// bad
const a = "foobar";
const b = 'foo' + a + 'bar';
// acceptable
const c = `foobar`;
// good
const a = 'foobar';
const b = `foo${a}bar`;
const c = 'foobar';

优先使用解构赋值

数组解构

1
2
3
4
5
6
7
8
const arr = [1, 2, 3, 4];
// bad
const first = arr[0];
const second = arr[1];
// good
const [first, second] = arr;

函数参数是对象,优先使用解构(注意当对参数无修改才如此使用,因为解构是赋值过程)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// bad
function getFullName(user) {
const firstName = user.firstName;
const lastName = user.lastName;
}
// good
function getFullName(obj) {
const { firstName, lastName } = obj;
}
// best
function getFullName({ firstName, lastName }) {//
}

对象定义不得随便增加新属性,如果要增加属性,要使用Object.assign

扩展运算符拷贝数组
const itemscopy = […items];

使用箭头函数取代bind,放弃self
由于箭头函数的this指向定义处,所以放弃以前的bindself写法

// //