new 运算符

  1. function Car(make, model, year) {
  2. this.make = make;
  3. this.model = model;
  4. this.year = year;
  5. }
  6. var mycar = new Car("Eagle", "Talon TSi", 1993);
  • Car(...):构造函数
  • mycar:对象实例
  • new Car(...) 执行时:
    1. 创建一个新的 Object(继承 Car.prototype)
    2. 调用构造函数 Car(…),this 绑定到上步中新创建的 Object 上
    3. 返回上步构造函数的结果(mycar)
      1. 若构造函数 Car(…) 中没有 return 或 return 的类型不是 Object,例如 return 'abc',则 mycar 为前步中新创建的 Object
      2. 若构造函数 Car(…) 返回了一个 Object,例如 return {a: 1},则 mycar 为这个 Object: {a: 1}

        简单实现

        1. function newMethod(Parent, ...args) {
        2. const obj = Object.create(Parent.prototype);
        3. Parent.apply(obj, args);
        4. return obj;
        5. }

        new.target

        new.target 属性允许你检测函数或构造方法是否是通过 new 运算符被调用的。在通过 new 运算符被初始化的函数或构造方法中,new.target 返回一个指向构造方法或函数的引用。在普通的函数调用中,new.target 的值是 undefined。

在箭头函数(arrow function)中,new.target 指向最近外层函数的 new.target。

  1. function Foo() {
  2. if (!new.target) throw "Foo() must be called with new";
  3. console.log("Foo instantiated with new");
  4. }
  5. Foo(); // throws "Foo() must be called with new"
  6. new Foo(); // logs "Foo instantiated with new"
  1. class A {
  2. constructor() {
  3. console.log(new.target.name);
  4. }
  5. }
  6. class B extends A { constructor() { super(); } }
  7. var a = new A(); // logs "A"
  8. var b = new B(); // logs "B"
  9. class C { constructor() { console.log(new.target); } }
  10. class D extends C { constructor() { super(); } }
  11. var c = new C(); // logs class C{constructor(){console.log(new.target);}}
  12. var d = new D(); // logs class D extends C{constructor(){super();}}

原型链

由 new 运算符开始的 Object 知识点整理 - 图1

几乎所有对象都是 Object 的实例。

new 运算符中提到的例子的原型链:
prototype.png

  • [[Prototype]]:实例对象的私有属性,指向其构造函数的原型对象
  • prototype:指向原型对象
  • constructor:指向构造函数


    instanceof 运算符

    instanceof 运算符用来检测 constructor.prototype是否存在于参数 object 的原型链上。
    1. object instanceof constructor

创建对象

虽然 new 运算符 或对象字面量都可以用来创建对象,但是为了解决重复代码问题,往往会使用一些设计模式:

  • 工厂模式
  • 构造函数模式
  • 原型模式
  • 组合使用构造函数模式和原型模式(常用
  • 动态原型模式
  • 寄生构造函数模式
  • 稳妥构造函数模式


class

通过 Babel 编译后的代码,实际上就是用组合使用构造函数模式和原型模式实现的 class。

例如:

  1. class Base {
  2. constructor() {
  3. this.baseProps = 42;
  4. }
  5. baseMethod() {
  6. console.log(this.baseProp);
  7. }
  8. static foo() {
  9. console.log("This is foo");
  10. }
  11. }

Babel 编译结果:

  1. 创建构造函数,在构造函数中定义属性;
  2. 在原型上定义方法 ```javascript function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (“value” in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }

function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }

var Base = function(){ function Base() { if (!(this instanceof Base)) throw new TypeError(“Cannot call a class as a function”); this.baseProp = 42; } _createClass(Base, [{ key: “baseMethod”, value: function baseMethod() { console.log(this.baseProp); } // A method to put on the constructor (a “static method”):

}], [{ key: “foo”, value: function foo() { console.log(“This is foo”); } }]); }

  1. <a name="oXSMn"></a>
  2. ## 对象属性
  3. <a name="qcMQa"></a>
  4. ### 返回一个对象包含的属性的数量
  5. - Object.keys():包括可枚举属性,不包括Symbol值作为名称的属性
  6. - `Object.getOwnPropertyNames()`:包括不可枚举属性但不包括Symbol值作为名称的属性
  7. - Reflect.ownKeys():包括不可枚举属性和Symbol值作为名称的属性
  8. <a name="HtKzP"></a>
  9. ### Object.defineProperty
  10. 添加或修改对象的属性。
  11. ```javascript
  12. Object.defineProperty(obj, prop, descriptor)

属性描述符 descriptor

  1. // 数据描述符
  2. var d = {
  3. enumerable: false,
  4. writable: false,
  5. configurable: false,
  6. value: undefined
  7. };
  8. // 存取描述符
  9. var d1 = {
  10. enumerable: false,
  11. configurable: false,
  12. get : function(){
  13. return bValue;
  14. },
  15. set : function(newValue){
  16. bValue = newValue;
  17. },
  18. };
  • enumerable: 是否可以在 for...in 循环和 [Object.keys()](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/keys) 中枚举
  • configurable: 属性是否可删除,以及该属性的 descriptor 是否可修改
  1. var o = {a: 1};
  2. Object.defineProperty(o, "a", { configurable : false } );
  3. // throws a TypeError
  4. Object.defineProperty(o, "a", {configurable : true});
  5. // throws a TypeError
  6. Object.defineProperty(o, "a", {enumerable : true});
  7. // throws a TypeError (set was undefined previously)
  8. Object.defineProperty(o, "a", {set : function(){}});
  9. // throws a TypeError (even though the new get does exactly the same thing)
  10. Object.defineProperty(o, "a", {get : function(){return 1;}});
  11. // throws a TypeError
  12. Object.defineProperty(o, "a", {value : 12});
  13. console.log(o.a); // logs 1
  14. o.a = 2; // 属性值依然可以修改
  15. console.log(o.a); // logs 2
  16. delete o.a; // Nothing happens
  17. console.log(o.a); // logs 1

Proxy

ES6 提供的新特性,Proxy 对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。

  1. let p = new Proxy(target, handler);

defineProperty 一样,也能重定义属性的读取(get)和设置(set)行为,只是 defineProperty 是“拦截”单个属性,Proxy 则是“拦截”一个对象。

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。


  1. /*
  2. var docCookies = ... get the "docCookies" object here:
  3. https://developer.mozilla.org/zh-CN/docs/DOM/document.cookie#A_little_framework.3A_a_complete_cookies_reader.2Fwriter_with_full_unicode_support
  4. */
  5. var docCookies = new Proxy(docCookies, {
  6. "get": function (oTarget, sKey) {
  7. return oTarget[sKey] || oTarget.getItem(sKey) || undefined;
  8. },
  9. "set": function (oTarget, sKey, vValue) {
  10. if (sKey in oTarget) { return false; }
  11. return oTarget.setItem(sKey, vValue);
  12. },
  13. "deleteProperty": function (oTarget, sKey) {
  14. if (sKey in oTarget) { return false; }
  15. return oTarget.removeItem(sKey);
  16. },
  17. "ownKeys": function (oTarget, sKey) {
  18. return oTarget.keys();
  19. },
  20. "has": function (oTarget, sKey) {
  21. return sKey in oTarget || oTarget.hasItem(sKey);
  22. },
  23. "defineProperty": function (oTarget, sKey, oDesc) {
  24. if (oDesc && "value" in oDesc) { oTarget.setItem(sKey, oDesc.value); }
  25. return oTarget;
  26. },
  27. "getOwnPropertyDescriptor": function (oTarget, sKey) {
  28. var vValue = oTarget.getItem(sKey);
  29. return vValue ? {
  30. "value": vValue,
  31. "writable": true,
  32. "enumerable": true,
  33. "configurable": false
  34. } : undefined;
  35. },
  36. });
  37. /* Cookies 测试 */
  38. alert(docCookies.my_cookie1 = "First value");
  39. alert(docCookies.getItem("my_cookie1"));
  40. docCookies.setItem("my_cookie1", "Changed value");
  41. alert(docCookies.my_cookie1);


  1. var obj = new Proxy({}, {
  2. get: function (target, propKey, receiver) {
  3. console.log(`getting ${propKey}!`);
  4. return Reflect.get(target, propKey, receiver);
  5. },
  6. set: function (target, propKey, value, receiver) {
  7. console.log(`setting ${propKey}!`);
  8. return Reflect.set(target, propKey, value, receiver);
  9. }
  10. });
  11. obj.count = 1
  12. // setting count!
  13. ++obj.count
  14. // getting count!
  15. // setting count!
  16. // 2

上述代码中提到的 Reflect,也是 ES6 中的一个新特性。

Reflect

Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与 Proxy handlers的方法相同。

  • Reflect.apply(target, thisArg, args)
  • Reflect.construct(target, args)
  • Reflect.get(target, name, receiver)
  • Reflect.set(target, name, value, receiver)
  • Reflect.defineProperty(target, name, desc)
  • Reflect.deleteProperty(target, name)
  • Reflect.has(target, name)
  • Reflect.ownKeys(target)
  • Reflect.isExtensible(target)
  • Reflect.preventExtensions(target)
  • Reflect.getOwnPropertyDescriptor(target, name)
  • Reflect.getPrototypeOf(target)
  • Reflect.setPrototypeOf(target, prototype)


Vue 3.x 观察者机制:使用 Proxy 替代 Object.defineProperty

vue 3 之前的版本都是使用 Object.defineProperty 来实现观察者机制(数据双向绑定),但是:

  1. 数组变化的监听仅仅局限于几个方法:push、pop、shift、unshift、splice、sort、reverse。(Object.defineProperty本身是可以监控到数组下标的变化的,但 Vue 作者在对性能和用户体验衡量后,阻止了对数组下标变化的监听)
  2. 由于 Object.defineProperty 仅限于对象的单个属性,所以 Vue 是通过递归和遍历 data 对象来实现对数据的监听。如果对象的属性也是一个对象,则需要深度遍历,那么“拦截”对象显然要比“拦截”对象的单个属性更好。

Proxy 优点:

  1. “拦截”整个对象
  2. 提供的方法多

Proxy 缺点:

  1. 没有兼容的 polyfill

参考链接:vue3.0 尝鲜 — 摒弃 Object.defineProperty,基于 Proxy 的观察者机制探索

  1. var obj = {
  2. prop: function() {},
  3. foo: 'bar'
  4. };
  5. // 这种实现有问题,Object.freeze 冻结不了 obj.foo,set 仍然生效,打印结果为 as
  6. // var bValue = '';
  7. // Object.defineProperty(obj, 'foo', {
  8. // get: function() {
  9. // return bValue;
  10. // },
  11. // set: function(v) {
  12. // bValue = v + 's';
  13. // }
  14. // });
  15. obj = new Proxy(obj, {
  16. get: function(target, propKey, receiver) {
  17. return Reflect.get(target, propKey, receiver);
  18. },
  19. set: function(target, propKey, value, receiver) {
  20. if (propKey === 'foo') value += 's';
  21. return Reflect.set(target, propKey, value, receiver);
  22. }
  23. });
  24. Object.freeze(obj);
  25. console.log(`${obj.foo}`);
  26. obj.foo = 'a';
  27. console.log(`${obj.foo}`);


防篡改对象

上述讨论了如何设置对象属性特性,修改属性的行为。但有时候出于安全等目的,需要对象不可篡改。
注意:一旦把对象定义为防篡改,就无法撤销了。

不可扩展对象

不可扩展,指的是不能给对象添加新的属性和方法,但是仍然可以修改和删除已有的属性和方法。

Object.preventExtensions

设置为不可扩展。

  1. var person = {name: 'Tom'};
  2. Object.preventExtensions(person);
  3. person.age = 29;
  4. console.log(person.age); // undefined

Object.isExtensible

确定对象是否可以扩展。

  1. var person = {name: 'Tom'};
  2. console.log(Object.isExtensible(person)); // true
  3. Object.preventExtensions(person);
  4. console.log(Object.isExtensible(person)); // false

密封对象 sealed object

  1. 不可扩展
  2. 对象成员的 configurable 特性为 false,即属性不能删除,属性的 descriptor 不可修改,但是属性值可以修改


Object.seal

设置为密封对象。

  1. var person = {name: 'Tom'};
  2. Object.seal(person);
  3. person.age = 29;
  4. console.log(person.age); // undefined
  5. delete person.name;
  6. console.log(person.name); // Tom
  7. person.name = 'Ann';
  8. console.log(person.name); // Ann


Object.isSealed

确定对象是否被密封。

  1. var person = {name: 'Tom'};
  2. console.log(Object.isSealed(person)); // false
  3. Object.seal(person);
  4. console.log(Object.isSealed(person)); // true

冻结对象

  1. 不可扩展
  2. 对象成员的 configurable 特性为 false,即属性不能删除,属性的 descriptor 不可修改
  3. 对象成员的 writable 特性为 false

Object.freeze

冻结对象。

  1. var person = {name: 'Tom'};
  2. Object.freeze(person);
  3. person.age = 29;
  4. console.log(person.age); // undefined
  5. delete person.name;
  6. console.log(person.name); // Tom
  7. person.name = 'Ann';
  8. console.log(person.name); // Tom

Object.isFrozen

确定对象是否被冻结。

  1. var person = {name: 'Tom'};
  2. console.log(Object.isExtensible(person)); // true
  3. console.log(Object.isSealed(person)); // false
  4. console.log(Object.isFrozen(person)); // false
  5. Object.freeze(person);
  6. console.log(Object.isExtensible(person)); // false
  7. console.log(Object.isSealed(person)); // true
  8. console.log(Object.isFrozen(person)); // true

继承

SubClass 继承 SuperClass,实际上就是 SubClass 的原型指向了 SuperClass 的原型。
从 ECMAScript 6 开始,[[Prototype]] 可以通过 Object.getPrototypeOf()Object.setPrototypeOf() 来访问。

例如:

  1. class Derived extends Base {
  2. constructor() {
  3. // Call super constructor (`Base`) to initialize `Base`'s stuff:
  4. super();
  5. // Properties to initialize when called:
  6. this.derivedProp = "the answer";
  7. }
  8. // Overridden instance method:
  9. baseMethod() {
  10. // Supercall to `baseMethod`:
  11. super.baseMethod();
  12. // ...
  13. console.log("new stuff");
  14. }
  15. // Another instance method:
  16. derivedMethod() {
  17. this.baseMethod();
  18. console.log(this.derivedProp);
  19. }
  20. }

Babel 编译结果:

  1. function _setPrototypeOf(o, p) {
  2. _setPrototypeOf = Object.setPrototypeOf
  3. || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; };
  4. return _setPrototypeOf(o, p);
  5. }
  6. function _inherits(subClass, superClass) {
  7. if (typeof superClass !== "function" && superClass !== null) {
  8. throw new TypeError("Super expression must either be null or a function");
  9. }
  10. subClass.prototype = Object.create(
  11. superClass && superClass.prototype,
  12. {
  13. constructor: {
  14. value: subClass,
  15. writable: true,
  16. configurable: true
  17. }
  18. }
  19. );
  20. if (superClass) _setPrototypeOf(subClass, superClass);
  21. }
  22. var Derived =
  23. /*#__PURE__*/
  24. function (_Base) {
  25. _inherits(Derived, _Base);
  26. // The code for `Derived`:
  27. function Derived() {
  28. var _this;
  29. _classCallCheck(this, Derived);
  30. // Call super constructor (`Base`) to initialize `Base`'s stuff:
  31. _this = _possibleConstructorReturn(this, _getPrototypeOf(Derived).call(this)); // Properties to initialize when called:
  32. _this.derivedProp = "the answer";
  33. return _this;
  34. } // Overridden instance method:
  35. _createClass(Derived, [{
  36. key: "baseMethod",
  37. value: function baseMethod() {
  38. // Supercall to `baseMethod`:
  39. _get(_getPrototypeOf(Derived.prototype), "baseMethod", this).call(this); // ...
  40. console.log("new stuff");
  41. } // Another instance method:
  42. }, {
  43. key: "derivedMethod",
  44. value: function derivedMethod() {
  45. this.baseMethod();
  46. console.log(this.derivedProp);
  47. }
  48. }]);
  49. return Derived;
  50. }(Base);

寄生组合式继承

  1. subClass.prototype = Object.create(
  2. superClass && superClass.prototype,
  3. {
  4. constructor: {
  5. value: subClass,
  6. writable: true,
  7. configurable: true
  8. }
  9. }
  10. );

为什么要手动设置 constructor ?
参考Javascript 高级程序设计 6.2.3 原型模式这一章节,当 subClass.prototype 设置为一个新对象时,constructor 属性变成了这个新对象的 constructor 属性,而没有指向 subClass,这时就需要手动设置正确的 constructor。

Object.setPrototypeOf() 方法

设置一个指定的对象的原型 ( 即, 内部[[Prototype]]属性)到另一个对象或 null。

  1. Object.setPrototypeOf(obj, prototype)

Polyfill:

  1. function _setPrototypeOf(o, p) {
  2. _setPrototypeOf = Object.setPrototypeOf
  3. || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; };
  4. return _setPrototypeOf(o, p);
  5. }

Object.create()方法

创建一个新对象,使用现有的对象来提供新创建的对象的 [[Prototype]]。

  1. Object.create(proto[, propertiesObject])

例子:

  1. Object.create({}, {
  2. // foo会成为所创建对象的数据属性
  3. foo: {
  4. writable:true,
  5. configurable:true,
  6. value: "hello"
  7. },
  8. // bar会成为所创建对象的访问器属性
  9. bar: {
  10. configurable: false,
  11. get: function() { return 10 },
  12. set: function(value) {
  13. console.log("Setting `o.bar` to", value);
  14. }
  15. }
  16. });

new Object() 和 Object.create()的区别:

  1. 创建对象的方式
    • new Object() 方式:通过构造函数来创建对象, 添加的属性是在自身实例下
    • Object.create() 方式:继承一个对象, 添加的属性是在原型下 ```javascript let a = { fruit : ‘apple’ } let b = new Object(a) console.log(b) // {fruit: “apple”} console.log(b.proto) // {} console.log(b.fruit) // apple

let a = { fruit: ‘apple’ } let b = Object.create(a) console.log(b) // {} console.log(b.proto) // {fruit: “apple”} console.log(b.fruit) // apple

  1. 2. 对象属性描述符
  2. ```javascript
  3. // 默认 configurable/enumerable/writable 为 false
  4. let obj = Object.create({}, { age: { value: 18 } })
  5. console.log(Object.getOwnPropertyDescriptors(obj))
  6. >
  7. {
  8. age: {
  9. value: 18,
  10. writable: false,
  11. enumerable: false,
  12. configurable: false
  13. }
  14. }
  15. // 手动添加属性,默认 configurable/enumerable/writable 为 true
  16. let obj = Object.create(null)
  17. obj.age = { value: 18 }
  18. console.log(Object.getOwnPropertyDescriptors(obj))
  19. {
  20. age: {
  21. value: { value: 18 },
  22. writable: true,
  23. enumerable: true,
  24. configurable: true
  25. }
  26. }
  1. 创建空对象时,是否有原型属性 ```javascript console.dir(new Object()) //{} > Object proto:

console.dir(Object.create(null))

Object No properties ```