本文主要是根据《head first javascript程序设计》摘抄学习而成,学习采用这本书主要是一位阿里大佬的建议,经过近期的学习阅读,发现确实比其他的js书籍能更好的理解和使用js的相关知识。
为了更好地理解对象的结构,我们可以向后端学习,做出对象图,它基本是分为三栏的一个矩形图,第一栏是构造函数名称,第二栏是对象具有的属性,第三栏是对象具有的方法。
一般情况下,我们是使用构造函数进行一些类定义的,在构造函数中你会定义一些这个对象需要用到的属性或者方法。比如下面的Dog的代码。
function Dog(name,owner){
this.name = name
this.owner = owner
this.walk = function(){
console.log("walking")
}
}
var fido = new Dog("fido","xiaoli")
var fully = new Dog("fully","zhangmou")
在上述代码执行后,虽然代码层面,各个实例对象的属性以及方法都指定到了新的对象中,但细节发现,其属性是各自不同,可以理解,但是其方法也会都有一个函数的指向,这部分是重复的。那么这里就会涉及到一个内存的问题,因为每个实例对象都创建了自己的方法,重用方法的代码,会占据很多的内存,影响性能(尤其移动端)。
之所以出现这样的问题,是因为我们没有充分利用好js的对象模型,其是基于原型的概念的。
js可以从其他对象那里继承属性和行为,更具体的说js使用原型继承。其中其行为继承的对象称为原型对象。这个是指,会继承原型对象的属性(包括属性方法),同时在新对象中添加属性。
那么如果我们要定义一个小狗的原型,其对象图是什么样的呢?
这里需要注意的是:在小狗的原型中,其定义的属性和属性方法都要求是每个实例必然会用到且基本不会被更改或者重新定义的。
那么它的代码实现会怎么写呢?
Dog.prototype.type = 'animal'
Dog.prototype.bark = function(){
console.log('barking')
}
在有了原型对象之后,我们可以使用虚线来代表继承,发现新小狗实例对象都会通过继承的方式来使用原型对象的属性和方法,而不是重新赋值或者自建私有方法。
其继承的工作原理是查找关系: 1 编写代码,比如调用bark()
2 在实例对象中查找方法,没有找到
3 在实例对象中找不到,继续沿着继承链向上寻找
4 在小狗的原型中找到了 方法bark
5 在找到方法之后完成调用
总结:在使用原型继承之后,其会调用同一个bark方法,大量的节省了内存。这样不但能够重用代码,而且能新增属性和行为。
那如果有些时候,我们需要重写bark方法,而不用原型中的方法呢?
答案肯定是可以的,我们可以在对象中重新定义自己的属性和方法,那么在继承的工作原理中,他就会现在实例对象中寻找,找到对应的方法之后就不会向上寻找原型对象的方法。
let little = new Dog("little",'xiaoli')
little.bark = function(){
console.log('self bark')
}
little.bark()
我们之前有讲过,在原型对象中this是指向原型对象的,同样我们也很好理解在没有原型的时候,this调用指向的是对象本身,但是你在调用原型原型的方法时你可能认为this指向的是原型对象,其实并非如此。
调用方法时,this被设置为方法被调用的对象,即便在对象中找不到该方法,在原型中找到了方法,也不会修改其this的指向。
所以其准确的解释是在任何情况下,this指向的都是原始对象,即方法被调用的对象,在原型中也是如此。
我们可以通过代码进行验证:同时,虽然this指向的是原始对象,但并不影响其对原型对象的属性和方法的调用,这就是继承的本身。
Dog.prototype.name = 'default'
Dog.prototype.type = 'animal'
Dog.prototype.bark = function(){
console.log(this)
console.log(this.name+' is barking')
console.log(this.name+' is '+this.type)
}
let little = new Dog("little",'xiaoli')
little.bark()
更多的知识请参考下一篇文章《原型了解进阶》