1. 对象原型
对象都会有一个属性 __proto__ 指向构造函数的 prototype 原型对象,之所以我们对象可以使用构造函数 prototype原型对象的属性和方法,就是因为对象有 __proto__ 原型的存在。
注意:
__proto__ 是JS非标准属性
[[prototype]]和__proto__意义相同
用来表明当前实例对象指向哪个原型对象prototype
__proto__对象原型里面也有一个 constructor属性,指向创建该实例对象的构造函数
2. 原型继承
继承是面向对象编程的另一个特征,通过继承进一步提升代码封装的程度,JavaScript 中大多是借助原型对象实现继承的特性。
龙生龙、凤生凤、老鼠的儿子会打洞描述的正是继承的含义。
我们来看个代码:
function Man() { this.head = 1 this.eyes = 2 this.legs = 2 this.say = function() {} this.eat = function() {} } const man = new Man()
function Woman() { this.head = 1 this.eyes = 2 this.legs = 2 this.say = function() {} this.eat = function() {} this.baby = function() {} } const woman = new Woman()
1. 封装-抽取公共部分
把男人和女人公共的部分抽取出来放到人类里面
// 人类 const People = { head: 1, eyes: 2, legs: 2, say: function() {}, eat: function() {} } // 男人 function Man() { } // 女人 function Woman() { this.baby = function() {} }
2. 继承-让男人和女人都能继承人类的一些属性和方法
把男人女人公共的属性和方法抽取出来 People
然后赋值给Man的原型对象,可以共享这些属性和方法
注意让constructor指回Man这个构造函数
// 人类 公共的属性和方法 const People = { head: 1, eyes: 2, legs: 2, say: function() {}, eat: function() {} } // 男人 function Man() { } // 把公共的属性方法给原型,这样就可以共享了 Man.prototype = People // 注意让原型里面的constructor重新指回Man找自己的爸爸 Man.prototype.constructor = Man const kun = new Man() console.log(kun)
3. 问题:
如果我们给男人添加了一个吸烟的方法,发现女人自动也添加这个方法
// 人类 公共的属性和方法 const People = { head: 1, eyes: 2, legs: 2, say: function() {}, eat: function() {} } // 男人 function Man() { } // 把公共的属性方法给原型,这样就可以共享了 Man.prototype = People // 注意让原型里面的constructor重新指回Man找自己的爸爸 Man.prototype.constructor = Man // 抽烟 Man.prototype.smoking = function() {} const kun = new Man() console.log(kun) // 女人 function Woman() { this.baby = function() {} } // 把公共的属性方法给原型,这样就可以共享了 Woman.prototype = People // 注意让原型里面的constructor重新指回Woman找自己的爸爸 Woman.prototype.constructor = Woman const ji = new Woman() console.log(ji)
原因
男人和女人都同时使用了同一个对象,根据引用类型的特点,他们指向同一个对象,修改一个就会都影响
4. 解决:
需求:男人和女人不要使用同一个对象,但是不同对象里面包含相同的属性和方法
答案:构造函数
new 每次都会创建一个新的对象
// 人类 公共的属性和方法 function People() { this.head = 1 this.eyes = 2 this.legs = 2 this.say = function() {} this.eat = function() {} } // 男人 function Man() { } // 把公共的属性方法给原型,这样就可以共享了 Man.prototype = new People() // 注意让原型里面的constructor重新指回Man找自己的爸爸 Man.prototype.constructor = Man // 抽烟 Man.prototype.smoking = function() {} const kun = new Man() console.log(kun) // 女人 function Woman() { this.baby = function() {} } // 把公共的属性方法给原型,这样就可以共享了 Woman.prototype = new People() // 注意让原型里面的constructor重新指回Woman找自己的爸爸 Woman.prototype.constructor = Woman const ji = new Woman() console.log(ji)
思路
真正做这个案例,我们的思路应该是先考虑大的,后考虑小的
1. 人类共有的属性和方法有那些,然后做个构造函数,进行封装,一般公共属性写到构造函数内部,公共方法,挂载到构造函数原型身上。
2. 男人继承人类的属性和方法,之后创建自己独有的属性和方法
3. 女人同理
3. 原型链
基于原型对象的继承使得不同构造函数的原型对象关联在一起,并且这种关联的关系是一种链状的结构,我们将原型对象的链状结构关系称为原型链
原型链-查找规则
① 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
② 如果没有就查找它的原型(也就是 __proto__指向的 prototype 原型对象)
③ 如果还没有就查找原型对象的原型(Object的原型对象)
④ 依此类推一直找到 Object 为止(null)
⑤ __proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线
⑥ 可以使用 instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上