个人回顾整理学习。
关于变量
ES5
全部作用域变量和函数作用域变量
“变量提升” 程序进入某一个函数时,会先将函数内的变量声明放在函数开头,不会提升变量的赋值。
1 | console.log(foo); // 输出:undefined |
ES6
变量声明let和const
块级作用域
没有“变量提升”,必须先声明后使用
let 声明的变量不能重复
const 定义的是只读变量,需在声明时即赋值,变量名不能重复
1 | console.log(bar); // 报错 |
块级作用域的应用场景
内层变量可能会覆盖外层变量
1
2
3
4
5
6
7
8
9
10 > var tmp = new Date();
> function f() {
> console.log(tmp);
> if (false) {
> var tmp = 'hello world';
> }
> }
>
> f(); // undefined
>
用来计数的循环变量泄露为全局变量
1
2
3
4
5
6
7
8 > var s = 'hello';
>
> for (var i = 0; i < s.length; i++) {
> console.log(s[i]);
> }
>
> console.log(i); // 5
>
不必要立即执行函数表达式
1
2
3
4
5
6
7
8
9
10
11
12 > // IIFE 写法
> (function () {
> var tmp = ...;
> ...
> }());
>
> // 块级作用域写法
> {
> let tmp = ...;
> ...
> }
>
应尽量避免在块级作用域中声明函数
声明变量的方式
var function let const import class
变量的结构赋值
数组
ES5
1 | $ let a = 1; |
ES6
1 | $ let [a, b, c] = [1, 2, 3]; |
本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。
1 | $ 嵌套数组结构 |
不完全结构
1 | let [x, y] = [1, 2, 3]; |
对象
1 | let { foo, bar } = { foo: "aaa", bar: "bbb" }; |
等价于
1 | let { foo: foo, bar: bar } = { foo: "aaa", bar: "bbb" }; |
字符串
1 | const [a, b, c, d, e] = 'hello'; |
length 属性
1 | let {length : len} = 'hello'; |
关于数组
扩展运算符
… 数组的展开运算
1 | var arr = [1,2,3,4,5]; |
函数批量传参
1 | fun5(...[11,22,33,44,55,66]) |
替代函数apply方法
应用Math.max
方法,简化求出一个数组最大元素的写法。
1 | // ES5 的写法 |
push
函数
1 | // ES5的 写法 |
应用
复制数组
数组是复合数据类型,直接复制的话只是复制了指向底层数据结构的指针,而不是克隆一个新的数组。
1 | const a1 = [1, 2]; |
1 | ES5 变通方法克隆数组 |
1 | const a1 = [1, 2]; |
1 | ES6 扩展运算符简单写法 |
1 | const a1 = [1, 2]; |
合并数组
1 | const arr1 = ['a', 'b']; |
注意
这两种方法合成的新数组都是对原数组成员的引用,属于浅拷贝
,如果原数组成员改变,则会反映到新合成的数组上。
与结构赋值结合
1 | const [first, ...rest] = [1, 2, 3, 4, 5]; |
注意
采用此方法,数组扩展运算符只能放在参数的最后一位,否则,报错。
字符串
扩展运算符可以将字符串转为数组
1 | [...'hello'] |
Array.form()
Array.from
用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象。
1 | let arrayLike = { |
实际应用中,常见的类似数组的对象是 DOM 操作返回的 NodeList 集合,以及函数内部的arguments
对象。Array.from
都可以将它们转为真正的数组。
1 | // NodeList对象 |
字符串扩展
字符串 for…of 遍历
1 | for (let codePoint of 'foo') { |
ES5
indexOf 用来确定一个字符串是否包含在另一个字符串中
ES6
Includes() 返回布尔值 是否找到参数字符串
startsWith() 返回布尔值 表示参数是否在原字符串头部
endsWith() 返回布尔值 表示参数是否在原字符串尾部
备注
这三个方法都支持第二个参数,表示开始搜索的位置。
使用第二个参数n
时,endsWith
的行为与其他两个方法有所不同。它针对前n
个字符,而其他两个方法针对从第n
个位置直到字符串结束
repeat()
返回一个新字符串,表示将原字符串重复
n
次
1 | 'x'.repeat(3) // "xxx" |
参数如果是小数,会被取整
1 | 'na'.repeat(2.9) // "nana" |
备注
- 如果
repeat
的参数是负数或者Infinity
,会报错 - 如果参数是 0 到-1 之间的小数,则等同于 0
- 参数
NaN
等同于 0
padStart(), padEnd()
字符串补全长度的功能,如果某个字符串不够指定长度,会在头部或尾部补全。
padStart()
用于头部补全,padEnd()
用于尾部补全。
1 | 'x'.padStart(5, 'ab') // 'ababx' |
模板字符串
ES5
1 | $('#result').append( |
ES6
模板字符串是增强版的字符串,反引号(`)标识。可以当做普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。
1 | $('#result').append(` |
模板字符串中嵌入变量,需要将变量名写在
${}
之中。
1 | function authorize(user, action) { |
大括号内部可以放入任意的 JavaScript 表达式,可以进行运算,以及引用对象属性
1 | let x = 1; |
对象扩展
属性简洁表示
ES6 允许直接写入变量和函数。
1 | const foo = 'bar'; |
方法简写
1 | const o = { |
属性名表达式
除了用标识符作为属性名,ES6 增加了使用表达式作为对象的属性名,需要放在[]
中。
1 | // 方法一 |
1 | let lastWord = 'last word'; |
Object.is()
ES5 比较两个值是否相等:==
(相等运算符)和 ===
(严格相等运算符)。
ES6 Object.is()
与===
行为基本一致。
1 | Object.is('foo', 'foo') |
两个不同之处
1 | +0 === -0 //true |
Object.assign()
Object.assign
方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
1 | const target = { a: 1 }; |
注意
- 浅拷贝(如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用)
- 同名属性替换不是添加
- 处理数组是会把数组视为对象
- 如果复制的是一个函数,则函数取值后再进行复制
Object.assign() 应用
为对象添加属性
将x
属性和y
属性添加到Point
类的对象实例。
1 | class Point { |
为对象添加方法
使用assign
方法添加到SomeClass.prototype
之中。
1 | Object.assign(SomeClass.prototype, { |
克隆对象
1 | function clone(origin) { |
合并多个对象
1 | const merge = |
属性的遍历
(1) for…in
循环遍历对象自身的和继承的可枚举属性(不含Symbol属性)
(2)Object.keys(obj)
返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。
(3)Object.getOwnPropertyNames(obj)
返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。
(4)Object.getOwnPropertySymbols(obj)
返回一个数组,包含对象自身的所有 Symbol 属性的键名。
(5)Reflect.ownKeys(obj)
返回一个数组,包含对象自身的所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。
扩展运算符
对象的扩展运算符(...
)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。
1 | let z = { a: 3, b: 4 }; |
1 | let aClone = { ...a }; |
合并对象
1 | let ab = { ...a, ...b }; |
Symbol
ES6 引入一种新的原始数据类型Symbol
,表示独一无二的值。
对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol 类型。凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。
1 | let s = Symbol(); |
Set和Map数据结构
Set
含义和用法
Set 数据结构类似数组,但是它的值都是唯一的,没有重复的值。
1 | const s = new Set(); |
Set 可以接受一个数组参数,用来初始化。
1 | // 例一 |
去除数组重复成员
1 | // 去除数组的重复成员 |
Set 实例属性和方法
属性
Set.prototype.constructor
:构造函数,默认就是Set
函数。Set.prototype.size
:返回Set
实例的成员总数。
操作方法
add(value)
:添加某个值,返回 Set 结构本身。delete(value)
:删除某个值,返回一个布尔值,表示删除是否成功。has(value)
:返回一个布尔值,表示该值是否为Set
的成员。clear()
:清除所有成员,没有返回值。
判断是否包括一个键,Object
结构和Set
结构的写法。
1 | // 对象的写法 |
Array.from
方法可以将 Set 结构转为数组。
1 | const items = new Set([1, 2, 3, 4, 5]); |
数组去重
1 | function dedupe(array) { |
遍历方法
keys()
:返回键名的遍历器values()
:返回键值的遍历器entries()
:返回键值对的遍历器forEach()
:使用回调函数遍历每个成员
Map
含义及用法
ES6 提供Map结构,类似于对象,对象本质上是键值对的集合,Object 结构只能字符串当做对象的键,而Map结构“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应。
1 | const m = new Map(); |
实例属性及操作方法
(1)size 属性
(2)set(key, value)
set
方法设置键名key
对应的键值为value
,然后返回整个 Map 结构。如果key
已经有值,则键值会被更新,否则就新生成该键。set
方法返回的是当前的Map
对象,因此可以采用链式写法。
1 | let map = new Map() |
(3)get(key)
get
方法读取key
对应的键值,如果找不到key
,返回undefined
。
(4)has(key)
has
方法返回一个布尔值,表示某个键是否在当前 Map 对象之中。
(5)delete(key)
delete
方法删除某个键,返回true
。如果删除失败,返回false
。
(6)clear()
clear
方法清除所有成员,没有返回值。
数值扩展
二进制和八进制表示法
分别用前缀
0b
(或0B
)和0o
(或0O
)表示二进制和八进制
Number.isFinite(), Number.isNaN()
Number.isFinite()
用来检查一个数值是否为有限的(finite),即不是Infinity
。
Number.isNaN()
用来检查一个值是否为NaN
。这两个新方法只对数值有效,
Number.isFinite()
对于非数值一律返回false
,Number.isNaN()
只有对于NaN
才返回true
,非NaN
一律返回false
。
Number.parseInt(), Number.parseFloat()
和全局方法相比行为完全不变,是为了减少全局方法的使用,使语言逐步模块化。
Number.isInteger()
用来判断一个数值是否为整数,如果不是数值返回false
1 | Number.isInteger(25) // true |
Math 对象扩展
Math.trunc()
Math.trunc
方法用于去除一个数的小数部分,返回整数部分。
注意
- 对于非数值,
Math.trunc
内部使用Number
方法将其先转为数值 - 对于空值和无法截取整数的值,返回
NaN
Math.sign()
Math.sign
方法用来判断一个数到底是正数、负数、还是零。对于非数值,会先将其转换为数值。
- 参数为正数,返回
+1
- 参数为负数,返回
-1
- 参数为 0,返回
0
- 参数为-0,返回
-0
- 其他值,返回
NaN
关于函数
函数默认值
ES6
给形参函数设置默认值,构造函数同理
1 | function fun2(a=1,b=2){ |
箭头函数
1 | //正常函数 |
this知识
函数体外this指window对象
函数体内,谁调用函数this就指向谁
构造函数,this指的是新创建的对象
html标签中,this指的是这个标签元素
ES6 对于箭头函数,this指向需要根据在哪里创建和当前作用域确定。
- 函数体内的
this
对象,就是定义时所在的对象,而不是使用时所在的对象。- 不可以当作构造函数,也就是说,不可以使用
new
命令,否则会抛出一个错误。- 不可以使用
arguments
对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。- 不可以使用
yield
命令,因此箭头函数不能用作 Generator 函数。
不适用场合
- 定义函数的方法,且该方法内部包括
this
1 | const cat = { |
上面代码中,
cat.jumps()
方法是一个箭头函数,这是错误的。调用cat.jumps()
时,如果是普通函数,该方法内部的this
指向cat
;如果写成上面那样的箭头函数,使得this
指向全局对象,因此不会得到预期结果。
- 需要动态
this
的时候,也不应使用箭头函数
1 | var button = document.getElementById('press'); |
上面代码运行时,点击按钮会报错,因为
button
的监听函数是一个箭头函数,导致里面的this
就是全局对象。如果改成普通函数,this
就会动态指向被点击的按钮对象。
双冒号运算符
函数绑定运算符(
::
),用来取代call
、apply
、bind
调用。冒号左边是对象,右边是一个函数。双冒号运算符会自动将左边对象作为上下文环境(this对象)绑定到右边函数。
1 | foo::bar; |
如果双冒号左边为空,右边是一个对象的方法,则等于将该方法绑定在该对象上面。
1 | var method = obj::obj.foo; |
如果双冒号运算符的运算结果,还是一个对象,就可以采用链式写法。
尾部调用
尾调用(Tail Call)是函数式编程的一个重要概念,本身非常简单,一句话就能说清楚,就是指某个函数的最后一步是调用另一个函数。
1 | function f(x){ |
尾递归
函数调用自身,称为递归。如果尾调用自身,就称为尾递归。
1 | 递归 |
1 | function factorial(n) { |
上面代码是一个阶乘函数,计算
n
的阶乘,最多需要保存n
个调用记录,复杂度 O(n)
1 | 尾递归 |
1 | function factorial(n, total) { |
如果改写成尾递归,只保留一个调用记录,复杂度 O(1) 。
Proxy
🏠简介
Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。
1 | var proxy = new Proxy(target, handler); |
其中,new Proxy()
表示生成一个Proxy
实例,target
参数表示所要拦截的目标对象,handler
参数也是一个对象,用来定制拦截行为。
拦截读取属性
1 | var proxy = new Proxy({}, { |
注意,要使得Proxy
起作用,必须针对Proxy
实例(上例是proxy
对象)进行操作,而不是针对目标对象(上例是空对象)进行操作。
如果handler
没有设置任何拦截,那就等同于直接通向原对象。
1 | var target = {}; |
Reflect
🏠简介
ES6 为了操作对象提供的新的API。
(1) 将Object
对象的一些明显属于语言内部的方法(比如Object.defineProperty
),放到Reflect
对象上。
(2) 修改某些Object
方法的返回结果,让其变得更合理。
(3) 让Object
操作都变成函数行为。某些Object
操作是命令式,比如name in obj
和delete obj[name]
,而Reflect.has(obj, name)
和Reflect.deleteProperty(obj, name)
让它们变成了函数行为。
(4)Reflect
对象的方法与Proxy
对象的方法一一对应,只要是Proxy
对象的方法,就能在Reflect
对象上找到对应的方法。这就让Proxy
对象可以方便地调用对应的Reflect
方法,完成默认行为,作为修改行为的基础。
静态方法
Reflect.get(target, name, receiver)
Reflect.get
方法查找并返回target
对象的name
属性,如果没有该属性,则返回undefined
。
如果name
属性部署了读取函数(getter),则读取函数的this
绑定receiver
。
1 | var myObject = { |
Reflect.set(target, name, value, receiver)
Reflect.set
方法设置target
对象的name
属性等于value
。
如果name
属性设置了赋值函数,则赋值函数的this
绑定receiver
。
1 | var myObject = { |
Reflect.has(obj, name)
Reflect.has
方法对应name in obj
里面的in
运算符。
1 | var myObject = { |
Reflect.deleteProperty(obj, name)
Reflect.deleteProperty
方法等同于delete obj[name]
,用于删除对象的属性。
1 | const myObj = { foo: 'bar' }; |
Reflect.construct(targer, args)
Reflect.construct
方法等同于new target(...args)
,这提供了一种不使用new
,来调用构造函数的方法。
1 | function Greeting(name) { |
应用
使用Proxy实现观察者模式。
观察者模式(Observer mode)指的是函数自动观察数据对象,一旦对象有变化,函数就会自动执行。
1 | const person = observable({ |
Promise 对象
🏠简介
Promise 是异步编程的一种解决方案。
- Promise 对象的状态不受外界影响。
- 代表异步操作,有三种状态:pending(进行中),fulfilled(已成功),rejected(已失败)。
- 一旦状态改变,就不会再变,任何时候都是这个结果。有两种可能的状态改变:由pending变为fulfilled,由pending变为rejected。
优势:
- 表达方面,将异步操作用同步操作的形式表达出来,避免了层层嵌套。
- 提供统一的接口,使异步操作更加方便。
缺点:
- Promise无法取消,一旦创建,立即执行。
- 如果不设置回调函数,Promise内部报错,不会反映到外部。
- 处于pending状态时无法知道进展到哪个阶段,是刚刚开始还是马上结束。
基本用法
ES6 规定,Promise
对象是一个构造函数,用来生成Promise
实例。
1 | const promise = new Promise(function(resolve, reject) { |
Promise
构造函数接受一个函数作为参数,该函数的两个参数分别是resolve
和reject
。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。
Promise
实例生成以后,可以用then
方法分别指定resolved
状态和rejected
状态的回调函数。
then
方法可以接受两个回调函数作为参数。第一个回调函数是Promise
对象的状态变为resolved
时调用,第二个回调函数是Promise
对象的状态变为rejected
时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise
对象传出的值作为参数。
1 | promise.then(function(value) { |
链式调用
1 | promise |
不管promise
最后的状态,在执行完then
或catch
指定的回调函数以后,都会执行finally
方法指定的回调函数。
Promise.all()
Promise.all
方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
1 | const p = Promise.all([p1, p2, p3]); |
其中p的状态由p1 p2决定
- 只有
p1
、p2
、p3
的状态都变成fulfilled
,p
的状态才会变成fulfilled
,此时p1
、p2
、p3
的返回值组成一个数组,传递给p
的回调函数。 - 只要
p1
、p2
、p3
之中有一个被rejected
,p
的状态就变成rejected
,此时第一个被reject
的实例的返回值,会传递给p
的回调函数。
1 | // 生成一个Promise对象的数组 |
Promise.resolve()
Promise.resolve
可以将现有对象转为 Promise 对象。
1 | Promise.resolve('foo') |
- 参数是Promise实例,返回这个实例
- 参数是一个thenable对象(具有then方法的对象),
Promise.resolve
将其转化为Promise对象,并立即执行thenable对象的then方法 - 参数不是具有then方法的对象,或根本就不是对象,返回一个新的Promise对象,状态为resolved
- 不带参数,直接返回一个
resolved
状态的 Promise 对象
Promise.reject()
Promise.reject(reason)
方法也会返回一个新的 Promise 实例,该实例的状态为rejected
。
1 | const p = Promise.reject('出错了'); |
应用
加载图片
1 | const preloadImage = function (path) { |
Generator函数与Promise结合
Iterator、for…of 循环
表示”集合“的数据结构:Array,Object,Map,Set。需要一个统一的接口机制,开处理不同的数据结构。
遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。
Iterator 作用
- 为各种数据结构提供统一的、简便的访问接口
- 使得数据结构的成员能够按照某种次序排列
- 创造一种新的遍历命令,for…of循环
模拟next方法
1 | var it = makeIterator(['a', 'b']); |
默认 Iterator 接口
一种数据结构只要部署了Iterator接口,就称这种数据结构是”可遍历的“。ES6 规定,默认的 Iterator 接口部署在数据结构的Symbol.iterator
属性,或者说,一个数据结构只要具有Symbol.iterator
属性,就可以认为是“可遍历的”(iterable)。
原生具备Iterator接口的数据:
- Array
- Map
- Set
- String
- TypeArray
- 函数的 arguments 对象
- NodeList 对象
调用Iterator接口的情况
结构赋值
对数组和 Set 结构进行解构赋值时,会默认调用Symbol.iterator
方法。
1 | let set = new Set().add('a').add('b').add('c'); |
扩展运算符
1 | / 例一 |
yield*
其他情况
由于数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,其实都调用了遍历器接口。
- for…of
- Array.from()
- Map(), Set(), WeakMap(), WeakSet()(比如
new Map([['a',1],['b',2]])
) - Promise.all()
- Promise.race()
Iterator接口
Symbol.Iterator方法的简单实现。
1 | let myIterable = { |
for…of
JavaScript 原有的for...in
循环,只能获得对象的键名,不能直接获取键值。ES6 提供for...of
循环,允许遍历获得键值。
1 | var arr = ['a', 'b', 'c', 'd']; |
优点:
- 有着同
for...in
一样的简洁语法,但是没有for...in
那些缺点。 - 不同于
forEach
方法,它可以与break
、continue
和return
配合使用。 - 提供了遍历所有数据结构的统一操作接口。
async 函数
Generator函数的语法糖
async
表示函数里有异步操作,await
表示紧跟在后面的表达式需要等待结果。
async
函数的返回值是一个Promise对象。
async
可以看做是多个一步操作包装成的一个Promise对象,await
命令就是内部then命令的语法糖。
基本用法
async
函数返回一个Promise
对象,可以使用then
方法添加回调函数。当函数执行的时候,遇到await
函数就会先返回,等到异步操作完成,才会执行函数体后面的语句。
1 | // 函数声明 |
返回Promise对象
async
函数内部return
返回的值,会成为then
回调函数的参数。
1 | async function f() { |
async
函数发生错误,会使返回的Promise对象变为reject状态,错误信息可以由catch
方法得到。
1 | async function f() { |
Promise对象的状态变化
async
函数返回的Promise对象,只有函数内部await
函数后边跟着的Promise对象执行完毕,才会产生状态改变,除非遇到return函数,或者发生错误。
async
函数内部的异步操作执行完成,才会执行then
回调函数操作。
1 | async function getTitle(url) { |
await 命令
通常await
函数后面是一个Promise对象,如果不是,则返回相应的值。
1 | async function f() { |
只要一个await函数后面的Promise对象变为reject,则async函数就会中断执行,如果一个await函数出错,使其不影响后续await函数的执行,就需要将其加上try…catch结构。
1 | async function f() { |
另一种方法是在await函数后面的Promise对象加上一个catch方法。
1 | async function f() { |
注意点
- 最好把await函数放在try..catch结构中
- 多个await函数如果不存在继发关系,最好让他们同时触发
1 | 继发触发 |
1 | 同时触发 |
- await函数只能写在async函数中
apply 和 call
对象本身没有某个属性或者方法,进而使用apply或者call去引用其他对象的属性方法,改变this的指向。
使用方法
apply(this指向,数组/arguments)
call(this指向,参数1,参数2,参数3)
1 | var name ="window的name"; |