ES5-对象和原型

435 阅读5分钟

本篇对对象和原型的一部分知识补充,其他可以参照以下提供的参考资料

对象

对象是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';  
  1. 如果某个对象 obj 原型上有对应的属性key,且该属性数据属性,且不是只读的,则 obj.key = '' 操作,会在obj上新增一个同名属性;
  2. 如果该属性是数据属性,且是只读的,则不会发生覆盖;
  3. 如果该属性是访问器属性,则执行赋值操作时,会调用setter,其this代表当前对象;

解释一下function

自定义的对象通过某个函数进行构造调用得到,函数对象的生成步骤就不说了(相关可以查询数据类型中对function的说明)

重述一下new运算符

语法:new NewExpression

  1. 一些判断直接略过,比如表达式是不是函数,有没有[[Construct]]属性等
  2. 执行[[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(自定义函数)进行构造函数调用时

  1. 创建一个新的ECMAScript原生对象 obj
  2. 设置 obj.__proto__ = People.prototype
  3. 以obj为this绑定,执行 result = People.call(this, args)
  4. 如果result是对象类型,返回result,否则返回obj

function.prototype

function Class(){}
var obj = new Class();

Class.prototype.sayHello = function(){
    console.info('我是新增在原型上的 sayHello方法')
};

obj.sayHello();

通过对new操作的概述,实际上当构造一个对象的时候,对象的内部属性[[prototype]]会与函数的prototype对象进行关联,进而由于原型查找机制,向 "原型" 上面添加属性,构造对象可以访问

对象只与构造函数对象原型有关,与构造函数本身没有关联

最后放一张图吧

JS原型

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