数组API——includes
Array.prototype.includes(value : any): boolean [1, 2, 3].includes(3) // true
这是判断数组中是否含有一个元素的方法,该方法最终返回一个布尔值。
现成的判断数组中是否含有一个元素的方法:
[1, 2, 3].findIndex(i => i === 2) // 1 [1, 2, 3].find(i => i == 2) // 2 [1, 2, 3].indexOf(2) // 1
实现一个一模一样的api
const includes = (array, target) => !!~ array.indexOf(target) includes([1,2,3], 3) // true includes([1,2,3], 4) // false
新特性的意义:首先,在语义上它直观明朗,这是 indexof 所无法取代的。当然还有更深层次的必要性和不可替代性。
Array.prototype.indexOf 采用的是 === 比较,而Array.prototype.includes 不同,它采用了 SameValueZero() 比较。
SameValueZero() 是引擎内置的比较方式,并没有对外接口,其实现采用了 Map 和 Set。采用这种比较,最直接的收益就是可以判断 NaN:
[NaN].includes(NaN) // true [NaN].indexOf(NaN) // -1 NaN === NaN // false
Object Spread VS Object.assign
Object Spread 和 Object.assign 在很多情况下做的事情是一致的,它们都属于 ES Next 的新特性,当然 Object Spread 更新。事实上,规范说明中,也告诉我们 “object spread”:{… obj} 和 Object.assign({},obj) 是等价的。
但是一定还具有区别。实际上,Object.assign() 将会修改它的第一个参数对象,这个修改可以触发其第一个参数对象的 setter。从这个层面上讲,Object spread 操作符会创建一个对象副本,而不会修改任何值,这也许是更好的选择,也更加符合React/Redux的“不可变性”的概念。
如果使用 Object.assign(),我们始终保证一个空对象作为第一个参数,也能实现同样的“不可变性”,但是性能比 Object Spread 就差的比较多了。
箭头函数
首先了解箭头函数的概念:箭头函数完全修复了this
的指向,this
总是指向词法作用域,也就是外层调用者obj
var obj = { birth: 1990, getAge: function () { var b = this.birth; // 1990 var fn = function () { return new Date().getFullYear() - this.birth; // this指向window或undefined }; return fn(); } }; var obj = { birth: 1990, getAge: function () { var b = this.birth; // 1990 var fn = () => new Date().getFullYear() - this.birth; // this指向obj对象 return fn(); } }; obj.getAge(); // 25
哪些场景下不适合使用 ES6 箭头函数?
- 构造函数的原型方法上
构造函数的原型方法需要通过 this 获得实例,因此箭头函数不可以出现在构造函数的原型方法上:
Person.prototype = () => { // ... }
- 需要获得 arguments 时
箭头函数不具有 arguments,因此在其函数体内无法访问这一特殊的伪数组,那么相关场景下也不适合使用箭头函数。
使用对象方法时
const person = { name: 'lucas', getName: () => { console.log(this.name) } }; person.getName()
getName 函数体内的 this 指向 window,显然不符合其用意
- 使用动态回调时
const btn = document.getElementById('btn') btn.addEventListener('click', () => { console.log(this === window) });
当点击 id 为 btn 的按钮时,将会输出:true,事件绑定函数的 this 指向了 window,而无法获取事件对象。
Proxy 代理
1、new的处理
class Person { constructor (name) { this.name = name } } let proxyPersonClass = new Proxy(Person, { apply (target, context, args) { throw new Error(`hello: Function ${target.name} cannot be invoked without 'new'`) } })
对 Person 构造函数进行了代理,这样就可以防止非构造函数实例化的调用
proxyPersonClass('lucas') // VM173058:9 Uncaught Error: hello: Function Person cannot be invoked without 'new' at <anonymous>:1:1 new proxyPersonClass('lucas') // {name: "lucas"}
也可以静默处理非构造函数实例化的调用,将其强制转换为 new 调用
class Person { constructor (name) { this.name = name } } let proxyPersonClass = new Proxy(Person, { apply (target, context, args) { return new (target.bind(context, ...args))() } }) proxyPersonClass('lucas') // Person {name: "lucas"}
2、 assert 处理
const lucas = { age: 23 } assert['lucas is older than 22!!!'] = 22 > lucas.age // Error: lucas is older than 22!!!
//我们看 assert 赋值语句右侧表达式结果为一个布尔值,当表达式成立时,断言不会抛出;如果 assert 赋值语句右侧表达式不成立时,也就是断言失败时,断言抛出错误。
assert实现,本质还是拦截操作:
const assert = new Proxy({}, { set (target, warning, value) { if (!value) { console.error(warning) } } })
Decorator (待完善)
装饰器(Decorators)让你可以在设计时对类和类的属性进行“注解”和修改。
Babel
编译 ES Next 代码,进行降级处理,进而规避了兼容性问题。
Babel 的核心原理是使用 AST(抽象语法树)将源码进行分析并转为目标代码。
const、let 编译
简单来说,const、let 一律转成 var。为了保证 const 的不可变性:Babel 如果在编译过程中发现对 const 声明的变量进行了二次赋值,将会直接报错,这样就在编译阶段进行了处理。至于 let 的块级概念,ES5 中,我们一般通过 IIFE 实现块级作用域,但是 Babel 处理非常取巧,那就是在块内给变量换一个名字,块外自然就无法访问到。
var foo = 123 { foo = 'abc' let foo } //Uncaught ReferenceError: Cannot access 'foo' before initialization
Babel 编译会将 let、const 变量重新命名,同时在 JavaScript 严格模式(strict mode)不允许使用未声明的变量,这样在声明前使用这个变量,也会报错。
"use strict"; var foo = 123 { _foo = 'abc' var _foo }
const 声明的变量一旦声明,其变量(内存地址)是不可改变 :
const foo = 0 foo = 1 // VM982:2 Uncaught TypeError: Assignment to constant variable
"use strict"; function _readOnlyError(name) { throw new Error("\"" + name + "\" is read-only"); } var foo = 0; foo = (_readOnlyError("a"), 1);
Babel 检测到 const 声明的变量被改变赋值,就会主动插入了一个 _readOnlyError
函数,并执行此函数。这个函数的执行内容就是报错,因此代码执行时就会直接抛出异常。
for 循环问题:
let array = [] for (let i = 0; i < 10; i++) { array[i] = function () { console.log(i) } } array[6]() // 6 let array = [] for (var i = 0; i < 10; i++) { array[i] = function () { console.log(i) } } array[6]() // 10
为了保存每一个循环变量 i 的值,Babel 也使用了闭包
"use strict"; var array = []; var _loop = function _loop(i) { array[i] = function () { console.log(i); }; }; for (var i = 0; i < 10; i++) { _loop(i); } array[6]();
箭头函数的编译分析
var obj = { prop: 1, func: function() { var _this = this; var innerFunc = () => { this.prop = 1; }; var innerFunc1 = function() { this.prop = 1; }; }, }; var obj = { prop: 1, func: function func() { var _this2 = this; var _this = this; var innerFunc = function innerFunc() { _this2.prop = 1; }; var innerFunc1 = function innerFunc1() { this.prop = 1; }; } };
通过 var _this2 = this;
保存当前环境的 this 为 _this2
,在调用 innerFunc 时,用新储存的 _this2
进行替换函数体内的 this 即可。
Decorators 的编译(待完善)
ES6 尾递归调用问题(待完善)
参考资料:https://www.cnblogs.com/liyuanhong/articles/10139214.html