proxy
get()
get方法用于拦截某个属性的读取操作,可以接受三个参数,依次为目标对象、属性名和 proxy 实例本身(严格地说,是操作行为所针对的对象),其中最后一个参数可选。
set()
set方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和 Proxy 实例本身,其中最后一个参数可选。
apply()
拦截函数调用
has()
- has方法用来拦截HasProperty操作,即判断对象是否具有某个属性时,这个方法会生效。典型的操作就是in运算符。
- has方法可以接受两个参数,分别是
目标对象、需查询的属性名。 - 如果原对象不可配置或者禁止扩展,这时has拦截会报错
- 值得注意的是,has方法拦截的是HasProperty操作,而不是HasOwnProperty操作,即has方法不判断一个属性是对象自身的属性,还是继承的属性。
- 另外,虽然for...in循环也用到了in运算符,但是has拦截对for...in循环不生效
- has拦截只对in运算符生效,对for...in循环不生效
var handler = {
has (target, key) {
if (key[0] === '_') {
return false;
}
return key in target;
}
};
var target = { _prop: 'foo', prop: 'foo' };
var proxy = new Proxy(target, handler);
'_prop' in proxy // false
construct() 接受三个参数。
- target:目标对象
- args:构造函数的参数对象
- newTarget:创造实例对象时,new命令作用的构造函数(下面例子的p)
- construct方法返回的必须是一个对象,否则会报错。
var p = new Proxy(function () {}, {
construct: function(target, args) {
console.log('called: ' + args.join(', '));
return { value: args[0] * 10 };
}
});
(new p(1)).value
// "called: 1"
// 10
deleteProperty()
- deleteProperty方法用于拦截delete操作,如果这个方法抛出错误或者返回false,当前属性就无法被delete命令删除。
- 目标对象自身的不可配置(configurable)的属性,不能被deleteProperty方法删除,否则报错。
defineProperty()
- defineProperty()方法拦截了Object.defineProperty()操作。
getOwnPropertyDescriptor()
- 方法拦截Object.getOwnPropertyDescriptor(),返回一个属性描述对象或者undefined。
- getOwnPropertyDescriptor 只能获取当前可枚举的属性,继承的获取不到
getPrototypeOf()
- 方法主要用来拦截获取对象原型。具体来说,拦截下面这些操作。
- Object.prototype.proto
- Object.prototype.isPrototypeOf()
- Object.getPrototypeOf()
- Reflect.getPrototypeOf()
- instanceof
- 方法的返回值必须是对象或者null,否则报错
isExtensible()
- isExtensible()方法拦截Object.isExtensible()操作。
- 该方法只能返回布尔值,否则返回值会被自动转为布尔值。
- 这个方法有一个强限制,它的返回值必须与目标对象的isExtensible属性保持一致,否则就会抛出错误。
ownKeys()
- 返回值为key ,没有则获取不到
```js
const obj = { hello: 'world' };
const proxy = new Proxy(obj, {
ownKeys: function () {
return ['hello']; // 数组key,当没有hellow下面则不输出
}
});
for (let key in proxy) {
console.log(key); // 没有任何输出
}
```
- 用来拦截对象自身属性的读取操作。具体来说,拦截以下操作。
- Object.getOwnPropertyNames()
- Object.getOwnPropertySymbols()
- Object.keys()
- for...in循环
- 注意,使用Object.keys()方法时,有三类属性会被ownKeys()方法自动过滤,不会返回。
- 目标对象上不存在的属性
- 属性名为 Symbol 值
- 不可遍历(enumerable)的属性
- ownKeys()方法返回的数组成员,只能是字符串或 Symbol 值。如果有其他类型的值,或者返回的根本不是数组,就会报错。
- 如果目标对象自身包含不可配置的属性,则该属性必须被ownKeys()方法返回,否则报错。
- 另外,如果目标对象是不可扩展的(non-extensible),这时ownKeys()方法返回的数组之中,必须包含原对象的所有属性,且不能包含多余的属性,否则报错。
preventExtensions()
- Object.preventExtensions()方法让一个对象变的不可扩展,也就是永远不能再添加新的属性。
- preventExtensions()方法拦截Object.preventExtensions()。该方法必须返回一个布尔值,否则会被自动转为布尔值。
- 这个方法有一个限制,只有目标对象不可扩展时(即Object.isExtensible(proxy)为false),proxy.preventExtensions才能返回true,否则会报错。
var proxy = new Proxy({}, {
preventExtensions: function(target) {
console.log('called');
Object.preventExtensions(target); // 这行不加会报错
return true;
}
});
Object.preventExtensions(proxy)
// "called"
// Proxy {}
setPrototypeOf()
- setPrototypeOf()方法主要用来拦截Object.setPrototypeOf()方法.
- Object.setPrototypeOf() 方法设置一个指定的对象的原型 ( 即, 内部[[Prototype]]属性)到另一个对象或 null。
- Object.setPrototypeOf(obj2,obj) 将obj2的原型设置为obj
- 注意,该方法只能返回布尔值,否则会被自动转为布尔值。另外,如果目标对象不可扩展(non-extensible),setPrototypeOf()方法不得改变目标对象的原型。
var handler = {
setPrototypeOf (target, proto) {
target.__proto__ = proto; //这行不加是不生效的 获取不到a
return target
}
};
var proto = {a:1};
var target = function () {};
var proxy = new Proxy(target, handler);
Object.setPrototypeOf(proxy, proto);
console.log(proxy.a)
Proxy.revocable()
- Proxy.revocable()方法返回一个可取消的 Proxy 实例。
- Proxy.revocable()方法返回一个对象,该对象的proxy属性是Proxy实例,revoke属性是一个函数,可以取消Proxy实例。上面代码中,当执行revoke函数之后,再访问Proxy实例,就会抛出一个错误。
- Proxy.revocable()的一个使用场景是,目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就收回代理权,不允许再次访问
let target = {};
let handler = {};
let {proxy, revoke} = Proxy.revocable(target, handler);
proxy.foo = 123;
proxy.foo // 123
revoke();
proxy.foo // TypeError: Revoked
this
- 由于this指向的变化,导致 Proxy 无法代理目标对象。
const _name = new WeakMap();
class Person {
constructor(name) {
_name.set(this, name);
}
get name() {
return _name.get(this);
}
}
const jane = new Person('Jane');
jane.name // 'Jane'
const proxy = new Proxy(jane, {}); // 此时的this指向 proxy 所以下面拿不到name
proxy.name // undefined
- 此外,有些原生对象的内部属性,只有通过正确的this才能拿到,所以 Proxy 也无法代理这些原生对象的属性。
const target = new Date();
const handler = {};
const proxy = new Proxy(target, handler); // 此时的this指向 proxy,有些拿不到原生内部属性,所以下面报错
proxy.getDate();
// TypeError: this is not a Date object.
- 解决方案
const target = new Date('2015-01-01');
const handler = {
get(target, key) {
if (prop === 'getDate') { //拦截 原生内部属性方法,判断是的话就改变其this指向,负责就返回target里的key值
return target.getDate.bind(target);
}
return Reflect.get(target, key);
}
};
const proxy = new Proxy(target, handler);
proxy.getDate() // 1
实例:Web 服务的客户端
// 模拟接口
const httpGet = (url) => {
return new Promise((reslove) => {
reslove(1111)
})
}
// 初始接接口 /api转换为 /test
const service = createWebService('http://example.com/api','api',httpGet);
// 接收代理返回的promise成功回调 ,改变service对象后面的属性可以动态修改代理的接口地址
service.test.then(json => {
const employees = JSON.parse(json);
console.log(employees)
});
function createWebService(baseUrl, path,server) {
return new Proxy({}, {
get(target, propKey, receiver) {
console.log(propKey)
return server(baseUrl.replace(path,propKey)); // 这里是代理接收到原始地址,和要替换的path,然后进行替换
}
});
}
Reflect
1. 将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。现阶段,某些方法同时在Object和Reflect对象上部署,未来的新方法将只部署在Reflect对象上。也就是说,从Reflect对象上可以拿到语言内部的方法。
2. 修改某些Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false。
let obj ={}
let val = '';
Object.preventExtensions(obj) // 设置obj对象不可扩展
// Object.defineProperty 这个要用try catch包住,否则会引起代码终止运行
if (Reflect.defineProperty(obj,obj , { // Reflect映射时不会抛出报错而是返回false
get() {
return val;
},
set(value) {
val = value;
},
})) {
console.log(obj,1)
// success
} else {
console.log('error') // error
// failure
}
obj.a =12
console.log(obj.a) // undefined
3. 让Object操作都变成函数行为。某些Object操作是命令式,比如name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)让它们变成了函数行为。
4. Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。
let obj ={}
var loggedObj = new Proxy(obj, {
get(target, name) {
console.log('get', target, name);
return Reflect.get(target, name);
},
deleteProperty(target, name) {
console.log('delete' + name);
return Reflect.deleteProperty(target, name);
},
has(target, name) {
console.log('has' + name);
return Reflect.has(target, name);
}
});
Reflect.set(loggedObj,'name', 123)
Reflect.get(loggedObj,'name')
Reflect.has(loggedObj, 'name')
Reflect.deleteProperty(loggedObj,'name')
// 老写法
Function.prototype.apply.call(Math.floor, undefined, [1.75]) // 1
// 任何函数都是Function类的对象实例,所以Math.floor方法对象具有apply方法。
// (Function.prototype.apply).call(Math.floor, undefined, [1.75])
// Math.floor.apply(undefined, [1.75])
// 新写法
Reflect.apply(Math.floor, undefined, [1.75]) // 1
静态方法
-
Reflect.apply(target, thisArg, args)
- Reflect.apply方法等同于Function.prototype.apply.call(func, thisArg, args),用于绑定this对象后执行给定函数。
- 一般来说,如果要绑定一个函数的this对象,可以这样写fn.apply(obj, args),但是如果函数定义了自己的apply方法,就只能写成Function.prototype.apply.call(fn, obj, args),采用Reflect对象可以简化这种操作。
const ages = [11, 33, 12, 54, 18, 96]; // 旧写法 const youngest = Math.min.apply(Math, ages); const oldest = Math.max.apply(Math, ages); const type = Object.prototype.toString.call(youngest); // 新写法 const youngest = Reflect.apply(Math.min, Math, ages); const oldest = Reflect.apply(Math.max, Math, ages); const type = Reflect.apply(Object.prototype.toString, youngest, []); // Object.prototype.toString.apply(youngest, []); // 第一个是绑定的this,第二个入参 -
Reflect.construct(target, args)
- Reflect.construct方法等同于new target(...args),这提供了一种不使用new,来调用构造函数的方法。
function Greeting(name) { this.name = name; } // new 的写法 const instance = new Greeting('张三'); // Reflect.construct 的写法 const instance = Reflect.construct(Greeting, ['张三']); -
Reflect.get(target, name, receiver) // this 绑定 receiver
-
Reflect.set(target, name, value, receiver) // this 绑定 receiver
-
Reflect.deleteProperty(target, name) // 删除某个字段
-
Reflect.defineProperty(target, name, desc)
- Reflect.defineProperty方法基本等同于Object.defineProperty,用来为对象定义属性。未来,后者会被逐渐废除,请从现在开始就使用Reflect.defineProperty代替它。
const p = new Proxy({}, { defineProperty(target, prop, descriptor) { console.log(descriptor); return Reflect.defineProperty(target, prop, descriptor); } }); p.foo = 'bar'; // {value: "bar", writable: true, enumerable: true, configurable: true} p.foo // "bar" -
Reflect.has(target, name) // 判断对象里是否有某个字段
-
Reflect.ownKeys(target)
- Reflect.ownKeys方法用于返回对象的所有属性,基本等同Object.getOwnPropertyNames与Object.getOwnPropertySymbols之和。
- 如果Reflect.ownKeys()方法的第一个参数不是对象,会报错。
var myObject = { foo: 1, bar: 2, [Symbol.for('baz')]: 3, [Symbol.for('bing')]: 4, }; // 旧写法 Object.getOwnPropertyNames(myObject) // ['foo', 'bar'] Object.getOwnPropertySymbols(myObject) //[Symbol(baz), Symbol(bing)] // 新写法 Reflect.ownKeys(myObject) // ['foo', 'bar', Symbol(baz), Symbol(bing)] -
Reflect.isExtensible(target)
-
Reflect.preventExtensions(target)
- Reflect.preventExtensions对应Object.preventExtensions方法,用于让一个对象变为不可扩展。它返回一个布尔值,表示是否操作成功。
- 如果参数不是对象,Object.preventExtensions在 ES5 环境报错,在 ES6 环境返回传入的参数,而Reflect.preventExtensions会报错。
var myObject = {}; // 旧写法 Object.preventExtensions(myObject) // Object {} // 新写法 Reflect.preventExtensions(myObject) // true // ES5 环境 Object.preventExtensions(1) // 报错 // ES6 环境 Object.preventExtensions(1) // 1 // 新写法 Reflect.preventExtensions(1) // 报错 -
Reflect.getOwnPropertyDescriptor(target, name)
- Reflect.getOwnPropertyDescriptor基本等同于Object.getOwnPropertyDescriptor,用于得到指定属性的描述对象,将来会替代掉后者。
var myObject = {}; Object.defineProperty(myObject, 'hidden', { value: true, enumerable: false, }); // 旧写法 var theDescriptor = Object.getOwnPropertyDescriptor(myObject, 'hidden'); // 新写法 var theDescriptor = Reflect.getOwnPropertyDescriptor(myObject, 'hidden'); -
Reflect.getPrototypeOf(target)
- Reflect.getPrototypeOf方法用于读取对象的__proto__属性,对应Object.getPrototypeOf(obj)。
const myObj = new FancyThing(); // 旧写法 Object.getPrototypeOf(myObj) === FancyThing.prototype; // 新写法 Reflect.getPrototypeOf(myObj) === FancyThing.prototype; -
Reflect.setPrototypeOf(target, prototype)
const myObj = {}; // 旧写法 Object.setPrototypeOf(myObj, Array.prototype); // 新写法 Reflect.setPrototypeOf(myObj, Array.prototype); myObj.length // 0
实例:使用 Proxy 实现观察者模式
const queuedObservers = new Set();
const observe = fn => queuedObservers.add(fn);
const observable = obj => new Proxy(obj, {set});
function set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
queuedObservers.forEach(observer => observer());
return result;
}
const person = observable({
name: '张三',
age: 20
});
function print() {
console.log(`${person.name}, ${person.age}`)
}
observe(print);
person.name = '李四';
// 输出
// 李四, 20
Generator函数的语法
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var a = foo(5);
a.next() // Object{value:6, done:false} // 无问题
a.next() // Object{value:NaN, done:false} // 无问题
a.next() // Object{value:NaN, done:true} // 无问题
var b = foo(5);
b.next() // { value:6, done:false }
// 第一次调用的时候不需要next传值,用的是方法传的入参5, 所以第一次值是5+1 = 6 ----value = 6
b.next(12) // { value:8, done:false }
// next传入的12 是上一次yield的返回值 所以 y = 2 * 12 = 24, z = 8 ----value = 8
b.next(13) // { value:42, done:true }
// next传入的13 是上一次yield的返回值 所以 z = 13, y = 24, x = 5 ----value = 42
next 方法的参数
- 由于next方法的参数表示上一个yield表达式的返回值,所以在第一次使用next方法时,传递参数是无效的,Generator 函数如果不用wrapper先包一层,是无法第一次调用next方法,就输入参数的。
// hello1 hello2 hello3 hello4
function wrapper(generatorFunction) {
return function (...args) {
console.log(...args)
let generatorObject = generatorFunction(...args);
generatorObject.next(); // 第一次隔过去,则每次都是有入参了
return generatorObject;
};
}
const wrapped = wrapper(function* () {
console.log(`First input: ${yield}1`);
console.log(`First input: ${yield}2`);
console.log(`First input: ${yield}3`);
console.log(`First input: ${yield}4`);
return 'DONE';
})();
wrapped.next('hello1!');
wrapped.next('hello2!');
wrapped.next('hello3!');
wrapped.next('hello4!');
控制流管理
将任务分解成步骤之后,还可以将项目分解成多个依次执行的任务。
const step1Func = () => {
return 'step1Func';
}
const step2Func = () => {
return 'step2Func';
}
const step3Func = () => {
return 'step3Func';
}
const step4Func = () => {
return 'step4Func';
}
const job1 = () => {
return {steps: [step1Func,step2Func]};
}
const job2 = () => {
return {steps: [step3Func,step4Func]};
}
function* iterateSteps(steps) {
for (var i = 0; i < steps.length; i++) {
var step = steps[i];
yield step();
}
}
let jobs = [job1, job2];
function* iterateJobs(jobs) {
for (var i = 0; i < jobs.length; i++) {
var job = jobs[i];
yield* iterateSteps(job().steps);
}
}
for (var step of iterateJobs(jobs)) {
console.log(step);
}
const step1 = (val) => {
setTimeout(()=>{
fun.next(val +1);
})
return val
}
const step2 = (val) => {
setTimeout(()=>{
fun.next(val +1);
})
}
const step3 = (val) => {
setTimeout(()=>{
fun.next(val +1);
})
}
const step4 = (val) => {
setTimeout(()=>{
fun.next(val +1);
})
}
function* longRunningTask(value1) {
try {
var value2 = yield step1(value1);
console.log(value2,'value2')
var value3 = yield step2(value2);
console.log(value3,'value3')
var value4 = yield step3(value3);
console.log(value4,'value4')
var value5 = yield step4(value4);
console.log(value5,'value5')
// Do something with value4
} catch (e) {
console.log(e,'e')
// Handle any error from step1 through step4
}
}
var fun = longRunningTask(1);
console.log(fun.next())
部署 Iterator 接口
利用 Generator 函数,可以在任意对象上部署 Iterator 接口。
function* iterEntries(obj) {
let keys = Object.keys(obj);
for (let i=0; i < keys.length; i++) {
let key = keys[i];
yield [key, obj[key]];
}
}
let myObj = { foo: 3, bar: 7 };
for (let [key, value] of iterEntries(myObj)) {
console.log(key, value);
}
Generator 函数的异步应用
1. 传统方法
- 回调函数
- 事件监听
- 发布/订阅
- Promise 对象
- Generator 函数将 JavaScript 异步编程带入了一个全新的阶段。
Thunk 函数的自动流程管理
// Thunk 函数真正的威力,在于可以自动执行 Generator 函数。下面就是一个基于 Thunk 函数的 Generator 执行器。
const Thunk = function (fn) { // fn -> f
return function (...args) { // [1] [2]
return function (callback) { // callback -> next
console.log(args, 'callback')
return fn.call(this, ...args, callback);
}
};
};
function f(a, cb) {
cb(null, a); // 递归
// cb -> next(err, data)
}
const ft = Thunk(f);
var g = function* () {
var f1 = yield ft(function () {return 33333}());
// Thunk(f)(1) -> function (callback) 函数
console.log(f1, 'f1')
var f2 = yield ft(2);
console.log(f2, 'f2')
};
function run(fn) {
var gen = fn();
function next(err, data) {
var result = gen.next(data);
if (result.done) return;
console.log(result.value,'result.value')
result.value(next); // 11行 callbak回调
}
next();
}
run(g);
Async
核心实现自动执行函数
function spawn(genF) {
return new Promise((reslove, reject) => {
const gen = genF();
const step = (nextF) => {
let next;
try {
next = nextF();
} catch (e) {
return reject(e)
}
if (next.done) {
return reslove(next.value)
}
Promise.resolve(next.value).then(
(v) => {
step(()=>gen.next(v))
},
(err) => {
step(()=>gen.throw(err))
}
)
}
step(() => gen.next(undefined))
})
}
参考:ES6标准入门