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(...)执行时:- 创建一个新的 Object(继承 Car.prototype)
- 调用构造函数 Car(…),
this绑定到上步中新创建的 Object 上 - 返回上步构造函数的结果(mycar)
- 若构造函数 Car(…) 中没有 return 或 return 的类型不是 Object,例如
return 'abc',则 mycar 为前步中新创建的 Object - 若构造函数 Car(…) 返回了一个 Object,例如
return {a: 1},则 mycar 为这个 Object: {a: 1}简单实现
function newMethod(Parent, ...args) {const obj = Object.create(Parent.prototype);Parent.apply(obj, args);return obj;}
new.target
new.target 属性允许你检测函数或构造方法是否是通过 new 运算符被调用的。在通过 new 运算符被初始化的函数或构造方法中,new.target 返回一个指向构造方法或函数的引用。在普通的函数调用中,new.target 的值是 undefined。
- 若构造函数 Car(…) 中没有 return 或 return 的类型不是 Object,例如
在箭头函数(arrow function)中,new.target 指向最近外层函数的 new.target。
function Foo() {if (!new.target) throw "Foo() must be called with new";console.log("Foo instantiated with new");}Foo(); // throws "Foo() must be called with new"new Foo(); // logs "Foo instantiated with new"
class A {constructor() {console.log(new.target.name);}}class B extends A { constructor() { super(); } }var a = new A(); // logs "A"var b = new B(); // logs "B"class C { constructor() { console.log(new.target); } }class D extends C { constructor() { super(); } }var c = new C(); // logs class C{constructor(){console.log(new.target);}}var d = new D(); // logs class D extends C{constructor(){super();}}
原型链
几乎所有对象都是 Object 的实例。
new 运算符中提到的例子的原型链:
- [[Prototype]]:实例对象的私有属性,指向其构造函数的原型对象
- prototype:指向原型对象
- constructor:指向构造函数
instanceof运算符instanceof运算符用来检测constructor.prototype是否存在于参数object的原型链上。object instanceof constructor
创建对象
虽然 new 运算符 或对象字面量都可以用来创建对象,但是为了解决重复代码问题,往往会使用一些设计模式:
- 工厂模式
- 构造函数模式
- 原型模式
- 组合使用构造函数模式和原型模式(常用)
- 动态原型模式
- 寄生构造函数模式
- 稳妥构造函数模式
类 class
通过 Babel 编译后的代码,实际上就是用组合使用构造函数模式和原型模式实现的 class。
例如:
class Base {constructor() {this.baseProps = 42;}baseMethod() {console.log(this.baseProp);}static foo() {console.log("This is foo");}}
Babel 编译结果:
- 创建构造函数,在构造函数中定义属性;
- 在原型上定义方法 ```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”); } }]); }
<a name="oXSMn"></a>## 对象属性<a name="qcMQa"></a>### 返回一个对象包含的属性的数量- Object.keys():包括可枚举属性,不包括Symbol值作为名称的属性- `Object.getOwnPropertyNames()`:包括不可枚举属性但不包括Symbol值作为名称的属性- Reflect.ownKeys():包括不可枚举属性和Symbol值作为名称的属性<a name="HtKzP"></a>### Object.defineProperty添加或修改对象的属性。```javascriptObject.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 TypeErrorObject.defineProperty(o, "a", {configurable : true});// throws a TypeErrorObject.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 TypeErrorObject.defineProperty(o, "a", {value : 12});console.log(o.a); // logs 1o.a = 2; // 属性值依然可以修改console.log(o.a); // logs 2delete o.a; // Nothing happensconsole.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 来实现观察者机制(数据双向绑定),但是:
- 数组变化的监听仅仅局限于几个方法:push、pop、shift、unshift、splice、sort、reverse。(Object.defineProperty本身是可以监控到数组下标的变化的,但 Vue 作者在对性能和用户体验衡量后,阻止了对数组下标变化的监听)
- 由于 Object.defineProperty 仅限于对象的单个属性,所以 Vue 是通过递归和遍历 data 对象来实现对数据的监听。如果对象的属性也是一个对象,则需要深度遍历,那么“拦截”对象显然要比“拦截”对象的单个属性更好。
Proxy 优点:
- “拦截”整个对象
- 提供的方法多
Proxy 缺点:
- 没有兼容的 polyfill
参考链接:vue3.0 尝鲜 — 摒弃 Object.defineProperty,基于 Proxy 的观察者机制探索
var obj = {prop: function() {},foo: 'bar'};// 这种实现有问题,Object.freeze 冻结不了 obj.foo,set 仍然生效,打印结果为 as// var bValue = '';// Object.defineProperty(obj, 'foo', {// get: function() {// return bValue;// },// set: function(v) {// bValue = v + 's';// }// });obj = new Proxy(obj, {get: function(target, propKey, receiver) {return Reflect.get(target, propKey, receiver);},set: function(target, propKey, value, receiver) {if (propKey === 'foo') value += 's';return Reflect.set(target, propKey, value, receiver);}});Object.freeze(obj);console.log(`${obj.foo}`);obj.foo = 'a';console.log(`${obj.foo}`);
防篡改对象
上述讨论了如何设置对象属性特性,修改属性的行为。但有时候出于安全等目的,需要对象不可篡改。
注意:一旦把对象定义为防篡改,就无法撤销了。
不可扩展对象
不可扩展,指的是不能给对象添加新的属性和方法,但是仍然可以修改和删除已有的属性和方法。
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)); // trueObject.preventExtensions(person);console.log(Object.isExtensible(person)); // false
密封对象 sealed object
- 不可扩展
- 对象成员的 configurable 特性为 false,即属性不能删除,属性的 descriptor 不可修改,但是属性值可以修改
Object.seal
设置为密封对象。
var person = {name: 'Tom'};Object.seal(person);person.age = 29;console.log(person.age); // undefineddelete person.name;console.log(person.name); // Tomperson.name = 'Ann';console.log(person.name); // Ann
Object.isSealed
确定对象是否被密封。
var person = {name: 'Tom'};console.log(Object.isSealed(person)); // falseObject.seal(person);console.log(Object.isSealed(person)); // true
冻结对象
- 不可扩展
- 对象成员的 configurable 特性为 false,即属性不能删除,属性的 descriptor 不可修改
- 对象成员的 writable 特性为 false
Object.freeze
冻结对象。
var person = {name: 'Tom'};Object.freeze(person);person.age = 29;console.log(person.age); // undefineddelete person.name;console.log(person.name); // Tomperson.name = 'Ann';console.log(person.name); // Tom
Object.isFrozen
确定对象是否被冻结。
var person = {name: 'Tom'};console.log(Object.isExtensible(person)); // trueconsole.log(Object.isSealed(person)); // falseconsole.log(Object.isFrozen(person)); // falseObject.freeze(person);console.log(Object.isExtensible(person)); // falseconsole.log(Object.isSealed(person)); // trueconsole.log(Object.isFrozen(person)); // true
继承
SubClass 继承 SuperClass,实际上就是 SubClass 的原型指向了 SuperClass 的原型。
从 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);
寄生组合式继承
subClass.prototype = Object.create(superClass && superClass.prototype,{constructor: {value: subClass,writable: true,configurable: true}});
为什么要手动设置 constructor ?
参考Javascript 高级程序设计 6.2.3 原型模式这一章节,当 subClass.prototype 设置为一个新对象时,constructor 属性变成了这个新对象的 constructor 属性,而没有指向 subClass,这时就需要手动设置正确的 constructor。
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);}}});
new Object() 和 Object.create()的区别:
- 创建对象的方式
- 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
2. 对象属性描述符```javascript// 默认 configurable/enumerable/writable 为 falselet obj = Object.create({}, { age: { value: 18 } })console.log(Object.getOwnPropertyDescriptors(obj))>{age: {value: 18,writable: false,enumerable: false,configurable: false}}// 手动添加属性,默认 configurable/enumerable/writable 为 truelet obj = Object.create(null)obj.age = { value: 18 }console.log(Object.getOwnPropertyDescriptors(obj)){age: {value: { value: 18 },writable: true,enumerable: true,configurable: true}}
- 创建空对象时,是否有原型属性 ```javascript console.dir(new Object()) //{} > Object proto:
console.dir(Object.create(null))
Object No properties ```
