1.5万字概括ES6全部特性(已更新ES2020)
作者:JowayYoung仓库:Github、CodePen博客:官网、掘金、思否、知乎公众号:IQ前端特别声明:原创不易,未经授权不得转载或抄袭,如需转载可联系笔者授权
前言
第三次阅读阮一峰老师的《ES6标准入门》了,以前阅读时不细心,很多地方都是一目十行。最近这次阅读都是逐个逐个字来读,发现很多以前都没有注意到的知识点,为了方便记忆和预览全部ES6特性,所以写下本文。
以下提到的《ES6标准入门》统一使用《ES6》这个名称来代替,而最新的ES6版本也是截止到当前的ES2020
复制代码
本文的知识点完全是参考或摘录《ES6》里的语句,有部分语句为了方便理解和记忆,进行了相同意思的转义,同时对知识点进行归类划分。为了让大家能集中精力来记住这些特性,全文一句废话和题外话都没有,全部模块以笔记的形式进行书写,如果看得不是很惯建议对照《ES6》的内容来学习。
本文整理出来的笔记都是书中的精华内容,囊括了整个ES6体系的所有特性,非常方便大家重新认识全部ES6特性。半小时的阅读就可对ES6有一个全面的了解,可认为是一本ES6特性小字典,收藏后可随时查阅。即使看不完也要拉到本文末尾喔,有个大彩蛋,嘻嘻!
修正
ES6是ECMA为JavaScript制定的第6个标准版本,相关历史可查看此章节《ES6-ECMAScript6简介》。
标准委员会最终决定,标准在每年6月正式发布并作为当年的正式版本,接下来的时间里就在此版本的基础上进行改动,直到下一年6月草案就自然变成新一年的版本,这样一来就无需以前的版本号,只要用年份标记即可。ECMAscript 2015是在2015年6月发布ES6的第一个版本。以此类推,ECMAscript 2016是ES6的第二个版本、ECMAscript 2017是ES6的第三个版本。ES6既是一个历史名词也是一个泛指,含义是5.1版本以后的JavaScript下一代标准,目前涵盖了ES2015、ES2016、ES2017、ES2018、ES2019、ES2020。
所以有些文章上提到的ES7(实质上是ES2016)、ES8(实质上是ES2017)、ES9(实质上是ES2018)、ES10(实质上是ES2019)、ES11(实质上是ES2020),实质上都是一些不规范的概念。从ES1到ES6,每个标准都是花了好几年甚至十多年才制定下来,你一个ES6到ES7,ES7到ES8,才用了一年,按照这样的定义下去,那不是很快就ES20了。用正确的概念来说ES6目前涵盖了ES2015、ES2016、ES2017、ES2018、ES2019、ES2020。
另外,ES6更新的内容主要分为以下几点
- 表达式:声明、解构赋值
- 内置对象:字符串扩展、数值扩展、对象扩展、数组扩展、函数扩展、正则扩展、Symbol、Set、Map、Proxy、Reflect
- 语句与运算:Class、Module、Iterator
- 异步编程:Promise、Generator、Async
ES2015
声明
- const命令:声明常量
- let命令:声明变量
作用
- 作用域
- 全局作用域
- 函数作用域:function() {}
- 块级作用域:{}
- 作用范围
- var命令在全局代码中执行
- const命令和let命令只能在代码块中执行
- 赋值使用
- const命令声明常量后必须立马赋值
- let命令声明变量后可立马赋值或使用时赋值
- 声明方法:var、const、let、function、class、import
重点难点
- 不允许重复声明
- 未定义就使用会报错:const命令和let命令不存在变量提升
暂时性死区:在代码块内使用const命令和let命令声明变量之前,该变量都不可用
解构赋值
字符串解构:const [a, b, c, d, e] = “hello”
- 数值解构:const { toString: s } = 123
- 布尔解构:const { toString: b } = true
- 对象解构
- 形式:const { x, y } = { x: 1, y: 2 }
- 默认:const { x, y = 2 } = { x: 1 }
- 改名:const { x, y: z } = { x: 1, y: 2 }
- 数组解构
- 规则:数据结构具有Iterator接口可采用数组形式的解构赋值
- 形式:const [x, y] = [1, 2]
- 默认:const [x, y = 2] = [1]
- 函数参数解构
- 数组解构:function Func([x = 0, y = 1]) {}
- 对象解构:function Func({ x = 0, y = 1 } = {}) {}
应用场景
- 交换变量值:[x, y] = [y, x]
- 返回函数多个值:const [x, y, z] = Func()
- 定义函数参数:Func([1, 2])
- 提取JSON数据:const { name, version } = packageJson
- 定义函数参数默认值:function Func({ x = 1, y = 2 } = {}) {}
- 遍历Map结构:for (let [k, v] of Map) {}
- 输入模块指定属性和方法:const { readFile, writeFile } = require(“fs”)
重点难点
- 匹配模式:只要等号两边的模式相同,左边的变量就会被赋予对应的值
- 解构赋值规则:只要等号右边的值不是对象或数组,就先将其转为对象
- 解构默认值生效条件:属性值严格等于undefined
- 解构遵循匹配模式
- 解构不成功时变量的值等于undefined
-
字符串扩展
Unicode表示法:大括号包含表示Unicode字符(\u{0xXX}或\u{0XXX})
- 字符串遍历:可通过for-of遍历字符串
- 字符串模板:可单行可多行可插入变量的增强版字符串
- 标签模板:函数参数的特殊调用
- String.raw():返回把字符串所有变量替换且对斜杠进行转义的结果
- String.fromCodePoint():返回码点对应字符
- codePointAt():返回字符对应码点(String.fromCodePoint()的逆操作)
- normalize():把字符的不同表示方法统一为同样形式,返回新字符串(Unicode正规化)
- repeat():把字符串重复n次,返回新字符串
- matchAll():返回正则表达式在字符串的所有匹配
- includes():是否存在指定字符串
- startsWith():是否存在字符串头部指定字符串
- endsWith():是否存在字符串尾部指定字符串
重点难点
-
数值扩展
二进制表示法:0b或0B开头表示二进制(0bXX或0BXX)
- 八进制表示法:0o或0O开头表示二进制(0oXX或0OXX)
- Number.EPSILON:数值最小精度
- Number.MIN_SAFE_INTEGER:最小安全数值(-2^53)
- Number.MAX_SAFE_INTEGER:最大安全数值(2^53)
- Number.parseInt():返回转换值的整数部分
- Number.parseFloat():返回转换值的浮点数部分
- Number.isFinite():是否为有限数值
- Number.isNaN():是否为NaN
- Number.isInteger():是否为整数
- Number.isSafeInteger():是否在数值安全范围内
- Math.trunc():返回数值整数部分
- Math.sign():返回数值类型(正数1、负数-1、零0)
- Math.cbrt():返回数值立方根
- Math.clz32():返回数值的32位无符号整数形式
- Math.imul():返回两个数值相乘
- Math.fround():返回数值的32位单精度浮点数形式
- Math.hypot():返回所有数值平方和的平方根
- Math.expm1():返回e^n - 1
- Math.log1p():返回1 + n的自然对数(Math.log(1 + n))
- Math.log10():返回以10为底的n的对数
- Math.log2():返回以2为底的n的对数
- Math.sinh():返回n的双曲正弦
- Math.cosh():返回n的双曲余弦
- Math.tanh():返回n的双曲正切
- Math.asinh():返回n的反双曲正弦
- Math.acosh():返回n的反双曲余弦
-
对象扩展
简洁表示法:直接写入变量和函数作为对象的属性和方法({ prop, method() {} })
- 属性名表达式:字面量定义对象时使用[]定义键([prop],不能与上同时使用)
- 方法的name属性:返回方法函数名
- 取值函数(getter)和存值函数(setter):get/set 函数名(属性的描述对象在get和set上)
- bind返回的函数:bound 函数名
- Function构造函数返回的函数实例:anonymous
- 属性的可枚举性和遍历:描述对象的enumerable
- super关键字:指向当前对象的原型对象(只能用在对象的简写方法中method() {})
- Object.is():对比两值是否相等
- Object.assign():合并对象(浅拷贝),返回原对象
- Object.getPrototypeOf():返回对象的原型对象
- Object.setPrototypeOf():设置对象的原型对象
- proto:返回或设置对象的原型对象
属性遍历
- 描述:自身、可继承、可枚举、非枚举、Symbol
- 遍历
- for-in:遍历对象自身可继承可枚举属性
- Object.keys():返回对象自身可枚举属性键组成的数组
- Object.getOwnPropertyNames():返回对象自身非Symbol属性键组成的数组
- Object.getOwnPropertySymbols():返回对象自身Symbol属性键组成的数组
- Reflect.ownKeys():返回对象自身全部属性键组成的数组
规则
扩展运算符(…):转换数组为用逗号分隔的参数序列([…arr],相当于rest/spread参数的逆运算)
- Array.from():转换具有Iterator接口的数据结构为真正数组,返回新数组
- 类数组对象:包含length的对象、Arguments对象、NodeList对象
- 可遍历对象:String、Set结构、Map结构、Generator函数
- Array.of():转换一组值为真正数组,返回新数组
- copyWithin():把指定位置的成员复制到其他位置,返回原数组
- find():返回第一个符合条件的成员
- findIndex():返回第一个符合条件的成员索引值
- fill():根据指定值填充整个数组,返回原数组
- keys():返回以索引值为遍历器的对象
- values():返回以属性值为遍历器的对象
- entries():返回以索引值和属性值为遍历器的对象
- 数组空位:ES6明确将数组空位转为undefined(空位处理规不一,建议避免出现)
扩展应用
- 克隆数组:const arr = […arr1]
- 合并数组:const arr = […arr1, …arr2]
- 拼接数组:arr.push(…arr1)
- 代替apply:Math.max.apply(null, [x, y])=>Math.max(…[x, y])
- 转换字符串为数组:[…”hello”]
- 转换类数组对象为数组:[…Arguments, …NodeList]
- 转换可遍历对象为数组:[…String, …Set, …Map, …Generator]
- 与数组解构赋值结合:const [x, …rest/spread] = [1, 2, 3]
- 计算Unicode字符长度:Array.from(“hello”).length=>[…”hello”].length
重点难点
使用keys()、values()、entries()返回的遍历器对象,可用for-of自动遍历或next()手动遍历
函数扩展
参数默认值:为函数参数指定默认值
- 形式:function Func(x = 1, y = 2) {}
- 参数赋值:惰性求值(函数调用后才求值)
- 参数位置:尾参数
- 参数作用域:函数作用域
- 声明方式:默认声明,不能用const或let再次声明
- length:返回没有指定默认值的参数个数
- 与解构赋值默认值结合:function Func({ x = 1, y = 2 } = {}) {}
- 应用
- 指定某个参数不得省略,省略即抛出错误:function Func(x = throwMissing()) {}
- 将参数默认值设为undefined,表明此参数可省略:Func(undefined, 1)
- rest/spread参数(…):返回函数多余参数
- 形式:以数组的形式存在,之后不能再有其他参数
- 作用:代替Arguments对象
- length:返回没有指定默认值的参数个数但不包括rest/spread参数
- 严格模式:在严格条件下运行JS
- 应用:只要函数参数使用默认值、解构赋值、扩展运算符,那么函数内部就不能显式设定为严格模式
- name属性:返回函数的函数名
- 将匿名函数赋值给变量:空字符串(ES5)、变量名(ES6)
- 将具名函数赋值给变量:函数名(ES5和ES6)
- bind返回的函数:bound 函数名(ES5和ES6)
- Function构造函数返回的函数实例:anonymous(ES5和ES6)
- 箭头函数(=>):函数简写
- 无参数:() => {}
- 单个参数:x => {}
- 多个参数:(x, y) => {}
- 解构参数:({x, y}) => {}
- 嵌套使用:部署管道机制
- this指向固定化
- 并非因为内部有绑定this的机制,而是根本没有自己的this,导致内部的this就是外层代码块的this
- 因为没有this,因此不能用作构造函数
- 尾调用优化:只保留内层函数的调用帧
- 尾调用
- 定义:某个函数的最后一步是调用另一个函数
- 形式:function f(x) { return g(x); }
- 尾递归
- 定义:函数尾调用自身
- 作用:只要使用尾递归就不会发生栈溢出,相对节省内存
- 实现:把所有用到的内部变量改写成函数的参数并使用参数默认值
- 尾调用
箭头函数误区
- 函数体内的this是定义时所在的对象而不是使用时所在的对象
- 可让this指向固定化,这种特性很有利于封装回调函数
- 不可当作构造函数,因此箭头函数不可使用new命令
- 不可使用yield命令,因此箭头函数不能用作Generator函数
- 不可使用Arguments对象,此对象在函数体内不存在(可用rest/spread参数代替)
-
正则扩展
变更RegExp构造函数入参:允许首参数为正则对象,尾参数为正则修饰符(返回的正则表达式会忽略原正则表达式的修饰符)
- 正则方法调用变更:字符串对象的match()、replace()、search()、split()内部调用转为调用RegExp实例对应的RegExp.prototype[Symbol.方法]
- u修饰符:Unicode模式修饰符,正确处理大于\uFFFF的Unicode字符
- 点字符(.)
- Unicode表示法
- 量词
- 预定义模式
- i修饰符
- 转义
- y修饰符:粘连修饰符,确保匹配必须从剩余的第一个位置开始全局匹配(与g修饰符作用类似)
- unicode:是否设置u修饰符
- sticky:是否设置y修饰符
- flags:返回正则表达式的修饰符
重点难点
- y修饰符隐含头部匹配标志^
单单一个y修饰符对match()只能返回第一个匹配,必须与g修饰符联用才能返回所有匹配
Symbol
定义:独一无二的值
- 声明:const set = Symbol(str)
- 入参:字符串(可选)
- 方法
- Symbol():创建以参数作为描述的Symbol值(不登记在全局环境)
- Symbol.for():创建以参数作为描述的Symbol值,如存在此参数则返回原有的Symbol值(先搜索后创建,登记在全局环境)
- Symbol.keyFor():返回已登记的Symbol值的描述(只能返回Symbol.for()的key)
- Object.getOwnPropertySymbols():返回对象中所有用作属性名的Symbol值的数组
- 内置
- Symbol.hasInstance:指向一个内部方法,当其他对象使用instanceof运算符判断是否为此对象的实例时会调用此方法
- Symbol.isConcatSpreadable:指向一个布尔,定义对象用于Array.prototype.concat()时是否可展开
- Symbol.species:指向一个构造函数,当实例对象使用自身构造函数时会调用指定的构造函数
- Symbol.match:指向一个函数,当实例对象被String.prototype.match()调用时会重新定义match()的行为
- Symbol.replace:指向一个函数,当实例对象被String.prototype.replace()调用时会重新定义replace()的行为
- Symbol.search:指向一个函数,当实例对象被String.prototype.search()调用时会重新定义search()的行为
- Symbol.split:指向一个函数,当实例对象被String.prototype.split()调用时会重新定义split()的行为
- Symbol.iterator:指向一个默认遍历器方法,当实例对象执行for-of时会调用指定的默认遍历器
- Symbol.toPrimitive:指向一个函数,当实例对象被转为原始类型的值时会返回此对象对应的原始类型值
- Symbol.toStringTag:指向一个函数,当实例对象被Object.prototype.toString()调用时其返回值会出现在toString()返回的字符串之中表示对象的类型
- Symbol.unscopables:指向一个对象,指定使用with时哪些属性会被with环境排除
数据类型
- Undefined
- Null
- String
- Number
- Boolean
- Object(包含Array、Function、Date、RegExp、Error)
- Symbol
应用场景
- 唯一化对象属性名:属性名属于Symbol类型,就都是独一无二的,可保证不会与其他属性名产生冲突
- 消除魔术字符串:在代码中多次出现且与代码形成强耦合的某一个具体的字符串或数值
- 遍历属性名:无法通过for-in、for-of、Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回,只能通过Object.getOwnPropertySymbols返回
- 启用模块的Singleton模式:调用一个类在任何时候返回同一个实例(window和global),使用Symbol.for()来模拟全局的Singleton模式
重点难点
- Symbol()生成一个原始类型的值不是对象,因此Symbol()前不能使用new命令
- Symbol()参数表示对当前Symbol值的描述,相同参数的Symbol()返回值不相等
- Symbol值不能与其他类型的值进行运算
- Symbol值可通过String()或toString()显式转为字符串
- Symbol值作为对象属性名时,此属性是公开属性,但不是私有属性
- Symbol值作为对象属性名时,只能用方括号运算符([])读取,不能用点运算符(.)读取
Symbol值作为对象属性名时,不会被常规方法遍历得到,可利用此特性为对象定义非私有但又只用于内部的方法
Set
Set
定义:类似于数组的数据结构,成员值都是唯一且没有重复的值
- 声明:const set = new Set(arr)
- 入参:具有Iterator接口的数据结构
- 属性
- constructor:构造函数,返回Set
- size:返回实例成员总数
- 方法
- add():添加值,返回实例
- delete():删除值,返回布尔
- has():检查值,返回布尔
- clear():清除所有成员
- keys():返回以属性值为遍历器的对象
- values():返回以属性值为遍历器的对象
- entries():返回以属性值和属性值为遍历器的对象
- forEach():使用回调函数遍历每个成员
应用场景
- 去重字符串:[…new Set(str)].join(“”)
- 去重数组:[…new Set(arr)]或Array.from(new Set(arr))
- 集合数组
- 声明:const a = new Set(arr1)、const b = new Set(arr2)
- 并集:new Set([…a, …b])
- 交集:new Set([…a].filter(v => b.has(v)))
- 差集:new Set([…a].filter(v => !b.has(v)))
- 映射集合
- 声明:let set = new Set(arr)
- 映射:set = new Set([…set].map(v => v 2))或set = new Set(Array.from(set, v => v 2))
重点难点
- 遍历顺序:插入顺序
- 没有键只有值,可认为键和值两值相等
- 添加多个NaN时,只会存在一个NaN
- 添加相同的对象时,会认为是不同的对象
- 添加值时不会发生类型转换(5 !== “5”)
keys()和values()的行为完全一致,entries()返回的遍历器同时包括键和值且两值相等
WeakSet
定义:和Set结构类似,成员值只能是对象
- 声明:const set = new WeakSet(arr)
- 入参:具有Iterator接口的数据结构
- 属性
- constructor:构造函数,返回WeakSet
- 方法
- add():添加值,返回实例
- delete():删除值,返回布尔
- has():检查值,返回布尔
应用场景
- 储存DOM节点:DOM节点被移除时自动释放此成员,不用担心这些节点从文档移除时会引发内存泄漏
- 临时存放一组对象或存放跟对象绑定的信息:只要这些对象在外部消失,它在WeakSet结构中的引用就会自动消
重点难点
- 成员都是弱引用,垃圾回收机制不考虑WeakSet结构对此成员的引用
- 成员不适合引用,它会随时消失,因此ES6规定WeakSet结构不可遍历
其他对象不再引用成员时,垃圾回收机制会自动回收此成员所占用的内存,不考虑此成员是否还存在于WeakSet结构中
Map
Map
定义:类似于对象的数据结构,成员键是任何类型的值
- 声明:const set = new Map(arr)
- 入参:具有Iterator接口且每个成员都是一个双元素数组的数据结构
- 属性
- constructor:构造函数,返回Map
- size:返回实例成员总数
- 方法
- get():返回键值对
- set():添加键值对,返回实例
- delete():删除键值对,返回布尔
- has():检查键值对,返回布尔
- clear():清除所有成员
- keys():返回以键为遍历器的对象
- values():返回以值为遍历器的对象
- entries():返回以键和值为遍历器的对象
- forEach():使用回调函数遍历每个成员
重点难点
- 遍历顺序:插入顺序
- 对同一个键多次赋值,后面的值将覆盖前面的值
- 对同一个对象的引用,被视为一个键
- 对同样值的两个实例,被视为两个键
- 键跟内存地址绑定,只要内存地址不一样就视为两个键
- 添加多个以NaN作为键时,只会存在一个以NaN作为键的值
Object结构提供字符串—值的对应,Map结构提供值—值的对应
WeakMap
定义:和Map结构类似,成员键只能是对象
- 声明:const set = new WeakMap(arr)
- 入参:具有Iterator接口且每个成员都是一个双元素数组的数据结构
- 属性
- constructor:构造函数,返回WeakMap
- 方法
- get():返回键值对
- set():添加键值对,返回实例
- delete():删除键值对,返回布尔
- has():检查键值对,返回布尔
应用场景
- 储存DOM节点:DOM节点被移除时自动释放此成员键,不用担心这些节点从文档移除时会引发内存泄漏
- 部署私有属性:内部属性是实例的弱引用,删除实例时它们也随之消失,不会造成内存泄漏
重点难点
- 成员键都是弱引用,垃圾回收机制不考虑WeakMap结构对此成员键的引用
- 成员键不适合引用,它会随时消失,因此ES6规定WeakMap结构不可遍历
- 其他对象不再引用成员键时,垃圾回收机制会自动回收此成员所占用的内存,不考虑此成员是否还存在于WeakMap结构中
- 一旦不再需要,成员会自动消失,不用手动删除引用
- 弱引用的只是键而不是值,值依然是正常引用
-
Proxy
定义:修改某些操作的默认行为
- 声明:const proxy = new Proxy(target, handler)
- 入参
- target:拦截的目标对象
- handler:定制拦截行为
- 方法
- Proxy.revocable():返回可取消的Proxy实例(返回{ proxy, revoke },通过revoke()取消代理)
- 拦截方式
- get():拦截对象属性读取
- set():拦截对象属性设置,返回布尔
- has():拦截对象属性检查k in obj,返回布尔
- deleteProperty():拦截对象属性删除delete obj[k],返回布尔
- defineProperty():拦截对象属性定义Object.defineProperty()、Object.defineProperties(),返回布尔
- ownKeys():拦截对象属性遍历for-in、Object.keys()、Object.getOwnPropertyNames()、Object.getOwnPropertySymbols(),返回数组
- getOwnPropertyDescriptor():拦截对象属性描述读取Object.getOwnPropertyDescriptor(),返回对象
- getPrototypeOf():拦截对象原型读取instanceof、Object.getPrototypeOf()、Object.prototype.proto、Object.prototype.isPrototypeOf()、Reflect.getPrototypeOf(),返回对象
- setPrototypeOf():拦截对象原型设置Object.setPrototypeOf(),返回布尔
- isExtensible():拦截对象是否可扩展读取Object.isExtensible(),返回布尔
- preventExtensions():拦截对象不可扩展设置Object.preventExtensions(),返回布尔
- apply():拦截Proxy实例作为函数调用proxy()、proxy.apply()、proxy.call()
- construct():拦截Proxy实例作为构造函数调用new proxy()
应用场景
- Proxy.revocable():不允许直接访问对象,必须通过代理访问,一旦访问结束就收回代理权不允许再次访问
- get():读取未知属性报错、读取数组负数索引的值、封装链式操作、生成DOM嵌套节点
- set():数据绑定(Vue数据绑定实现原理)、确保属性值设置符合要求、防止内部属性被外部读写
- has():隐藏内部属性不被发现、排除不符合属性条件的对象
- deleteProperty():保护内部属性不被删除
- defineProperty():阻止属性被外部定义
- ownKeys():保护内部属性不被遍历
重点难点
- 要使Proxy起作用,必须针对实例进行操作,而不是针对目标对象进行操作
- 没有设置任何拦截时,等同于直接通向原对象
- 属性被定义为不可读写/扩展/配置/枚举时,使用拦截方法会报错
-
Reflect
定义:保持Object方法的默认行为
- 方法
- get():返回对象属性
- set():设置对象属性,返回布尔
- has():检查对象属性,返回布尔
- deleteProperty():删除对象属性,返回布尔
- defineProperty():定义对象属性,返回布尔
- ownKeys():遍历对象属性,返回数组(Object.getOwnPropertyNames()+Object.getOwnPropertySymbols())
- getOwnPropertyDescriptor():返回对象属性描述,返回对象
- getPrototypeOf():返回对象原型,返回对象
- setPrototypeOf():设置对象原型,返回布尔
- isExtensible():返回对象是否可扩展,返回布尔
- preventExtensions():设置对象不可扩展,返回布尔
- apply():绑定this后执行指定函数
- construct():调用构造函数创建实例
设计目的
- 将Object属于语言内部的方法放到Reflect上
- 将某些Object方法报错情况改成返回false
- 让Object操作变成函数行为
- Proxy与Reflect相辅相成
废弃方法
- Object.defineProperty()=>Reflect.defineProperty()
- Object.getOwnPropertyDescriptor()=>Reflect.getOwnPropertyDescriptor()
重点难点
- Proxy方法和Reflect方法一一对应
- Proxy和Reflect联合使用,前者负责拦截赋值操作,后者负责完成赋值操作
数据绑定:观察者模式
const observerQueue = new Set();
const observe = fn => observerQueue.add(fn);
const observable = obj => new Proxy(obj, {
set(tgt, key, val, receiver) {
const result = Reflect.set(tgt, key, val, receiver);
observerQueue.forEach(v => v());
return result;
}
});
const person = observable({ age: 25, name: “Yajun” });
const print = () => console.log(${person.name} is ${person.age} years old
);
observe(print);
person.name = “Joway”;
复制代码
Class
- 定义:对一类具有共同特征的事物的抽象(构造函数语法糖)
- 原理:类本身指向构造函数,所有方法定义在prototype上,可看作构造函数的另一种写法(Class === Class.prototype.constructor)
- 方法和关键字
- constructor():构造函数,new命令生成实例时自动调用
- extends:继承父类
- super:新建父类的this
- static:定义静态属性方法
- get:取值函数,拦截属性的取值行为
- set:存值函数,拦截属性的存值行为
- 属性
- proto:构造函数的继承(总是指向父类)
- proto.proto:子类的原型的原型,即父类的原型(总是指向父类的proto)
- prototype.proto:属性方法的继承(总是指向父类的prototype)
- 静态属性:定义类完成后赋值属性,该属性不会被实例继承,只能通过类来调用
- 静态方法:使用static定义方法,该方法不会被实例继承,只能通过类来调用(方法中的this指向类,而不是实例)
- 继承
- 实质
- ES5实质:先创造子类实例的this,再将父类的属性方法添加到this上(Parent.apply(this))
- ES6实质:先将父类实例的属性方法加到this上(调用super()),再用子类构造函数修改this
- super
- 作为函数调用:只能在构造函数中调用super(),内部this指向继承的当前子类(super()调用后才可在构造函数中使用this)
- 作为对象调用:在普通方法中指向父类的原型对象,在静态方法中指向父类
- 显示定义:使用constructor() { super(); }定义继承父类,没有书写则显示定义
- 子类继承父类:子类使用父类的属性方法时,必须在构造函数中调用super(),否则得不到父类的this
- 父类静态属性方法可被子类继承
- 子类继承父类后,可从super上调用父类静态属性方法
- 实质
- 实例:类相当于实例的原型,所有在类中定义的属性方法都会被实例继承
- 显式指定属性方法:使用this指定到自身上(使用Class.hasOwnProperty()可检测到)
- 隐式指定属性方法:直接声明定义在对象原型上(使用Class.proto.hasOwnProperty()可检测到)
- 表达式
- 类表达式:const Class = class {}
- name属性:返回紧跟class后的类名
- 属性表达式:[prop]
- Generator方法:* mothod() {}
- Async方法:async mothod() {}
- this指向:解构实例属性或方法时会报错
- 绑定this:this.mothod = this.mothod.bind(this)
- 箭头函数:this.mothod = () => this.mothod()
- 属性定义位置
- 定义在构造函数中并使用this指向
- 定义在类最顶层
- new.target:确定构造函数是如何调用
原生构造函数
- String()
- Number()
- Boolean()
- Array()
- Object()
- Function()
- Date()
- RegExp()
- Error()
重点难点
- 在实例上调用方法,实质是调用原型上的方法
- Object.assign()可方便地一次向类添加多个方法(Object.assign(Class.prototype, { … }))
- 类内部所有定义的方法是不可枚举的(non-enumerable)
- 构造函数默认返回实例对象(this),可指定返回另一个对象
- 取值函数和存值函数设置在属性的Descriptor对象上
- 类不存在变量提升
- 利用new.target === Class写出不能独立使用必须继承后才能使用的类
- 子类继承父类后,this指向子类实例,通过super对某个属性赋值,赋值的属性会变成子类实例的属性
- 使用super时,必须显式指定是作为函数还是作为对象使用
- extends不仅可继承类还可继承原生的构造函数
私有属性方法
const name = Symbol(“name”);
const print = Symbol(“print”);
class Person {
constructor(age) {
this[name] = “Bruce”;
this.age = age;
}
print {
console.log(${this[name]} is ${this.age} years old
);
}
}
复制代码
继承混合类
function CopyProperties(target, source) {
for (const key of Reflect.ownKeys(source)) {
if (key !== “constructor” && key !== “prototype” && key !== “name”) {
const desc = Object.getOwnPropertyDescriptor(source, key);
Object.defineProperty(target, key, desc);
}
}
}
function MixClass(…mixins) {
class Mix {
constructor() {
for (const mixin of mixins) {
CopyProperties(this, new mixin());
}
}
}
for (const mixin of mixins) {
CopyProperties(Mix, mixin);
CopyProperties(Mix.prototype, mixin.prototype);
}
return Mix;
}
class Student extends MixClass(Person, Kid) {}
复制代码
Module
- 命令
- export:规定模块对外接口
- 默认导出:export default Person(导入时可指定模块任意名称,无需知晓内部真实名称)
- 单独导出:export const name = “Bruce”
- 按需导出:export { age, name, sex }(推荐)
- 改名导出:export { name as newName }
- import:导入模块内部功能
- 默认导入:import Person from “person”
- 整体导入:import * as Person from “person”
- 按需导入:import { age, name, sex } from “person”
- 改名导入:import { name as newName } from “person”
- 自执导入:import “person”
- 复合导入:import Person, { name } from “person”
- 复合模式:export命令和import命令结合在一起写成一行,变量实质没有被导入当前模块,相当于对外转发接口,导致当前模块无法直接使用其导入变量
- 默认导入导出:export { default } from “person”
- 整体导入导出:export * from “person”
- 按需导入导出:export { age, name, sex } from “person”
- 改名导入导出:export { name as newName } from “person”
- 具名改默认导入导出:export { name as default } from “person”
- 默认改具名导入导出:export { default as name } from “person”
- export:规定模块对外接口
- 继承:默认导出和改名导出结合使用可使模块具备继承性
- 设计思想:尽量地静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量
- 严格模式:ES6模块自动采用严格模式(不管模块头部是否添加use strict)
模块方案
- CommonJS:用于服务器(动态化依赖)
- AMD:用于浏览器(动态化依赖)
- CMD:用于浏览器(动态化依赖)
- UMD:用于浏览器和服务器(动态化依赖)
- ESM:用于浏览器和服务器(静态化依赖)
加载方式
- 运行时加载
- 定义:整体加载模块生成一个对象,再从对象上获取需要的属性和方法进行加载(全部加载)
- 影响:只有运行时才能得到这个对象,导致无法在编译时做静态优化
- 编译时加载
- 定义:直接从模块中获取需要的属性和方法进行加载(按需加载)
- 影响:在编译时就完成模块加载,效率比其他方案高,但无法引用模块本身(本身不是对象),可拓展JS高级语法(宏和类型校验)
加载实现
- 传统加载:通过