本篇对对象和原型的一部分知识补充,其他可以参照以下提供的参考资料
对象
对象是JavaScript中的基石,原型作为JavaScript中完成面向对象的重要理论,封装由闭包体现私有和保护;继承的功能由原型承担;至于多态,JavaScript是弱类型的,本身就已经达到了多态的基本要求吧。
创建对象
var obj = {}; // 直接量创建
function _create(p){ // 间接等同于Object.create
// 这里不做任何必要的判断
var f = function(){}
f.prototype = p;
return new f;
}
obj = new Object(); // 函数构造创建
属性的操作
通过对算符 [] 和 . 进行表达式的处理,ES5增加了对象属性的一些描述。
数据属性
我们经常在某个对象上定义的属性都是数据属性,基本由 value,enumerable、writable、configurable 四个特性组成
var obj = {key: 'keyValue' };
console.info(Object.getOwnPropertyDescriptor(obj, 'key'));
// {value: "keyValue", writable: true, enumerable: true, configurable: true}
// 基本通过直接量定义的对应的值都是true
含义:
value: 表示属性代表的值
enumerable: 表示是否可以进行枚举
var obj = {key: 'keyValue' };
console.info('key' in obj, Object.keys(obj)); // true ["key"]
Object.defineProperty(obj, 'key', {
enumerable: false
});
console.info(Object.keys(obj)); // []
for(var attr in obj){console.info(attr)} // for...in也无法遍历到
writable: 表示是否可以进行赋值操作
var obj = {key: 'keyValue' };
Object.defineProperty(obj, 'key', {
writable : false
});
obj.key = "newValue";
console.info(obj); // 依旧是 keyValue, 注意在严格模式下,对只读属性重新赋值会报错
configurable: 是否可以进行配置,该属性一旦设置为false,就无法在继续更改为true
var obj = {key: 'keyValue' };
Object.defineProperty(obj, 'key', {
configurable : false
});
obj.key = "newValue";
Object.defineProperty(obj, 'key', { // 不能在重新定义其他特性,但是writable可以改为false
writable: false
});
obj.key = "newValue2";
console.info(Object.getOwnPropertyDescriptor(obj, 'key'));
// 结果:{value: "newValue", writable: false, enumerable: true, configurable: false}
访问器属性
除了enumerable、configurable两个特性一致之外,其他两个分别有 set 和 get 所替代
var obj = {
set key(val){},
get key(){}
}
ES5新增对象的扩展
以下操作不可逆,只会影响到自己,不会影响到原型,谨慎使用
Object.preventExtensions()/Object.isExtensible()
Object.seal()/Object.isSealed() // 不能添加新的属性、已有属性不可删除或配置,已有可写属性可以设置
Object.freeze()/Object.isFrozen // 所有属性不可配置,所有数据属性设置为只读,如果有setter存储器,则不受影响[可以设值]
原型
原型是一种机制,是一种属性查询机制,每个对象都有一个[[Prototype]]的内部属性,间接可以用__proto__属性体现 (不谈 Object.create(null)),这个属性理论上都会被赋予一个非空的值,以此来构成原型链。对于以下代码:
var obj = {
key: 'value'
};
obj.key;
对于 obj.key 来说,即属性表达式调用,获取对应的 引用类型,并执行 [[GetValue]] 操作,执行对象内部 [[Get]] 操作,判断是否存在于自身,如果没有,进而执行 [[GetProperty]] 递归寻找其原型,直至找到对应的属性 或 没有。
var proto = {
pKey: 'pVal'
};
var obj = Object.create(proto, {
key: {
value: 'value'
}
});
console.info(obj);
// 我就不截图了, obj.__proto__ = proto = {pKey: 'pVal'}
JavaScript的原型链是有尽头的,尽头是 Object.prototype.__proto__ = null,就像Java一样,类继承的源头就是Object [我当初了解的是这样]。
属性的设置和屏蔽
function Parent(){}
Parent.prototype.simpleValue = 'simpleValue';
Parent.prototype._setValue = '';
Object.defineProperty(Parent.prototype, 'noWritbale', {
value: 'noWritbale'
});
Object.defineProperty(Parent.prototype, 'setValue', {
set: function(value){
this._setValue = value;
},
get: function(){
return this._setValue;
}
});
function Child(){
this.name = "childName";
}
Child.prototype = Object.create(Parent.prototype);
var childObject = new Child;
// 因为是只读属性,所以不会生效
childObject.noWritbale = 'new noWritbale';
// 这个时候childObject本身会生成一个新的属性
childObject.simpleValue = 'new simpleValue';
// this._setValue = value; this会指示childObject,也就是会在childObject上生成一个 _setValue 的新属性
childObject.setValue = 'new setValue';
- 如果某个对象 obj 原型上有对应的属性key,且该属性数据属性,且不是只读的,则 obj.key = '' 操作,会在obj上新增一个同名属性;
- 如果该属性是数据属性,且是只读的,则不会发生覆盖;
- 如果该属性是访问器属性,则执行赋值操作时,会调用setter,其this代表当前对象;
解释一下function
自定义的对象通过某个函数进行构造调用得到,函数对象的生成步骤就不说了(相关可以查询数据类型中对function的说明)
重述一下new运算符
语法:new NewExpression
- 一些判断直接略过,比如表达式是不是函数,有没有[[Construct]]属性等
- 执行[[Construct]]操作, 令 F 为表达式的构造函数
- 创建一个新的ECMAScript原生对象 obj
- 设置 obj.[[Class]] = "Object", obj[[Extensible]] = true;
- 获取 proto = F.prototype, 设定 obj.[[prototype]] = proto, 即obj.__proto__ = proto;
- 以 obj 为this值,调用 F , result = F.call(this, args);
- 如果result是对象类型,返回result,否则返回obj
简要说明一下,当进行new People(自定义函数)进行构造函数调用时
- 创建一个新的ECMAScript原生对象 obj
- 设置 obj.__proto__ = People.prototype
- 以obj为this绑定,执行 result = People.call(this, args)
- 如果result是对象类型,返回result,否则返回obj
function.prototype
function Class(){}
var obj = new Class();
Class.prototype.sayHello = function(){
console.info('我是新增在原型上的 sayHello方法')
};
obj.sayHello();
通过对new操作的概述,实际上当构造一个对象的时候,对象的内部属性[[prototype]]会与函数的prototype对象进行关联,进而由于原型查找机制,向 "原型" 上面添加属性,构造对象可以访问
对象只与构造函数对象原型有关,与构造函数本身没有关联
最后放一张图吧

参考资料:
《You Don't Know JS: Types & Grammar》上卷
《JavaScript 高级程序设计》
《JavaScript 权威指南》