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)

 

在上面的代码中,Person函数是一个构造函数,函数内部定义了 name,age属性,sayName方法,所有实例对象(person1person2)都会有这些属性和方法。通过构造函数为实例对象定义属性和方法,有一个缺点就是每个方法都要在每个实例上重新创建一遍,浪费内存。

于是我们就重构一下,把 sayName 函数放在 Person 函数的外面,然后在 Person 构造函数中的 this.sayName 指向外部的 sayName 全局函数,变成了下面的样子

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的属性。这就是所有对象都有valueOftoString方法的原因,因为这是从Object.prototype继承的。

那么,Object.prototype对象有没有它的原型呢?回答是Object.prototype的原型是nullnull没有任何属性和方法,也没有自己的原型。因此,原型链的尽头就是null

Object.prototype.__proto__ === null // true

 

读取对象的某个属性时,JavaScript 引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的Object.prototype还是找不到,则返回undefined,这样的查过过程就是原型链查找。

 

posted @ 2019-04-25 17:08  AALMIX  阅读(283)  评论(0编辑  收藏  举报