ES6学习并记录

185 阅读11分钟

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标准入门