导航


HTML

CSS

JavaScript

浏览器 & 网络

版本管理

框架

构建工具

TypeScript

性能优化

软实力

算法

UI、组件库

Node

冷门技能

认知

不要因为 Vue 使用到了 Object.defineProperty() 数据劫持,就把它和 Proxy 混为一谈。它俩不是一个东西

功能

自定义对象属性的获取、赋值、枚举、函数调用等功能

⭐️ 用法

getter、setter 基本用法

let target = {
  a: 1,
  b: 2
}

let proxy = new Proxy(target, {
  get(target, prop) {
    console.log('This is property value ' + target[prop]);
    return target[prop];
  },
  set(target, prop, value) {
    target[prop] = value;
  }
});

console.log("proxy.a", proxy.a); // 走了代理 This is ...
console.log("target.a", target.a); // 直接访问,没走代理。没显示 This is ...
proxy.b = 3;
console.log("target.b", target.b); // 3
console.log("proxy.b", proxy.b); // 3
console.log(proxy); // Proxy {a: 1, b: 2}

处理数组、函数

let arr = [
  { name: 'Lance', age: 27 },
  { name: 'GC', age: 31 },
  { name: 'Jerry', age: 29 },
  { name: 'Sherry', age: 30 }
];

let persons = new Proxy(arr, {
  get(arr, prop) {
    return arr[prop];
  },
  set(arr, prop, value) {
    arr[prop] = value;
  }
});

persons[0] = { name: 'Xi', age: 30 }
console.log(arr, persons);
// [
//   { name: 'Xi', age: 30 },
//   { name: 'GC', age: 31 },
//   { name: 'Jerry', age: 29 },
//   { name: 'Sherry', age: 30 }
// ] [
//   { name: 'Xi', age: 30 },
//   { name: 'GC', age: 31 },
//   { name: 'Jerry', age: 29 },
//   { name: 'Sherry', age: 30 }
// ]
let fn = function() {
  console.log('I am a function. ');
}

fn.a = 123;

let newFn = new Proxy(fn, {
  get(fn, prop) {
    return fn[prop] + ' This is a Proxy return';
  }
});

console.log(newFn.a); // 123 This is a Proxy return

has

let target = {
  a: 1,
  b: 2
}

let proxy = new Proxy(target, {
  get(target, prop) {
    return 'GET: ' + prop + ' = ' + target[prop];
  },
  set(target, prop, value) {
    target[prop] = value;
    console.log('SET: ' + prop + ' = ' + value);
  },
  has(target, prop) {
    console.log(target[prop]);
  }
});

console.log('a' in proxy); // false
console.log(proxy); // a, b 在 Proxy 的 Target 下

Untitled

deleteProperty

let target = {
  a: 1,
  b: 2
}

let proxy = new Proxy(target, {
  get(target, prop) {
    return 'GET: ' + prop + ' = ' + target[prop];
  },
  set(target, prop, value) {
    target[prop] = value;
    console.log('SET: ' + prop + ' = ' + value);
  },
  has(target, prop) {
    return Reflect.has(target, prop);
  },
  deleteProperty(target, prop) {
  	delete target[prop];
    console.log(1);
  }
});

delete proxy.b;
console.log("proxy", proxy);
console.log("target", target);

Untitled

ES - 14 种对象操作方法

获取原型 [[GetPrototypeOf]]

var obj = { a: 1, b: 2 };
var proto = Object.getPrototypeOf(obj);
console.log(proto); // Object.prototype
console.log(obj.__proto__);
console.log(Object.prototype);

Untitled

设置原型 [[SetPrototypeOf]]

var obj = { a: 1, b: 2 };
Object.setPrototypeOf(obj, { c: 3, d: 4 });
// obj.__proto__ = { c: 3, d: 4};
// Object.prototype = { c: 3, d: 4 };
console.log(obj);

Untitled

获取对象的可扩展性 [[IsExtensible]]

Object.isExtensible() 方法判断一个对象是否是可扩展的(是否可以在它上面添加新的属性)。

var extensible = Object.isExtensible(obj);
console.log(extensible); // true
Object.freeze(obj); // 冻结
var extensible2 = Object.isExtensible(obj);
console.log(extensible2); // false

Untitled

Object.seal

var obj = { a: 1, b: 2 };
Object.seal(obj); // 封闭对象
obj.c = 3; // 不可新增
console.log(obj);
delete obj.a; // 不可删除
console.log(obj);
obj.b = 3; // 可修改原有属性
console.log(obj);

for (const key in obj) {
  console.log(`key: ${key} - val: ${obj[key]}`);
}

Untitled

Object.freeze

var obj = { a: 1, b: 2 };
Object.freeze(obj); // 冻结对象
obj.c = 3; // 不可新增
console.log(obj);
delete obj.a; // 不可删除
console.log(obj);
obj.b = 3; // 不可修改
console.log(obj);

for (const key in obj) {
  console.log(`key: ${key} - val: ${obj[key]}`);
}

Untitled

获取自有属性 [[GetOwnProperty]]

var obj = { a: 1, b: 2 };
Object.setPrototypeOf(obj, { c: 3, d: 4 });
console.log(Object.getOwnPropertyNames(obj)); // ['a', 'b']

Untitled

禁止扩展对象 [[PreventExtensions]]

var obj = { a: 1, b: 2 };
Object.preventExtensions(obj);
obj.c = 3;
console.log(obj);

delete obj.a;
console.log(obj);

Untitled

拦截对象操作 [[DefineProperty]]

判断是否是自身属性 [[HasOwnProperty]]

var obj = { a: 1, b: 2 };
console.log(obj.hasOwnProperty('a'));

Untitled

获取 [[GET]]

var obj = { a: 1, b: 2 };
console.log('c' in obj);
console.log('a' in obj);
console.log(obj.a);

Untitled

设置 [[SET]]

var obj = { a: 1, b: 2 };
obj.a = 3;
obj['b'] = 4;
console.log(obj);

Untitled

删除 [[Delete]]

var obj = { a: 1, b: 2 };
delete obj.a;
console.log(obj);

Untitled

枚举 [[Enumerate]]

var obj = { a: 1, b: 2 };
for (var k in obj) {
	console.log(obj[k]);
}

Untitled

获取键集合 [[OwnPropertyKeys]]

var obj = { a: 1, b: 2 };
console.log(Object.keys(obj));

Untitled

调用函数

function test() {}
test();

test.call/apply

var obj = { a: 1, b: 2 };
obj.test = function() {};
obj.test();

实例化对象

function Test() {}
new Test();

自定义 Proxy

function MyProxy(target, handler) {
  const _target = deepClone(target);
  Object.keys(_target).forEach(key => {
    Object.defineProperty(_target, key, {
      get() {
        return handler.get && handler.get(target, key);
      },
      set(newVal) {
        handler.set && handler.set(target, key, newVal);
      }
    })
  });
  return _target;

  function deepClone(org, tar) {
    let target = tar || {},
        toStr = Object.prototype.toString,
        arrType = '[object Array]';

    for (var key in org) {
      if (org.hasOwnProperty(key)) {
        let value = org[key];
        if (typeof value === 'object' && value !== null) {
          if (toStr.call(value) === arrType) {
            target[key] = [];
          } else {
            target[key] = {};
          }
          deepClone(value, target[key]);
        } else {
          target[key] = value;
        }
      }
    }
    return target;
  }
}

const obj = { a: 1, b: 2 }

const proxy = new MyProxy(obj, {
  get(obj, prop) {
    console.log('GET: ' + prop + ' = ' + obj[prop]);
    return obj[prop];
  },
  set(obj, prop, newVal) {
    console.log('SET: ' + prop + ' = ' + newVal);
    obj[prop] = newVal;
  }
});

proxy.a = 3;
console.log(proxy);

Untitled

总结

⭐️ defineProperty 和 Proxy 的异同

相同

都能对数据进行“拦截”

不同

本质不同

⭐️ defineProperty 存在的问题

数组通过 索引、修改数组 length、push、pop 等一系列方式操作数组,都无法触发 setter,而 Proxy 可以触发

Vue 对 push、pop 等数组方法进行了重写

Reflect 反射

Untitled

介绍

ES6 中定义的一个内置对象,是方法集合的容器。全局下的一个对象,不能 new

上面讲的14种方法,这里边有13种,少了一个枚举

存在的原因

放在这上面的原因是:

因为很多对象方法,以前都放在 Object 上在,但很多方法不是直接操作 Object,有可能是操作函数、或数组。

由于这样的不合理,所以专门用 Reflect 这样一个容器来装这些方法

使用

使用 Reflect 更合理,因为是用方法返回值

let target = {
	a: 1,
  b: 2
}

let proxy = new Proxy(target, {
	get(target, prop) {
  	// return target[prop];
    return Reflect.get(target, prop);
  },
  set(target, prop, value) {
  	const isOk = Reflect.set(target, prop, value);
    if (isOk) {
      console.log('SET successfully');
    }
  }
});

proxy.a = 233;
console.log(proxy.b);
console.log(target.a);

Untitled