JS 的语法,参考了廖雪峰的博客以及阮一峰的ES6
基础语法
数据类型和变量
Number
JS不区分整数浮点数,统一用Number表示:1
2
31.2345e3; // 科学计数法表示1.2345x1000,等同于1234.5
NaN; // NaN表示Not a Number,当无法计算结果时用NaN表示
Infinity; // Infinity表示无限大,当数值超过了JavaScript的Number所能表示的最大值时,就表示为Infinity
Number可以直接运算:1
2
3
4
5(1 + 2) * 5 / 2; // 7.5
2 / 0; // Infinity
0 / 0; // NaN
10 % 3; // 1 (取余)
10.5 % 3; // 1.5
字符串
以单引号'或双引号"括起来的任意文本.如'abc'
布尔值
用true和false表示。
比较运算符
JavaScript在设计时,有两种比较运算符:
第一种是==比较,它会自动转换数据类型再比较.
第二种是===比较,它不会自动转换数据类型,如果数据类型不一致,返回false,如果一致,再比较。
如果是比较类对象,就是比较两者的地址。如果地址不同,== 和 === 都返回 false。
NaN这个特殊的Number与所有其他值都不相等,包括它自己:1
2NaN == NaN; // false
NaN === NaN; // false
唯一能判断NaN的方法是通过isNaN()函数:1
isNaN(NaN); // true
浮点数在运算过程中会产生误差,因为计算机无法精确表示无限循环小数。要比较两个浮点数是否相等,只能计算它们之差的绝对值,看是否小于某个阈值:1
Math.abs(1 / 3 - (1 - 2 / 3)) < 0.0000001; // true
数组
JavaScript的数组可以包括任意数据类型。例如:1
[1, 2, 3.14, 'Hello', null, true];
另一种创建数组的方法是通过Array()函数实现:1
new Array(1, 2, 3); // 创建了数组[1, 2, 3]
然而,出于代码的可读性考虑,强烈建议直接使用[]。
数组的元素可以通过索引来访问。索引的起始值为0:1
2
3
4var arr = [1, 2, 3.14, 'Hello', null, true];
arr[0]; // 返回索引为0的元素,即1
arr[5]; // 返回索引为5的元素,即true
arr[6]; // 索引超出了范围,返回undefined
对象
JavaScript的对象是一组由键-值组成的无序集合,例如:1
2
3
4
5
6
7
8var person = {
name: 'Bob',
age: 20,
tags: ['js', 'web', 'mobile'],
city: 'Beijing',
hasCar: true,
zipcode: null
};
JavaScript对象的键都是字符串类型,值可以是任意数据类型。
要获取一个对象的属性,可以用一下两种方式:1
2
3
4
5
6
7person.name; // 'Bob'
person.zipcode; // null
function getName() {
return 'name'
}
person[getName()]
变量与常量
ES6 之前,变量用 var 修饰,但是会引起问题,包括作用域问题与变量提升问题。使用 let 代替。1
let b = 1; // 申明了变量$b,同时给$b赋值,此时$b的值为1
ES6 之后,常量用 const 修饰,表示不会再改变的值:
1 | const b = '234' |
Swift 中变量为
var常量为let略有不同。
字符串
模板字符串
要把多个字符串连接起来,可以用+号连接:1
2
3
4var name = '小明';
var age = 20;
var message = '你好, ' + name + ', 你今年' + age + '岁了!';
alert(message);
如果有很多变量需要连接,用+号就比较麻烦。ES6新增了一种模板字符串,表示方法和上面的多行字符串一样,但是它会自动替换字符串中的变量:1
2
3
4var name = '小明';
var age = 20;
var message = `你好, ${name}, 你今年${age}岁了!`;
alert(message);
注意这里不只是
${}外边的不再是单引号了,而是``。
在 Swift 中有类似的模板字符串:
\(要打印的对象),类似于这里的${}。不过 Swift 写起来比这个好简单好记多了。
操作字符串
字符串常见的操作如下:1
2
3
4var s = 'Hello, world!';
s.length; // 13
s[7]; // 'w'
s[12]; // '!'
字符串相当于是一个不能改变的数组。
indexOf
indexOf()会搜索指定字符串出现的位置:1
2
3var s = 'hello, world';
s.indexOf('world'); // 返回7
s.indexOf('World'); // 没有找到指定的子串,返回-1
substring
substring()返回指定索引区间的子串:1
2
3var s = 'hello, world'
s.substring(0, 5); // 从索引0开始到5(不包括5),返回'hello'
s.substring(7); // 从索引7开始到结束,返回'world'
数组
展开运算符
展开运算符使用 ... 将一个数组转为用逗号分隔的参数序列:
1 | console.log(1, ...[2, 3, 4], 5) |
length
要取得Array的长度,直接访问length属性:1
2var arr = [1, 2, 3.14, 'Hello', null, true];
arr.length; // 6
indexOf
与String类似,Array也可以通过indexOf()来搜索一个指定的元素的位置:1
2
3
4
5var arr = [10, 20, '30', 'xyz'];
arr.indexOf(10); // 元素10的索引为0
arr.indexOf(20); // 元素20的索引为1
arr.indexOf(30); // 元素30没有找到,返回-1
arr.indexOf('30'); // 元素'30'的索引为2
万能方法 splice
为数组添加和删除元素有很多方法,包括 push,pop,unshift,shift,slice,concat。这些都可以使用 splice 代替:
1 | var arr = ['Microsoft', 'Apple', 'Yahoo', 'AOL', 'Excite', 'Oracle']; |
第一个元素是起始索引(从0开始),第二个参数是删除元素个数。后面的参数是要添加的元素。
join
join 方法是一个非常实用的方法,用于当前Array的每个元素都用指定的字符串连接起来,然后返回连接后的字符串:
1 | var arr = ['A', 'B', 'C', 1, 2, 3]; |
数组的高阶函数
map
map()方法定义在JavaScript的Array中,我们调用Array的map()方法,传入我们自己的函数,就得到了一个新的Array作为结果:
1 | function pow(x) { |
map()将传入的函数一一作用在数组的每一个元素上。
reduce
Array的reduce()把一个函数作用在这个Array的[x1, x2, x3...]上,这个函数必须接收两个参数,reduce()把结果继续和序列的下一个元素做累积计算,其效果就是:
1 | [x1, x2, x3, x4].reduce(f) = f(f(f(x1, x2), x3), x4) |
比如对Array求和:
1 | var arr = [1, 3, 5, 7, 9]; |
filter
用于把Array的某些元素过滤掉,然后返回剩下的元素Array的filter()接收一个函数,把传入的函数依次作用于每个元素,然后根据返回值是true还是false决定保留还是丢弃该元素。
例如,在一个Array中,删掉偶数,只保留奇数,可以这么写:
1 | var arr = [1, 2, 4, 5, 6, 9, 10, 15]; |
sort
可以接收一个比较函数来实现自定义的排序
要按数字大小排序,我们可以这么写:
1 | var arr = [10, 20, 1, 2]; |
注意:sort()方法会直接对Array进行修改,它返回的结果仍是当前Array
对象
基本使用
现在 ES6 中有了 class,我觉得现在对象更像是结构体。
定义方式如下:
1 | var xiaohong = { |
获取分为两种形式,点语法和[]语法:1
2xiaohong.name; // '小红'
xiaohong['name']; // '小红'
由于JavaScript的对象是动态类型,你可以自由地给一个对象添加或删除属性:1
2
3
4
5
6
7
8
9
10
11var xiaoming = {
name: '小明'
};
xiaoming.age; // undefined
xiaoming.age = 18; // 新增一个age属性
xiaoming.age; // 18
delete xiaoming.age; // 删除age属性
xiaoming.age; // undefined
delete xiaoming['name']; // 删除name属性
xiaoming.name; // undefined
delete xiaoming.school; // 删除一个不存在的school属性也不会报错
如果我们要检测xiaoming是否拥有某一属性,可以用in操作符,自己的和继承的都会返回 true:1
2'name' in xiaoming; // true
'toString' in xiaoming; // true
要判断一个属性是否是xiaoming自身拥有的,而不是继承得到的,可以用hasOwnProperty()方法:1
2
3
4
5var xiaoming = {
name: '小明'
};
xiaoming.hasOwnProperty('name'); // true
xiaoming.hasOwnProperty('toString'); // false
对象的属性名
属性名为方法返回值
JS 中属性必须要是字符串型的,不过你也可以使用方法的返回值动态的设置属性名:
1 | const Birth = { |
属性的简写
ES6 中允许创建对象的时候,简写属性,直接以变量名称作为属性名。比如:
1 | let birth = '2000/01/01'; |
prototype
在 ES6 之前,没有类的概念,只有一个个函数,通过 prototype实现。
prototype 是函数的属性,这个属性是指向一个对象的引用,这个对象称为原型对象,原型对象包含函数实例共享的方法和属性,也就是说将函数用作构造函数调用(使用new操作符调用)的时候,新创建的对象会从原型对象上继承属性和方法。
所以,如果我们想要 new 出来的对象能够使用的属性或者方法,就将它们赋给 prototype。
另一方面,有一些属性和方法应该是属于类的,ES6 中用 static 标识,ES6 之前则直接将其赋值给函数,它会被加载到 constructor 中。constructor 本身也是 prototype 的一个默认属性。具体例子如下:

new Person() 后的实例对象,都可以获得 prototype 上的属性,但是其中一个对象改变其值的时候,另一个对象相应属性值不变。
prototype中的属性能随着原型链被继承下去。
Object.assign()
这个方法是非常有用的一个方法。用于为对象添加属性,方法,克隆对象,合并对象。第一个是目标对象,我们可以使用空对象,后面对象的属性会覆盖前面对象的属性:
1 | const target = {}; |
这个和闭包一样,也是指针传递,生成的 target 对象或者源 source 对象,两者任一改动都会触发另外对象的改动。
除了方法是基本类型是值传递,对象类型是引用传递。其他都是指针传递。
(网上纠结的什么传递地址也是值传递,就当是扯淡吧,理解意思就行)
Object.entries()
返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历属性的键值对数组:
1 | const obj = { foo: 'bar', baz: 42 }; |
配合 for...of... 可以拿到所有键值。
可以用它来将对象转化为 Map
JSON.stringify()
一个对象你在打印log的时候,可能会给你返回 [object object]。此时,你需要将对象展开,可以使用 JSON.stringify() 方法。
Map和Set
JS中默认对象表达方式{}可以视为其他语言中的Map或Dictionary的数据结构,即一组键值对。
但是JavaScript的对象有个小问题,就是键必须是字符串。但实际上Number或者其他数据类型作为键也是非常合理的。
为了解决这个问题,最新的ES6规范引入了新的数据类型Map。
Map
Map 可以接受一个数组,数组元素是键值对的数组:
1 | var m = new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]); |
这个结构是不是很熟悉?Object.entries() 返回的就是这样的数组中包含了键值对数组的形式。所以说,可以用 Object.entries() 创建 Map。因为直接把返回值作为参数传入 Map 即可。
对象可以变为 Map,但是 Map 不一定能变为对象。所以创建 Map 的时候的入参不能以对象的形式。
方法
size属性返回 Map 结构的成员总数,类似数组的lengthset(key, value):设置键名key对应的键值为value,然后返回整个 Map 结构get(key): 读取key对应的键值,如果找不到key,返回undefined。has(key):返回一个布尔值,表示某个键是否在当前 Map 对象之中。delete(key):delete方法删除某个键,返回true。如果删除失败,返回false。clear():清除所有成员,没有返回值。entries():返回所有成员的遍历器。配合for...of...
WeakMap
WeakMap 的键是对象类型。如果你要往对象上添加数据,又不想干扰垃圾回收机制,就可以使用 WeakMap。
注意弱引用的是键,而不是值
Set
重复元素在Set中自动被过滤:1
2var s = new Set([1, 2, 3, 3, '3']);
s; // Set {1, 2, 3, "3"}
注意数字3和字符串'3'是不同的元素。
方法
add(value):添加某个值。delete(value):删除某个值,返回一个布尔值has(value):返回一个布尔值,表示该值是否为Set的成员。clear():清除所有成员。
WeakSet
WeakSet 中只能存对象。并且都是弱引用。
WeakSet 不能遍历。因为所有对象都是弱引用,随时可能消失。
iterable
ES6标准引入了新的iterable类型,Array、Map和Set都属于iterable类型。iterable 类型其实就是
具有iterable类型的集合可以通过新的for ... of循环来遍历。用法如下:
1 | var a = ['A', 'B', 'C']; |
for ... of循环和for ... in循环有何区别?for ... in实际上是对象的属性名称(注意,是键,不是值)。不要用在Array,Set,Map上,会出现奇怪的问题。for ... of循环则完全修复了这些问题,它只循环得到集合内该出现的元素(注意,是值,不是键),例如:1
2
3
4
5
6
7
8
9
10
11
12
13
14var a = ['A', 'B', 'C'];
a.name = 'Hello';
a['4'] = 'D';
console.log(a);
for (var num of a){
console.log(num)
}
输出:
[ 'A', 'B', 'C', , 'D', name: 'Hello' ]
A
B
C
undefined
D
name由于不是正常Array该有的,所以在for ... of循环时,其对应的值不会被输出。
变量的解构赋值
有两种解构赋值,分别为数组的解构赋值和对象的解构赋值
数组的解构赋值
多出的部分为 undefined,可以使用展开运算符。
1 | let [a, b, c] = [1, 2]; // a = 1, b = 2, c = undefined |
解构赋值允许指定默认值,当对应的值为 undefined 时,等于默认值:
1 | let [x, y = 'b'] = ['a']; // x='a', y='b' |
对象的解构赋值
1 | let { foo, bar } = { foo: "aaa", bar: "bbb" }; |
想要改变变量的名字也是可以的,上面其实等效于:
1 | let { foo: foo, bar: bar } = { foo: "aaa", bar: "bbb" }; |
所以如果我们要改变变量名,可以这样:
1 | let { foo: param1, bar: param2 } = { foo: "aaa", bar: "bbb" }; |
对象也是可以指定默认值的:
1 | var {x, y: z = 5} = {x: 1}; |
函数
函数定义和调用
定义函数
方式一:1
2
3
4
5
6
7function abs(x) {
if (x >= 0) {
return x;
} else {
return -x;
}
}
由于JavaScript的函数也是一个对象,上述定义的abs()函数实际上是一个函数对象,而函数名abs可以视为指向该函数的变量。
方式二:1
2
3
4
5
6
7var abs = function (x) {
if (x >= 0) {
return x;
} else {
return -x;
}
};
在这种方式下,function (x) { ... }是一个匿名函数,它没有函数名。但是,这个匿名函数赋值给了变量abs,所以,通过变量abs就可以调用该函数。
上述两种定义完全等价,注意第二种方式按照完整语法需要在函数体末尾加一个;,表示赋值语句结束。
Swift 中使用 func 定义函数
调用函数
由于JavaScript允许传入任意个参数而不影响调用,因此传入的参数比定义的参数多也没有问题,虽然函数内部并不需要这些参数:1
2abs(10, 'blablabla'); // 返回10
abs(-9, 'haha', 'hehe', null); // 返回9
传入的参数比定义的少也没有问题:1
abs(); // 返回NaN
此时abs(x)函数的参数x将收到undefined,计算结果为NaN。
要避免收到undefined,可以对参数进行检查:1
2
3
4
5
6
7
8
9
10function abs(x) {
if ((typeof x) !== 'number') {
throw 'Not a number';
}
if (x >= 0) {
return x;
} else {
return -x;
}
}
rest参数
由于JavaScript函数允许接收任意个参数,ES6标准引入了rest参数,可以将剩余的参数放在rest中,rest 是个数组(不是一定要叫 rest,任何名称都可以):1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17function foo(a, b, ...rest) {
console.log('a = ' + a);
console.log('b = ' + b);
console.log(rest);
}
foo(1, 2, 3, 4, 5);
// 结果:
// a = 1
// b = 2
// Array [ 3, 4, 5 ]
foo(1);
// 结果:
// a = 1
// b = undefined
// Array []
其实相当于把 rest 展开,然后和入参对应
如果传入的参数连正常定义的参数都没填满,rest参数会接收一个空数组(注意不是undefined)。
参数默认值
ES6 给 js 的函数提供了默认值:
1 | function Point(x = 0, y = 0) { |
带默认值的参数需要写在末尾。
特性
name 属性
函数的 name属性,返回函数的函数名:
1 | function foo() {} |
箭头函数
ES6标准新增了一种新的函数:Arrow Function(箭头函数)。
1 | x => x*x |
相当于一个输入为x输出为x*x的匿名函数
1 | function (x) { |
如果参数不是一个,就需要用括号()括起来:
1 | // 两个参数: |
语法糖,类似于python中的lambda函数
当然,箭头函数还是有点用处的,由于是es6的新特性,箭头函数内部的this是词法作用域,由上下文确定。
试做比较:
1 | //由于JavaScript函数对this绑定的错误处理,下面的例子无法得到预期结果 |
如果使用箭头函数,以前 var that = this; 以及 .apply() 和 .call() 这种写法就不需要了。
箭头函数并不创建它自身执行的上下文,使得 this 取决于它在定义时的外部函数。
箭头函数用在嵌套函数里,不能作为某一个属性的值,比如用成这样:
1
2
3
4
5
6
7 > var obj = {
> birth: 1990,
> getAge: () => {
> var b = this.birth; // this 是 undefined
> }
> }
>
这里并没有外部函数,所以箭头函数里的 this 永远是 undefined
装饰器
利用apply(),我们可以动态改变函数的行为。
JavaScript的所有对象都是动态的,即使内置的函数,我们也可以重新指向新的函数。
现在假定我们想统计一下代码一共调用了多少次parseInt(),可以把所有的调用都找出来,然后手动加上count += 1,不过这样做太傻了。最佳方案是用我们自己的函数替换掉默认的parseInt():1
2
3
4
5
6
7
8
9
10
11
12
13var count = 0;
var oldParseInt = parseInt; // 保存原函数
window.parseInt = function () {
count += 1;
return oldParseInt.apply(null, arguments); // 调用原函数
};
// 测试:
parseInt('10');
parseInt('20');
parseInt('30');
count; // 3
apply() 接收两个参数,第一个参数表示调用的对象,第二个参数是入参数组。此处的 arguments 表示的是外部 function 的入参,是 js 提供的一个参数。
闭包
这是因为 JS 中的闭包对外部变量都是指针传递,而不是引用传递。。
iOS 中的闭包都是引用传递,外部变量相当于是以参数的形式传入的,也就是说,闭包内改变外部变量的值,外部变量不会变化。
js 中的闭包,相当于外部变量的地址都获得了。闭包内改变外部变量的值,外部变量的值相应改变。
Promise
ES6 提供了原生的 Promise 对象。可以将异步操作以同步的形式表达出来。
基本用法
1 | const promise = new Promise(function(resolve, reject) { |
如上所示,Promise 接受一个函数,入参为成功和失败回调,函数内执行异步操作,在必要时执行成功和失败回调。Promise 不需要返回值。
Promise 实例生成后,可以使用 then 方法指定 resolve 和 reject 的回调函数:
1 | promise.then(function(value) { |
Promise 的大致原理是通过 then 在 Promise 里注册回调,然后创建 Promise 时传入的函数会调用注册的回调函数。
注意,创建 Promise 的时候,函数就被执行了。但是执行到 resolve 或者 reject 方法时并不立刻执行,而是等到最后:
1 | new Promise((resolve, reject) => { |
这是因为,Promise 将 then 注册的回调函数都包裹上了 setTimeout(resolved,0),将回调函数放置到 JS 任务队列末尾。
链式调用then()
每一个 promise 就是一个异步调用。多个异步调用串行可以使用链式 .then() 的方式。
这个时候 then 方法向调用的 Promise 注入成功回调的时候需要返回一个新的 Promise
1 | new Promise((resolve, reject) => { |
并不是说
.then返回一个 promise,使用者就必须要自己创建一个 promise。如果.then返回的是某个值someObject,那么会被默认包装为一个 promise,并且立刻调用resolve(someObject),即执行后一个.then的而方法。
###失败调用 catch()
catch() 方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数:
1 | getJSON('/posts.json').then(function(posts) { |
注意,Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。也就是说,本例中,无论是 getJSON 还是 .then() 生成的 Promise 产生的错误都会被 catch() 捕获。
一般来说不要在 then() 方法中定义失败回调。总是使用 catch() 方法。
catch() 方法返回的还是 Promise 对象,会接着运行后面的 then() 方法。
永远执行 finally()
1 | promise |
上面代码中,不管promise最后的状态,在执行完then或catch指定的回调函数以后,都会执行finally方法指定的回调函数。
合并多个Promise all()
Promise.all方法接受一个 Promise 数组,用于将多个 Promise 实例,包装成一个新的 Promise 实例:
1 | const p = Promise.all([p1, p2, p3]); |
p的状态由p1、p2、p3决定,分成两种情况。
(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
合并多个Promise race()
Promise.race方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例:
1 | const p = Promise.race([p1, p2, p3]); |
上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
async 函数
async 函数式 Generator 函数的语法糖.
async函数返回一个 Promise 对象,可以使用then方法添加回调函数。
当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句:
1 | async function getStockPriceByName(name) { |
上面代码是一个获取股票报价的函数,函数前面的async关键字,表明该函数内部有异步操作。调用该函数时,会立即返回一个Promise对象不是说 stockPrice 是一个 Promise 而是说 async 修饰的这个方法最终返回一个 Promise,stockPrice 只是作为 resolve 的 input 而已。
如果确实希望多个请求并发执行,可以使用Promise.all方法:
1 | async function dbFuc(db) { |
- async 相当于告诉外界,这个方法会返回一个 promise
- await 必须在 async 中才能使用
- await 后面的方法会延迟到 await 方法执行完后执行,相当于 then 操作
- await 的错误捕获需要使用 try…catch 的方式
Proxy
Proxy 就如字面所说的,是 ES6 提供的代理模式的封装。ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例:
1 | var proxy = new Proxy(target, handler); |
第一个参数是被代理对象,第二个参数是要拦截的行为对象。当调用代理对象的被拦截行为时,触发代理方法。常用的拦截行为有以下几种:
get(),set()
1 | var obj = new Proxy({}, { |
其中,代理对象是 obj,被代理的对象是 {} 空对象,拦截的行为是 get,set 方法。也就是说,任何对于 obj 的读取写入操作都将执行代理方法。
get,set 方法的参数也很好理解。target 表示被代理对象,key 表示要进行读写操作的属性,value 就是写操作的值。receiver 就是代理对象 obj。(先不用关 Reflect)。
如果handler没有设置任何拦截,那就等同于直接通向原对象:
1 | var target = {}; |
apply()
apply 方法拦截函数的调用、call和 apply 操作。可以接受三个参数:目标对象(函数)、目标对象上下文对象(this)、目标对象的参数数组。
1 | var twice = { |
注意这里的 ...arguments 表示的是 target,context以及args的全部。
construct()
construct 方法用于拦截 new 命令。接受两个参数:目标对象(类对象),构建函数的参数对象。
1 | class A { |
注意,拦截的是 new 命令,不是拦截的 constructor 命令,所以,拦截方法要返回一个 target 创建的新对象。
class
基本用法
创建和使用
新的关键字class从ES6开始正式被引入到JavaScript中。class的目的就是让定义类更简单。
如果用新的class关键字来编写Student,可以这样写:1
2
3
4
5
6
7
8
9
10
11
12class Student {
constructor(name) {
this.name = name;
}
hello() {
alert('Hello, ' + this.name + '!');
}
}
var xiaoming = new Student('小明');
xiaoming.hello();
name 属性
上面说到,函数有 name 属性,可以返回函数的名字。ES6 的 class 其实就是 ES5 函数的包装。所以 Class 也包含 name 属性:
1 | class Point {} |
get set 方法
在“类”的内部可以使用get和set关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为:
1 | class MyClass { |
static 方法
如果在一个方法前,加上static关键字,表示类方法:
1 | class Foo { |
ES6 明确规定,Class 内部只有静态方法,没有静态属性。
new.target 属性
ES6 为new命令引入了一个new.target属性,该属性一般用在构造函数之中,返回new命令作用于的那个构造函数。需要注意的是,子类继承父类时,new.target会返回子类。利用这个特点,可以写出不能独立使用、必须继承后才能使用的接口:
1 | class Shape { |
class继承
用class定义对象的另一个巨大的好处是继承更方便了,直接通过extends来实现:1
2
3
4
5
6
7
8
9
10class PrimaryStudent extends Student {
constructor(name, grade) {
super(name); // 记得用super调用父类的构造方法!
this.grade = grade;
}
myGrade() {
alert('I am at grade ' + this.grade);
}
}
通过super(name)来调用父类的构造函数。PrimaryStudent已经自动获得了父类Student的hello方法,我们又在子类中定义了新的myGrade方法。
export 与 import
基本用法
1 | export let name = 'zachary' |
注意,不能直接写export name,要写也要写成 export {name}。为什么呢?其实就相当于给 export 的对象起一个名字,export name 就相当于 export 'zachary' ,这样别人就无法知道如何引入了,所以就需要起一个名字:export let name = 'zachary' .引入方式如下:
1 | import {name, getName} from './myName' |
import 和 require 都不需要写 .js 后缀!!!
重命名 as
导出导入的名字有时候可能需要改变,所以提供了 as 这个关键字:
1 | export {name as yourName, getName as getYourName} |
导出的时候用 yourName 作为内部 name 的别名。导入的时候用 myName 作为 youName 的别名。
default
有时候,我们不需要知道导出的叫什么,可以使用 default :
1 | export default name |
其实就是 as 的语法糖。注意,import 的时候就不需要大括号了。
ES6 和 CommonJs 在引用方式的差异
CommonJS的一个模块,就是一个脚本文件。require命令第一次执行的时候,加载该脚本,然后执行整个脚本
ES6模块的运行机制与CommonJS不一样,它遇到模块加载命令import时,直接执行相应的文件
require的好处是可以在代码中使用,而import只能在头部。这样require就可以在执行到当前代码的时候,根据代码的上下文动态的引入想要的模块。而
import是强制静态化的
混用产生的问题
export 和 module.exports 都是导出模块,但是最好不要在一个文件中一起使用。混用会产生一定的问题。
首先,ES6 的 export 在 webpack 打包后都会变成 CommonJS 的语法。因此 export 最终会变为 CommonJS 的 exports。当 exports 和 module.exports 同时存在的时候,最终导出的将会是 module.exports 的对象。exports 导出的对象会被覆盖。
这个结论非常重要。牢记这个结论在混用的时候就能明白为什么很多时候 import 的时候是 undefined 的了。
归类
… 的使用场景
… 一般用在数组前,把数组转为用逗号隔开的一个个值。可以正用逆用都是等价的:
1 | ...[a, b, c] => a, b, c |
一般用于:
- 将数组拆开作为入参
- 将入参合并为数组
- 数组解构赋值
1 | // 1. 做入参 |
另外,在 JSX 中,... 可以把对象中的键值对拿出来,以等号连接。
对象和 Map
对象和 Map 初步的不同就在于键是否是字符串。
创建与增减键值对
对象:
1 | let obj = {} |
Map:
1 | let map = new Map() |
遍历
对象:
1 | Object.entries(obj) => [['haha', '123'], ['lala', '234']] |
Map:
1 | map.entries() => [['haha', '123'], ['lala', '234']] |
没有看的
- symbol 部分
- reflect 部分(感觉可以直接调用,不需要使用 reflect)
- generator 部分(习惯于用promise的回调写法,不习惯这种写法)
- async 部分(同 generator)
- port