Javascript的原型链可以理解为Java中的继承关系,一个类继承另一个类,另一个类如果没继承别的类,就会默认继承Object。Java中的这些关系是能够在代码中轻易看到的,而Javascript则没那么明显。
首先对象可以分为两大类,即:普通对象跟函数对象
var obj1 = {}; // 普通对象
function Person() {}
var obj2 = new Person(); // 函数对象
Javascript有prototype跟__proto__
来实现原形链。prototype是函数的属性。__proto__
是实例的属性,实例的__proto__
指向函数的prototype。
function Person() {}
var person = new Person();
console.log(person.__proto__ === Person.prototype); // true
而普通对象原型链只是简单的指向Object.prototype:
var obj = {};
console.log(obj.__proto__ === Object.prototype); // true
看下面例子:
function Person (name) {
this.name = name;
}
Person.prototype.age = 15
Person.prototype.sayHello = function () {
console.log("my name is " + this.name);
}
var person = new Person("haha");
person.sayHello(); // my name is haha
console.log(person.age); // 15
console.log(person.__proto__ === Person.prototype); // true
prototype是让其函数对象使用的,对象可以调用prototype的方法(当然并不是对象.prototype.xxx这样调用)。具体是当对象调用某方法时,如果没有在自身的方法群中查找到,那他会去找__proto__
所指向的原型中是否存在方法,如果有,则会调用。
注意:prototype相对Person来说并不能够被直接使用,也就是Person.sayHello()是错误的。prototype是其对象所使用。
对于修改原型中的属性
function Person () {}
Person.prototype.name = 'default'
let person1 = new Person()
let person2 = new Person()
person1.name = 'new'
console.log(person1.name) // new
console.log(person2.name) // default
对象并不能修改原型中的值,而是创建一个跟原型同名的私有属性,然后会无视原型中的属性。
整个原形层当然不止这么简单的两层关系,上面的原形链的真实面目其实是这样的:
person.__proto__ ---> Person.prototype,
Person.prototype.__proto__ ---> Object.prototype,
Object.prototype.__proto__ ---> null
prototype所指向的对象,因为是对象,所以也是存在__proto__
这个属性。如果是最基本的链层,这时指向的都是Object.prototype(就类似Java中一个类没有继承别的类,那么他就是继承的Object),然后Object.prototype.__proto__
最后就是指向的null,此时就是完整的一条原型链。
建议使用以下方式设置原型链:
function Person () {}
Person.prototype.aaa = ""
Person.prototype.bbb = ""
以下方式会导致函数prototype本身的constructor属性丢失:
function Person() {}
Person.prototype = {
aaa: "",
bbb: ""
}
// Person.prototype.constructor为null
或者手动加上constructor
function Person() {}
Person.prototype = {
aaa: "",
bbb: ""
}
Object.defineProperty(Person.prototype, 'constructor', {
enumerable: false, // 原始的constructor不可枚举
value: Person
})
关于__proto__
__proto__
并不是语言本身的特性,这是各大厂商具体实现时添加的私有属性,虽然目前很多现代浏览器的 JS 引擎中都提供了这个私有属性,但依旧不建议在生产中使用该属性,避免对环境产生依赖。生产环境中,我们可以使用 Object.getPrototypeOf 方法来获取实例对象的原型,然后再来为原型添加方法/属性。
instanceof关键字
判断一个对象是否为该类所创建。
比如:
function fn() {}
var obj = new fn()
console.log(obj instanceof fn) // true
但是,实际上instanceof判断的是obj.__proto__
是否等于fn.prototype
也就是如果:
function fn() {}
var obj =new fn()
obj.__proto__ = null
console.log(obj instanceof fn) // false
constructor属性
指向创建当前对象的函数。每个对象都有的属性,主要是对象由new Function创建时,Function默认的prototype.constructor已经设置好了,就是指向Function自己。
比如:
function fn() {}
var obj = new fn()
console.log(obj.constructor === fn) // true
instanceof跟constructor的区别
instanceof返回的是true/false,constructor直接就是指向创建当前对象的类。目前用的比较多的是instanceof。
原型实现继承的方式(目前建议使用es6的class extends继承)
1.使用构造函数跟原型链的组合式继承
function A() {
this.aValue = 'aValue'
}
A.prototype.a = 'aaa'
function B () {
A.call(this) // 调用父类的构造函数
this.bValue = 'bValue'
}
B.prototype = new A() // 这里也会调用父类的构造函数
B.prototype.b = 'bbb'
Object.defineProperty(B, 'constructor', {
enumerable: false,
value: B
})
let objB = new B()
objB.aValue = 'newValue' // 只会改变当前对象的aValue,并不会改变B.prototype.aValue
console.log(objB)
缺点:上面会调用两次父类的构造函数,实际上B.prototype = new A()这里调用父类构造函数是没有意义的,会产生多余的变量在B的原型上,而实际B的构造函数已经调用了父类的构造函数进而让父类的构造函数生成的属性保留在了对象上。所以B的原型中是没必要保存这些属性,因为会被无视。
2.寄生组合式继承
优化组合式继承的调用两次父类构造函数的缺点。
function A() {
this.aValue = 'aValue'
}
A.prototype.a = 'aaa'
function B () {
A.call(this)
this.bValue = 'bValue'
}
// 复制一份superType的原型给subType
function inheritPrototype(subType, superType) {
const prototype = Object.create(superType.prototype)
Object.defineProperty(prototype, 'constructor', {
enumerable: false,
value: subType
})
subType.prototype = prototype
}
inheritPrototype(B, A)
B.prototype.b = 'bbb'
let objB = new B()
objB.aValue = 'newValue' // 只会改变当前对象的aValue,并不会改变B.prototype.aValue
console.log(objB)