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}
- 若构造函数 Car(...) 中没有 return 或 return 的类型不是 Object,例如
原型链
几乎所有对象都是 Object 的实例。
new 运算符中提到的例子的原型链:
- [[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 来实现观察者机制(数据双向绑定),但是:
- 数组变化的监听仅仅局限于几个方法:push、pop、shift、unshift、splice、sort、reverse。(Object.defineProperty本身是可以监控到数组下标的变化的,但 Vue 作者在对性能和用户体验衡量后,阻止了对数组下标变化的监听)
- 由于 Object.defineProperty 仅限于对象的单个属性,所以 Vue 是通过递归和遍历 data 对象来实现对数据的监听。如果对象的属性也是一个对象,则需要深度遍历,那么“拦截”对象显然要比“拦截”对象的单个属性更好。
Proxy 优点:
- “拦截”整个对象
- 提供的方法多
Proxy 缺点:
- 没有兼容的 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
- 不可扩展
- 对象成员的 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
冻结对象
- 不可扩展
- 对象成员的 configurable 特性为 false,即属性不能删除,属性的 descriptor 不可修改
- 对象成员的 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);
}
}
});
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。