由于ES6之前的JavaScript不支持类(class),所以我们通过其他方式来模拟面向对象编程。
定义类对象(Object)
// constructor,ES6提供有新的定义类的方法
function MyClass(field1,...) { // 这个类也是一个对象
// fields
var privateVal; // private fields
this.field1 = fields; // public fields
...
// methods
this.MyMethod = function(parameters,...) {
...
}
}
var myClass = new MyClass(field1,...); // 也是一个对象
但是实际上这样做,有一个很大的弊端。那就是对于那些每一个实例对象都共有的属性,每一次生成一个实例,都必须为重复的内容,多占用一些内存。
Javascript规定,每一个构造函数都有一个prototype属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承。
这意味着,我们可以把那些不变的属性和方法,直接定义在prototype对象上。
原型(prototype)继承
function A() {
this.name="A";
this.say=function() {
console.log("I'm " + this.name);
}
}
function B() {
this.name="B";
}
function F(){}
F.prototype = A.prototype; // 从A.prototype继承属性
B.prototype = new F(); // B.prototype继承F对象的属性,这样修改B.prototype的属性不会影响A.prototype
B.prototype.constructor = B;// B.prototype.constructor改为B()
// ES5后可以直接使用封装好的方法来实现上述的原型继承
B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;
多重继承
function M1() {
this.hello="hello";
}
function M2() {
this.world="world";
}
function S() {
M1.call(this);
M2.call(this);
}
S.prototype = Object.create(M1.prototype); // 继承M1
Object.assign(S.prototype, M2.prototype); // 在继承链中加入M2
S.prototype.constructor = S;
/* 原型对象的属性不是实例对象自身的属性。只要修改原型对象,
* 变动就立刻会体现在所有实例对象上。当实例对象本身没有某个属性或方法的时候,
* 它会到原型对象去寻找该属性或方法。这就是原型对象的特殊之处。
* 如果实例对象自身就有某个属性或方法,它就不会再去原型对象寻找这个属性或方法。
* 原型对象的作用,就是定义所有实例对象共享的属性和方法。
* 这也是它被称为原型对象的原因,而实例对象可以视作从原型对象衍生出来的子对象。
*/
原型链
JavaScript 规定,所有对象都有自己的原型对象(prototype)。一方面,任何一个对象,都可以充当其他对象的原型;另一方面,由于原型对象也是对象,所以它也有自己的原型。因此,就会形成一个“原型链”(prototype chain):对象到原型,再到原型的原型……
如果一层层地上溯,所有对象的原型最终都可以上溯到Object.prototype
,即Object
构造函数的prototype
属性。也就是说,所有对象都继承了Object.prototype
的属性。这就是所有对象都有valueOf
和toString
方法的原因,因为这是从Object.prototype
继承的。
那么,Object.prototype
对象有没有它的原型呢?回答是Object.prototype
的原型是null
。null
没有任何属性和方法,也没有自己的原型。因此,原型链的尽头就是 null
。
读取对象的某个属性时,JavaScript 引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的Object.prototype还是找不到,则返回undefined
。如果对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性,这叫做“覆盖”(overriding)。
constructor属性
prototype
对象有一个constructor
属性,默认指向prototype
对象所在的构造函数。由于constructor
属性定义在prototype
对象上面,意味着可以被所有实例对象继承。
function P() {}
var p = new P();
p.contructor === P; // true
p.constructor === P.prototype.constructor; // true
p.hasOwnPrototype("constructor"); // false, ∵ constructor属性继承自原型
修改原型对象时,一般要同时修改constructor属性的指向。
isinstanceof运算符
- 作用:
instanceof
运算符返回一个布尔值,表示对象是否为某个构造函数的实例。
- 判断规则
instanceof
运算符的左边是实例对象,右边是构造函数。它会检查右边构建函数的原型对象(prototype),是否在左边对象的原型链上。有一种特殊情况,就是左边对象的原型链上,只有null
对象。这时,instanceof
判断会失真。但是,只要一个对象的原型不是null
,instanceof
运算符的判断就不会失真。