Object.create

  • Object.create和Object.setPrototypeOf的区别?
    1.Object.create
// 第一个参数为原型链上的属性
// 第二个属性为实例上的属性
var obj = Object.create({protytypeProp: 666}, {
  p1: {
    value: 123,
    enumerable: true,
    configurable: true,
    writable: true,
  },
  p2: {
    value: 'abc',
    enumerable: true,
    configurable: true,
    writable: true,
  }
});
// Object.create实现
function create (prototypes) {
    const noop = function () {}
    noop.prototype = prototypes
    return new noop()
}

2.Object.setPrototypeOf

// .Object.setPrototypeOf将一个对象设置为另一个对象的原型对象。
var a = {};
var b = {x: 1};
Object.setPrototypeOf(a, b);

Object.getPrototypeOf(a) === b // true
a.x // 1

proxy

  • proxy是ES5中Object.defineProperty的升级版本。
  • proxy可以设置在原型链上。
  • 原来的属性描述符会影响proxy的使用,如果一个属性不可配置(configurable)且不可写(writable),则 Proxy 不能修 改该属性,否则通过 Proxy 对象访问该属性会报错。
  • proxy可以判断数据合法性、读取属性名是否为下划线开头。如果是就跑错,模拟私有属性。
  • proxy拦截方法一共有十三种。
  • proxy不仅可以代理对象,还可以代理很多类型对数据,比如函数,对应的拦截方法就是apply。
  • get有三个参数:第一个是原对象,第二个是访问的属性名,第三个是proxy实例。
  • set有四个参数:第一个是原对象,第二个是访问属性名,第三个是设置的属性值,第四个是proxy实例。
  • has只拦截in运算符,不拦截for...in
  • 在 Proxy 代理的情况下,目标对象内部的this关键字会指向 Proxy 代理。需要绑定原始对象this来解决。
  • 一个应用的例子
function createWebService(baseUrl) {
  return new Proxy({}, {
    get(target, propKey, receiver) {
      return () => httpGet(baseUrl+'/' + propKey);
    }
  });
}

const service = createWebService('http://example.com/data');

service.employees().then(json => {
  const employees = JSON.parse(json);
  // ···
});

defineProperty

// 语法
Object.defineProperty(obj, prop, descriptor)

descriptor包含了一些属性描述符,属性描述符包含以下几种
value、writeable、configurable、enumerable、get、set
value或writable不能和set或get同时出现。

function update () {
            console.log('更新视图')
        }
        let data = {
            name: '吴晗君',
            age: 18,
            friends: [{aa:1}, 2, 3, 4, 5],
            address:{
                location:'浦江'
            }
        }
        function observer (data) {
            if (typeof data !== 'object') return data
            for (let k in data) {
                // 在这里调用observer的目的是:
                // 1. 当data[k]的类型是对象时,需要递归遍历,修改把该对象的各属性值的存取描述符set和get。
                // 2. 同时需要将对象本身的存取描述符也进行更改(这一步在下面做,和此处调用observer无关)
                // 3. 其实只需要当值为对象时候,才需要调用,但是observer函数开头有判断,值不是对象就直接返回。所以在这里就不判断了。
                observer(data[k])
                // 将值缓存到一个变量上,这样在get方法中直接返回该变量,而不是返回data[k],如果是后者,会造成循环引用,造成栈溢出。
                // 在这里更好的方式就是将下面的代码封装成一个函数,将data[k]作为参数传进去,就不用这一步了。
                let value = data[k]
                Object.defineProperty(data, k, {
                    set (newValue) {
                        update()
                        observer(newValue)
                        value = newValue
                    },
                    get () {
                        return value
                    }
                })
            }
        }
        observer(data)
        data.address = {
            newLocation: '杭州'
        }

        const arrMethods = [
            'traverse', 'push',
            'pop', 'shift',
            'unshift', 'sort',
            'join', 'splice',
            'slice', 'map',
            'forEach', 'filter',
            'some', 'every',
            'reduce', 'reduceRight',
            'concat', 'indexOf',
            'lastIndexOf'
        ]
        arrMethods.forEach((method) => {
            let oldMethod = Array.prototype[method]
            Array.prototype[method] = function (...args) {
                update()
                oldMethod.call(this, ...args)
            }
        })
        let arr = [1, 2, 3]
        arr.unshift({
            first: 666
        })

Object.defineProperty的缺点,不能监控数组长度的变化,对原生方法的监控也需要进行重写才能监控。

遗留问题

  • 为什么Proxy严格模式下set方法内要返回true??
  • proxy在框架源码中有哪些应用?
  • 重写方法后,如push方法传进的值还是需要用属性描述符转换。要不然对该值进行更改不会触发update。

参考

  1. ECMAScript6入门
  2. javascript标准参考教程