暂时性死区
只要块级作用域存在let命令,它所声明的变量就“绑定”这个区域,不再受外部的影响。这么说可能有些抽象,举个例子:
var temp = 123; if(true) { console.log(temp); let temp; }
结果:
> ReferenceError: temp is not defined
在代码块内,使用let声明变量之前,该变量都是不可用的。在语法上,称为“暂时性死区”。(temporal dead zone)
ES6规定暂时性死区和let、const语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。
call和apply方法这两个方法都可以改变一个函数的上下文对象,只是接受参数的方式不一样。
call接收的是逗号分隔的参数。
apply接收的是参数列表。
相信你肯定看到过这样的代码:
var arr = [1, 2, 3]; var max = Function.prototype.apply.call(Math.max, null, arr); console.log(max); // 3
那么对这段代码怎么理解呢?
1.将Function.prototype.apply看成一个整体
(Function.prototype.apply).call(Math.max, null, arr)
2.func.call(context, args)可以转化为context.func(args)
所以代码被转换为:
Math.max.apply(undefined, arr)
基本上到这一步已经没必要去解释了。
那么你有没有试过将call和apply互换位置呢?
var arr = [1, 2, 3]; var max = Function.prototype.call.apply(Math.max, null, arr); console.log(max); // -Infinity
为什么的它的输出结果为-Infinity呢?
因为apply的第二参数必须为数组,这里并不是,所以参数不能正确的传递给call函数。
根据func.apply(context, args)可以转化为context.func(args)。所以被转化成了Math.max.call(), 直接调用则会输出-Infinity。
如果想要正确调用,则应这样书写:
var arr = [1, 2, 3]; var max = Function.prototype.call.apply(Math.max, arr); console.log(max); // 3
为了巩固以上内容,且看一个面试题:
var a = Function.prototype.call.apply(function(a){return a;}, [0,4,3]); alert(a);
分析弹出的a值为多少?
// 将call方法看成一个整体 (Function.prototype.call).apply(function(a){return a;}, [0,4,3]); // func.apply(context, args)可以转化为context.func(...args) (function(a){return a;}).call(0, 4, 3); // 所以结果很明显,输出4
Proxy对象作用:用来自定义对象中的操作。
let p = new Proxy(target, handler)
target 代表需要添加代理的对象,handler 用来自定义对象中的操作,比如可以用来自定义 set 或者 get 函数。
且看一个的小栗子:
// onChange 即要进行的监听操作 var watch = (object, onChange) => { const handler = { // 如果属性对应的值为对象,则返回一个新的Proxy对象 get(target, property, receiver) { try { return new Proxy(target[property], handler); } catch (err) { return Reflect.get(target, property, receiver); } }, // 定义或修改对象属性 defineProperty(target, property, descriptor) { onChange('define',property); return Reflect.defineProperty(target, property, descriptor); }, // 删除对象属性 deleteProperty(target, property) { onChange('delete',property); return Reflect.deleteProperty(target, property); } }; return new Proxy(object, handler); }; // 测试对象 var obj = { name: 'bjw', age: 22, child: [1, 2, 3] } // 对象代理 var p = watch(obj1, (type, property) => { console.log(`类型:${type}, 修改的属性:${property}`) }); p.name = 'qwe' 类型:define, 修改的属性:name "qwe" p.child Proxy {0: 1, 1: 2, 2: 3, length: 3} p.child.push(4) 类型:define, 修改的属性:3 类型:define, 修改的属性:length 4 p.child.length = 2 类型:define, 修改的属性:length 2 p.child Proxy {0: 1, 1: 2, length: 2}
如果关注Vue进展的话,可能已经知道Vue3.0中将通过Proxy来替换原来的Object.defineProperty来实现数据响应式。之所以要用Proxy替换原来的API原因在于Proxy无需一层层递归为每个属性添加代理,一次即可完成以上操作。性能上更好,并且原本的实现有一些数据更新不能监听到,但Proxy可以完美监听到任何方式的数据改变,相信通过上面的例子已经能够感受到Proxy带来的优势了。唯一的缺点可能就是浏览器兼容性不太好了。
Reflect对象为什么要有这样一个对象?
用一个单一的全局对象去存储这些方法,能够保持其他的JavaScript代码整洁、干净。(不然的话得通过原型链调用)
将一些命令式的操作delete、in使用函数代替,目的是为了让代码更好维护,避免出现更多的保留字。
Reflect对象拥有以下静态方法:
Reflect.apply
Reflect.construct
Reflect.defineProperty
Reflect.deleteProperty
Reflect.enumerate // 废弃的
Reflect.get
Reflect.getOwnPropertyDescriptor
Reflect.getPrototypeOf
Reflect.has
Reflect.isExtensible
Reflect.ownKeys
Reflect.preventExtensions
Reflect.set
Reflect.setPrototypeOf
具体函数细节:
Reflect.apply(target, this, arguments)