new 运算符

function Car(make, model, year) {
   this.make = make;
   this.model = model;
   this.year = year;
}

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}



原型链

proto.jpg

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

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

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


instanceof 运算符

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

object instanceof constructor

创建对象

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

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

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

例如:

class Base {
  constructor() {
    this.baseProps = 42;
  }
  
  baseMethod() {
    console.log(this.baseProp);
  }
  
  static foo() {
    console.log("This is foo");
  }
}

Babel 编译结果:

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");
    }
  }]);
}

Object.defineProperty

添加或修改对象的属性。

Object.defineProperty(obj, prop, descriptor)

属性描述符 descriptor

// 数据描述符
var d = {
  enumerable: false,
  writable: false,
  configurable: false,
  value: undefined
};

// 存取描述符
var d1 = {
  enumerable: false,
  configurable: false,
  get : function(){
    return bValue;
  },
  set : function(newValue){
    bValue = newValue;
  },
};
  • enumerable: 是否可以在 for...in 循环和 [Object.keys()](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/keys) 中枚举
  • configurable: 属性是否可删除,以及该属性的 descriptor 是否可修改
var o = {a: 1};
Object.defineProperty(o, "a", { configurable : false } );

// throws a TypeError
Object.defineProperty(o, "a", {configurable : true}); 
// throws a TypeError
Object.defineProperty(o, "a", {enumerable : true}); 
// throws a TypeError (set was undefined previously) 
Object.defineProperty(o, "a", {set : function(){}}); 
// throws a TypeError (even though the new get does exactly the same thing) 
Object.defineProperty(o, "a", {get : function(){return 1;}});
// throws a TypeError
Object.defineProperty(o, "a", {value : 12});

console.log(o.a); // logs 1
o.a = 2; // 属性值依然可以修改
console.log(o.a); // logs 2
delete o.a; // Nothing happens
console.log(o.a); // logs 1

Proxy

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

let p = new Proxy(target, handler);

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

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


/*
  var docCookies = ... get the "docCookies" object here:  
  https://developer.mozilla.org/zh-CN/docs/DOM/document.cookie#A_little_framework.3A_a_complete_cookies_reader.2Fwriter_with_full_unicode_support
*/

var docCookies = new Proxy(docCookies, {
  "get": function (oTarget, sKey) {
    return oTarget[sKey] || oTarget.getItem(sKey) || undefined;
  },
  "set": function (oTarget, sKey, vValue) {
    if (sKey in oTarget) { return false; }
    return oTarget.setItem(sKey, vValue);
  },
  "deleteProperty": function (oTarget, sKey) {
    if (sKey in oTarget) { return false; }
    return oTarget.removeItem(sKey);
  },
  "ownKeys": function (oTarget, sKey) {
    return oTarget.keys();
  },
  "has": function (oTarget, sKey) {
    return sKey in oTarget || oTarget.hasItem(sKey);
  },
  "defineProperty": function (oTarget, sKey, oDesc) {
    if (oDesc && "value" in oDesc) { oTarget.setItem(sKey, oDesc.value); }
    return oTarget;
  },
  "getOwnPropertyDescriptor": function (oTarget, sKey) {
    var vValue = oTarget.getItem(sKey);
    return vValue ? {
      "value": vValue,
      "writable": true,
      "enumerable": true,
      "configurable": false
    } : undefined;
  },
});

/* Cookies 测试 */

alert(docCookies.my_cookie1 = "First value");
alert(docCookies.getItem("my_cookie1"));

docCookies.setItem("my_cookie1", "Changed value");
alert(docCookies.my_cookie1);


var obj = new Proxy({}, {
  get: function (target, propKey, receiver) {
    console.log(`getting ${propKey}!`);
    return Reflect.get(target, propKey, receiver);
  },
  set: function (target, propKey, value, receiver) {
    console.log(`setting ${propKey}!`);
    return Reflect.set(target, propKey, value, receiver);
  }
});

obj.count = 1
//  setting count!
++obj.count
//  getting count!
//  setting count!
//  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 的观察者机制探索

防篡改对象

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

不可扩展对象

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

Object.preventExtensions

设置为不可扩展。

var person = {name: 'Tom'};
Object.preventExtensions(person);

person.age = 29;
console.log(person.age); // undefined

Object.isExtensible

确定对象是否可以扩展。

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

密封对象 sealed object

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



Object.seal

设置为密封对象。

var person = {name: 'Tom'};
Object.seal(person);

person.age = 29;
console.log(person.age); // undefined

delete person.name;
console.log(person.name); // Tom

person.name = 'Ann';
console.log(person.name); // Ann



Object.isSealed

确定对象是否被密封。

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

冻结对象

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

Object.freeze

冻结对象。

var person = {name: 'Tom'};
Object.freeze(person);

person.age = 29;
console.log(person.age); // undefined

delete person.name;
console.log(person.name); // Tom

person.name = 'Ann';
console.log(person.name); // Tom

Object.isFrozen

确定对象是否被冻结。

var person = {name: 'Tom'};
console.log(Object.isExtensible(person)); // true
console.log(Object.isSealed(person)); // false
console.log(Object.isFrozen(person)); // false

Object.freeze(person);
console.log(Object.isExtensible(person)); // false
console.log(Object.isSealed(person)); // true
console.log(Object.isFrozen(person)); // true

继承

从 ECMAScript 6 开始,[[Prototype]] 可以通过 Object.getPrototypeOf()Object.setPrototypeOf() 来访问。

例如:

class Derived extends Base {
    constructor() {
        // Call super constructor (`Base`) to initialize `Base`'s stuff:
        super();

        // Properties to initialize when called:
        this.derivedProp = "the answer";
    }

    // Overridden instance method:
    baseMethod() {
        // Supercall to `baseMethod`:
        super.baseMethod();

        // ...
        console.log("new stuff");
    }

    // Another instance method:
    derivedMethod() {
        this.baseMethod();
        console.log(this.derivedProp);
    }
}

Babel 编译结果:

function _setPrototypeOf(o, p) {
  _setPrototypeOf = Object.setPrototypeOf
      || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; };
  return _setPrototypeOf(o, p);
}


function _inherits(subClass, superClass) {
  if (typeof superClass !== "function" && superClass !== null) {
    throw new TypeError("Super expression must either be null or a function");
  }
  subClass.prototype = Object.create(
    superClass && superClass.prototype,
    {
      constructor: {
        value: subClass,
        writable: true,
        configurable: true
      }
    }
  );
  if (superClass) _setPrototypeOf(subClass, superClass);
}

var Derived =
/*#__PURE__*/
function (_Base) {
  _inherits(Derived, _Base);

  // The code for `Derived`:
  function Derived() {
    var _this;

    _classCallCheck(this, Derived);

    // Call super constructor (`Base`) to initialize `Base`'s stuff:
    _this = _possibleConstructorReturn(this, _getPrototypeOf(Derived).call(this)); // Properties to initialize when called:

    _this.derivedProp = "the answer";
    return _this;
  } // Overridden instance method:


  _createClass(Derived, [{
    key: "baseMethod",
    value: function baseMethod() {
      // Supercall to `baseMethod`:
      _get(_getPrototypeOf(Derived.prototype), "baseMethod", this).call(this); // ...


      console.log("new stuff");
    } // Another instance method:

  }, {
    key: "derivedMethod",
    value: function derivedMethod() {
      this.baseMethod();
      console.log(this.derivedProp);
    }
  }]);

  return Derived;
}(Base);

Object.setPrototypeOf() 方法

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

Object.setPrototypeOf(obj, prototype)

Polyfill:

function _setPrototypeOf(o, p) {
  _setPrototypeOf = Object.setPrototypeOf
      || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; };
  return _setPrototypeOf(o, p);
}

Object.create()方法

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

Object.create(proto[, propertiesObject])

例子:

Object.create({}, {
  // foo会成为所创建对象的数据属性
  foo: { 
    writable:true,
    configurable:true,
    value: "hello" 
  },
  // bar会成为所创建对象的访问器属性
  bar: {
    configurable: false,
    get: function() { return 10 },
    set: function(value) {
      console.log("Setting `o.bar` to", value);
    }
  }
});

Jane_Shen
629 声望4 粉丝