JS 原型和原型链
原型
JavaScript 通过构造函数生成新对象,因此构造函数可以视为对象的模板。实例对象的属性和方法,可以定义在构造函数内部。
function Person(name, age){ this.name = name this.age = age this.sayName = function(){ alert(this.name) } } var person1 = new Person('张三', 18) var person2 = new Person('李四', 26)
var sayName = function(){ alert(this.name) } function Person(name, age){ this.name = name this.age = age this.sayName = sayName } var person1 = new Person('张三', 18) var person2 = new Person('李四', 26)
但是如果 Person 里面有100个方法,那么就需要定义100个全局函数,这样做有个缺点,定义的全局函数只能给 Person 用,函数容易被污染,而且没有封装性可言。
能不能把 Person 的属性和方法全部放在一个对象中,让实例对象person1,person2等共享它,能够直接使用?答案是可以的。
JavaScript 规定,每个函数都有一个prototype属性,指向一个对象(原型对象),这个对象就能够让实例对象共享属性和方法。所以代码可以改成下面这样
function Person(name, age){ this.name = name this.age = 18 } Person.prototype.country = '中国' Person.prototype.sayName = function(){ console.log(this.name) } var person1 = new Person('张三', 18) var person2 = new Person('李四', 26) person1.sayName() // 张三 person2.sayName() // 李四 person1.country // 中国 person2.country // 中国
JS 又给对象提供了 __proto__ 属性去访问共享属性和方法的对象,那么 person1.__proto__ 获取的对象就是 Person.prototype,它们在内存中都指向 addr=700 的地址,所以 person1.__proto__ === Person.prototype 。
__proto__
和prototype
的区别
__proto__
是实例对象内部的一个属性,它指向对象的原型,在原型链上查找方法及属性时使用的便是__proto__
prototype
是函数上的属性。
原型对象的属性不是实例对象自身的属性。只要修改原型对象,变动就立刻会体现在所有实例对象上。
Person.prototype.country = '美国' person1.country // 美国 person2.country // 美国
如果实例对象自身就有某个属性或方法,它就不会再去原型对象寻找这个属性或方法。
person1.country // 美国 person1.country = '俄罗斯' person1.country // 俄罗斯 person2.country // 美国 Person.prototype.country //美国
总结一下,原型对象的作用,就是定义所有实例对象共享的属性和方法。这也是它被称为原型对象的原因,而实例对象可以视作从原型对象衍生出来的子对象。
__proto__ 取值
// 1.对于使用对象字面量创建的对象,这个值是 Object.prototype。 var obj = {} obj.__proto__ === Object.prototype // 2.对于使用数组字面量创建的对象,这个值是 Array.prototype。 var arr = [] arr.__proto__ === Array.prototype // 3.对于函数来说,这个值是 Function.prototype var fn1 = function(){} fn1.__proto__ === Function.prototype // true // 4.对于使用 new func() 方式创建的对象,这个值就是 func.prototype var num1 = new Number(1) num1.__proto__ === Number.prototype // true var str1 = new String('hello') str1.__proto__ === String.prototype // true
所以我们就得到了一个公式:
var 对象 = new 函数() 对象.__proto__ === 对象的构造函数.prototype
进入烧脑环节
person1.__proto__ === Person.prototype //true typeof Person.prototype // "object" // 规则一:在 JavaScript 中所有其他对象都继承自 Object 对象 // 那么我们把 Person.prototype 看作是一个整体,套用上面的公式 Person.prototype.__proto__ === Object.prototype // true // 规则二:在 JavaScript 中, 每个函数实际上都是一个Function对象。 Person.__proto__ === Function.prototype // true // 我们再把 Function.prototype,套用公式,运用规则一,就得到下面的结果 Function.prototype.__proto__ === Object.prototype // true // Function 的特殊情况 Function.__proto__ === Function.prototype // true
Function.__proto__ === Object.prototype // false
// 总要有一个终点的嘛 Object.prototype.__proto__ === null // true
原型链
JavaScript 规定,所有对象都有自己的原型对象(prototype)。一方面,任何一个对象,都可以充当其他对象的原型;另一方面,由于原型对象也是对象,所以它也有自己的原型。因此,就会形成一个“原型链”(prototype chain):对象到原型,再到原型的原型……
如果一层层地上溯,所有对象的原型最终都可以上溯到Object.prototype
,即Object
构造函数的prototype
属性。也就是说,所有对象都继承了Object.prototype
的属性。这就是所有对象都有valueOf
和toString
方法的原因,因为这是从Object.prototype
继承的。
那么,Object.prototype
对象有没有它的原型呢?回答是Object.prototype
的原型是null
。null
没有任何属性和方法,也没有自己的原型。因此,原型链的尽头就是null
。
Object.prototype.__proto__ === null // true
读取对象的某个属性时,JavaScript 引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的Object.prototype
还是找不到,则返回undefined,这样的查过过程就是原型链查找。