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 结构的成员总数,类似数组的length
set(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