1.原型对象:
在我们创建函数或者构造函数的时候,都会自动添加一个属性prototype 也就是我们所说的原型对象。当我们调用一个函数的某个属性或者调用构造函数的实例对象中所对应的某个属性时,首先js会寻找在当前函数中是否拥有该属性:
比如
function Person(name,age){
this.name=name
this.age=age
}
let p=new person("xiaoming",21)
console.log(p.name)
对于这端代码来讲,获取p实例对象的的name属性,显然这里我们是具备该属性的,所以会很自然的打印出
但是 如果我接着输出 address
console.log(p.address)
那么我们的结果肯定是
因为我们的构造函数中并不具备该属性
但是如果我们将该属性添加到构造函数的原型对象中
Person.prototype.address="xiaoming address"
那么再次输出p.address时就会发现结果为
这就说明,当我们的实例对象调用address属性时,它首先寻找自己是否具有该属性,当具备时就输出,如果没有找到的话就会进一步寻找自己的构造函数所对应的原型对象,也就是说原型对象所保存的属性可以被改函数本身直接获取。
但是这里注意到我是将他添加到了Person构造函数的原型上,那么Person对应的p上是否也具备原型呢?答案是肯定的,但是对于实例对象来说,它的属性名不是prototype而是__proto__。当然并不是只有构造函数的实例对象是这样,普通的对象也有该属性,他与prototype指向是相同的,这里我们可以看一下输出结构
console.log(p.__proto__==Person.prototype,"对比");
结果如图。说明我们的说法是正确的。
所以这里我也可以这么写
p.__proto__.sex = "femal"
console.log(p.sex);
结果为
这样我们可以通过实例对象来添加原型中的属性
然后我们再做另一个实验
var p2=new Person("xiaohua",18)
console.log(p2.sex);
输出还是
我们已经创建了新的实例对象,但是对p添加的sex还是可以被p2调用,进一步说明了我们通过__proto__添加的属性实际是指向构造函数的原型,这样创建出的p2同样可以直接获取sex属性。
2.原型链:
了解上面的原理以后,就可以轻松理解原型链,当我们访问一个属性时,首先js访问该函数本身,当没有该属性时访问其原型对象,那么如果该原型对象任然没有呢。
这里我们可以输出一下Person的原型
console.log(Person.prototype);
然后查看它的原型对象:
接下来我们输出
console.log(Person.toString);
然后得到结果
这就很奇怪了 我们的构造函数本身没有这个属性,原型对象中也内有,为什么还能读取到它呢,原因其实很简单,当我们查找一个属性时,它不仅会从本身和原型对象中寻找,同样的当原型对象没有改属性时,他会继续向上一层的原型对象寻求该数据,依次类推直到找到该属性或者找完所有原型对象后仍然无法找到给属性。
这里的toString实际是Object这个构造函数的方法,我们的Person也是来源于它
输出结果为
这就说明,当我们的toString属性无法在Person原型对象中找到时,它向上寻找直到找到了Object中的toString 这里我们可以打印一下
console.log(Object.prototype);
输出中我们可以找到
这就进一步证明了我们的说法
这就是原型链的一个简单的例子,所谓原型链,按我的理解就是 这种链式的一层一层向上查找,向下继承的存储属性的方式。原型链最常用的情景就是为构造函数添加同一属性或者方法,避免浪费内存(一个构造方法的属性对于不同的实例对象来说都是不同的,哪怕他们是一样的值)同时避免污染全局命名空间而使用
下面是个小例子
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.say = function () {
console.log("hello I am " + this.name);
}
var p1 = new Person("xiaoming", 21)
var p2=new Person("xiaohua",18)
console.log(p1.say==p2.say);