原型对象 实战使用

一般地,javascript使用构造函数和原型对象来进行面向对象编程,它们的表现与其他面向对象编程语言中的“类”相似而又不同。

上一篇文章 已经做过对 构造函数 和 原型对象 的简单介绍。在这篇文章主要介绍使用实战,你可不要小看“实战”,坑点还是挺多的。

原型对象 属性的查找

对象属性查找,经常会查找到原型对象上:

当读取一个对象的属性时,javascript引擎首先在该对象的自有属性中查找属性名字。如果找到则返回。如果自有属性不包含该名字,则javascript会沿着__proto__从原型对象上找。如果找到则返回。如果一直往上都找不到,则返回undefined

var o = {};
console.log(o.toString());//'[object Object]'

o.toString = function(){
    return 'o';
}
console.log(o.toString());//'o'

delete o.toString;
console.log(o.toString());//'[objet Object]'

in

in操作符可以判断属性在不在该对象上,但无法区别自有还是继承属性:

function Test(){};
var obj = new Test;
Test.prototype.a = 1;
obj.b = 2;
console.log('a' in obj);//true
console.log('b' in obj);//true
console.log('b' in Test.prototype);//false

hasOwnProperty()

通过hasOwnProperty()方法可以确定该属性是自有属性还是继承属性:

var o = {a:1};
var obj = Object.create(o);
obj.b = 2;
console.log(obj.hasOwnProperty('a'));//false
console.log(obj.hasOwnProperty('b'));//true

于是可以将hasOwnProperty方法和in运算符结合起来使用,用来鉴别原型属性

function hasPrototypeProperty(object,name){
    return name in object && !object.hasOwnProperty(name);
}

原型对象 属性的添加

点运算添加属性

function Person(name){
    this.name = name;
}
Person.prototype.sayName = function(){
    console.log(this.name);
}
var person1 = new Person('bai');
var person2 = new Person('hu');

person1.sayName();//'bai'

用对象字面形式添加属性 #警惕constructor属性的丢失

虽然可以在原型对象上一一添加属性,但是直接用一个对象字面形式替换原型对象更简洁:

function Person(name){
    this.name = name;
}
//对象字面形式:
Person.prototype = {
    sayName: function(){
        console.log(this.name);
    },
    toString : function(){
        return '[person ' + this.name + ']'
    }
};

var person1 = new Person('bai');

console.log(person1 instanceof Person);//true
console.log(person1.constructor === Person);//false,解释见下一个demo:
console.log(person1.constructor === Object);//true

当一个实例对象被创建时,该原型对象的constructor属性自动创建,并指向该函数。当使用对象字面形式改写原型对象Person.prototype时,需要在改写原型对象时手动重置其constructor属性:

function Person(name){
    this.name = name;
}
Person.prototype = {
    constructor: Person, // 只加了这一行代码
    sayName: function(){
        console.log(this.name);
    },
    toString : function(){
        return '[person ' + this.name + ']'
    }
};

var person1 = new Person('bai');

console.log(person1 instanceof Person);//true
console.log(person1.constructor === Person);//true
console.log(person1.constructor === Object);//false

defineProperty,设置constructor不可遍历

由于默认情况下,原生的constructor属性是不可枚举的,更妥善的解决方法是使用Object.defineProperty()方法,改变其属性描述符中的枚举性enumerable:

function Person(name){
    this.name = name;
}
Person.prototype = {
    sayName: function(){
        console.log(this.name);
    },
    toString : function(){
        return '[person ' + this.name + ']'
    }
};
Object.defineProperty(Person.prototype,'constructor',{
    enumerable: false,
    value: Person
});
var person1 = new Person('bai');
console.log(person1 instanceof Person);//true
console.log(person1.constructor === Person);//true
console.log(person1.constructor === Object);//false

构造函数、原型对象和实例对象之间的 关系 和 其指向判断

如果没有了解基础知识,可参考我的上一篇文章: 理解prototype、proto和constructor等关系

构造函数、原型对象和实例对象之间的关系是实例对象和构造函数之间没有直接联系。

function Foo(){};
var f1 = new Foo;

以上代码的原型对象是Foo.prototype,实例对象是f1,构造函数是Foo

原型对象和实例对象的关系:

console.log(Foo.prototype === f1.__proto__);//true

原型对象和构造函数的关系 :

console.log(Foo.prototype.constructor === Foo);//true

而实例对象和构造函数则没有直接关系,间接关系是实例对象可以继承原型对象的constructor属性:

console.log(f1.constructor === Foo);//true

如果非要扯实例对象和构造函数的关系,那只能是下面这句代码:

// new操作的过程中,会调用构造函数来塑造返回的实例对象
var f1 = new Foo();

当这句代码执行以后, 如果重置原型对象,则会打破它们三者间的关系:

function Foo(){};
var f1 = new Foo;
console.log(Foo.prototype === f1.__proto__);//true
console.log(Foo.prototype.constructor === Foo);//true

Foo.prototype = {}; // 此时重置原型对象
console.log(Foo.prototype === f1.__proto__);//false
console.log(Foo.prototype.constructor === Foo);//false

所以,对于 改写Foo.prototype 这种操作,代码顺序很重要。它所遵循的规律还是脱离不了操作js对象的基本规律。

一是谨慎重置原型对象,二是要警惕constructor属性的丢失。(恰巧constructor是原型对象的唯一自有属性,所以2个问题是可以同时预防的)

文章目录
  1. 原型对象 属性的查找
    1. in
    2. hasOwnProperty()
  2. 原型对象 属性的添加
    1. 点运算添加属性
    2. 用对象字面形式添加属性 #警惕constructor属性的丢失
      1. defineProperty,设置constructor不可遍历
  3. 构造函数、原型对象和实例对象之间的 关系 和 其指向判断