原型
如果将构造函数方法写在构造函数体内,那么在创建对象时这些方法就会被分别写到每个对象中去,而当我们大量使用对象时就会占用非常多的内存空间从而造成内存浪费,下面的代码就很好的说明了这一点:使用同一个构造函数创建的两个不同对象的同名方法指向了不同的存储空间。
<script>
function Star(uname, age) {
this.uname = uname
this.age = age
this.sing = function () {
console.log('唱歌')
}
}
const a = new Star('a',23)
const b = new Star('b',21)
console.log(a.sing === b.sing);
</script>
1.1 原型(对象)
为了解决浪费内存的问题,我们可以把这些方法全都挂载到一个地方,让所有对象在使用这些方法时直接找到挂载的地方然后再进行调用,也就是说让原先被每个对象独占的方法变成一个共享的方法,这样便能节约内存空间。
JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象,所以我们也称为原型(对象),构造函数通过原型分配的函数是所有对象所共享的。我们可以把那些不变的方法,直接定义在 prototype 对象上,对象实例化不会多次创建原型上的函数,这样所有对象的实例就可以共享这些方法,从而节约内存。
<script>
function Star(uname, age) {
this.uname = uname
this.age = age
}
Star.prototype.sing = function (singname) {
// console.log('唱歌')
console.log('这位歌手唱的歌是' + singname);
}
const a = new Star('a',23)
const b = new Star('b',21)
a.sing('aa')
b.sing('bb')
console.log(a.sing === b.sing);
</script>
上面的代码中虽然 a 和 b 是两个不同的对象,但它们的方法 sing 却有着相同的存储地址。
1.2 constructor 属性
在每个原型对象里都有一个 constructor 属性,其默认存在,不用单独定义,并且指向该原型对象的构造函数。
如果要在一个构造函数中定义多个方法,那么我们可以给原型对象采取对象形式的赋值,
,
但如果这样赋值的话,大括号内的新值便会覆盖掉原值, constructor 就不再指向当前的构造函数,因此需要在修改后的原型对象中添加一个 constructor 指向原来的构造函数。
1.3 对象原型
被创建的每个对象都会有一个名叫 __proto__ 的属性,它指向了构造函数的 prototype (原型对象),之所以对象可以使用构造函数中原型对象的属性和方法,就是因为 __proto__ 的存在。
上图中,[[prototype]] 和 __proto__ 意义相同,其用来表明当前实例对象指向哪个原型对象 prototype ,同样的, __proto__ 对象原型里面也有一个 constructor 属性,指向创建该实例对象的构造函数。
1.4 原型继承
原型继承的思想是将多个构造函数中相同的属性和方法提取出来单独表示并且能在这些构造函数创建对象时被使用。具体用法如下,
<script>
// 构造函数 new 出来的对象 结构一样,但是对象不一样
function Person() {
this.eyes = 2
this.head = 1
this.sleep = function(){
console.log('sleep');
}
}
// 女人 构造函数
function Woman() {
}
// Woman 通过原型来继承 Person
Woman.prototype = new Person()
Woman.prototype.constructor = Woman // 指回原来的构造函数
// 男人 构造函数
function Man() {
}
// Man 通过 原型继承 Person
Man.prototype = new Person()
Man.prototype.constructor = Man
</script>
将 Man 和 Woman 这两个构造函数中相同属性和方法提取出来后放进一个新的构造函数 Person 中,然后通过 Man 和 Woman 的原型 prototype 来继承 new Person() 这个对象中的属性和方法。
1.5 原型链
1.概念
基于原型对象的继承使得不同构造函数的原型对象关联在一起,并且这种关联的关系是一种链状的结构,我们将原型对象的链状结构关系称为原型链。
2.原型链的查找规则
当访问一个对象的属性(或方法)时,首先查找其自身有没有该属性(或方法),如果有则直接使用,如果没有就沿着它的原型( __proto__ )向上查找构造函数的原型对象( prototype ),如果查到了就直接使用,如果没查到就接着向上查找原型对象( prototype )所指向的上一层构造函数的原型对象( prototype )...以此类推,直到无上层可查为止(即null)。
3. instanceof 的使用
使用 instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上,true 表示该实例对象的 __proto__ 属性能链接到构造函数的 prototype 属性,false 则表示不能。
<script>
function Person() {
}
const abc = new Person()
// console.log(abc.__proto__ === Person.prototype)
// console.log(Person.prototype.__proto__ === Object.prototype)
console.log(abc instanceof Person)
console.log(abc instanceof Object)
console.log(abc instanceof Array)
console.log('\n\n');
const arr = [1,2,3]
console.log(arr instanceof Array)
console.log(arr instanceof Object);
console.log(Array instanceof Object)
</script>