文章已同步至掘金:https://juejin.cn/post/6844903901091594253
欢迎访问😃,有任何问题都可留言评论哦~
原型
什么是JS原型?
如果对JS原型解释的话,会涉及到两个概念:构造函数,原型对象
- 构造函数
通俗的说,就是你在script标签里面声明的那个函数
<script>
function Test(){
// 我就是构造函数
}
</script>
- 原型对象
在声明了一个函数之后,浏览器会自动按照一定的规则创建一个对象,这个对象就叫做原型对象。这个原型对象其实是储存在了内存当中
在声明了一个函数后,这个构造函数(声明了的函数)中会有一个属性prototype,这个属性指向的就是这个构造函数(声明了的函数)对应的原型对象;原型对象中有一个属性constructor,这个属性指向的是这个构造函数(声明了的函数)
如下图:
使用构造函数创建对象
我们的构造函数使用new来创建对象的时候,就像下面这样:
<script>
function students(){
// 我就是构造函数
}
let stu = new students();
</script>
此时,stu就是那个构造函数students创建出来的对象,这个stu对象中是没有prototype属性的,prototype属性只有在构造函数students中有,看图!
可以看出,构造函数students中有prototype属性,指向的是students对应的原型对象;而stu是构造函数students创建出来的对象,他不存在prototype属性,所以在调用prototype的时候的结构是undefined,但stu有一个__proto__属性,stu调用这个属性可以直接访问到构造函数students的原型对象(也就是说,stu的__proto__属性指向的是构造函数的原型对象),看图:
说明:
- 从上面的代码中可以看到,创建stu对象虽然使用的是students构造函数,但是对象创建出来之后,这个stu对象其实已经与students构造函数没有任何关系了,stu对象的__proto__属性指向的是students构造函数的原型对象
- 如果使用new students()创建多个对象stu1、stu2、stu3,则多个对象都会同时指向students构造函数的原型对象
- 我们可以手动给这个原型对象添加属性和方法,那么stu1,stu2,stu3…这些对象就会共享这些在原型中添加的属性和方法
- 如果我们访问stu中的一个属性name,如果在stu对象中找到,则直接返回。如果stu对象中没有找到,则直接去stu对象的__proto__属性指向的原型对象中查找,如果查找到则返回。(如果原型中也没有找到,则继续向上找原型的原型—原型链)
- 如果通过stu对象添加了一个属性name,则stu对象来说就屏蔽了原型中的属性name。 换句话说:在stu中就没有办法访问到原型的属性name了
- 通过stu对象只能读取原型中的属性name的值,而不能修改原型中的属性name的值。 stu.name = “李四”; 并不是修改了原型中的值,而是在stu对象中给添加了一个属性name
开始撸代码:
<script type="text/javascript">
function students () {
}
// 可以使用students.prototype 直接访问到原型对象
//给students函数的原型对象中添加一个属性 name并且值是 "张三"
students.prototype.name = "张三";
students.prototype.age = 20;
var stu = new students();
/*
访问stu对象的属性name,虽然在stu对象中我们并没有明确的添加属性name,但是
stu的__proto__属性指向的原型中有name属性,所以这个地方可以访问到属性name
就值。
注意:这个时候不能通过stu对象删除name属性,因为只能删除在stu中删除的对象。
*/
alert(stu.name); // 张三
var stu1 = new students();
alert(stu1.name); // 张三 都是从原型中找到的,所以一样。
alert(stu.name === stu1.name); // true
// 由于不能修改原型中的值,则这种方法就直接在stu中添加了一个新的属性name,然后在stu中无法再访问到
//原型中的属性。
stu.name = "李四";
alert(stu.name); //李四
// 由于stu1中没有name属性,则对stu1来说仍然是访问的原型中的属性。
alert(stu1.name); // 张三
</script>
与原型有关的几个方法:
- prototype属性
prototype 存在于构造函数中 (其实任意函数中都有,只是不是构造函数的时候prototype我们不关注而已) ,他指向了这个构造函数的原型对象
- constructor属性
constructor属性存在于原型对象中,他指向了构造函数
如下代码:
<script type="text/javascript">
function students () {
}
alert(students.prototype.constructor === students); // true
</script>
- _ _ proto_ _ 属性(注意:左右各是2个下划线)
用构造方法创建一个新的对象之后,这个对象中默认会有一个属性__proto__, 这个属性就指向了构造方法的原型对象
原型链
说完原型后,趁热打铁,说一下原型链
在js中,万物皆对象,对象可以说是重中之重了。每一个对象都拥有自己的属性。但是在这个世界中有很多东西都是相似的,可以归为一类,他们有共同的方法和属性。不可能让每一个对象都定义一个属性吧。那样太消耗内存了。所以,在js中怎么才能让多个对象共享一个或多个方法呢?原型的出现就是为了解决这个问题。
- prototype和__proto__的区别
一切皆对象,函数是特殊的对象。
所有的引用类型(函数、数组和对象)都拥有__proto__属性(隐式原型)
所有函数拥有prototype属性(显示原型)
原型对象:拥有prototype属性的对象,在定义函数时就被创建
- 构造函数
大多数情况下,__proto__可以理解为“构造器的原型”(__proto__ === constructor.prototype)
function Word(words){
this.words = words
}
Word.prototype = {
alert(){
alert(this.words)
}
}
var w = new Word("Hello Word");
w.alert();
console.log(w.__proto__ === Word.prototype) // true
实例w的隐式原型指向它构造函数的显示原型。(指向即恒等于)
w.__proto__ === Word.prototype // true
当调用某种方法或查找某种属性时,首先会在自身调用和查找,如果自身没有该属性或方法,则会去它的__proto__属性中调用查找,也就是构造方法的prototype属性中查找。实例通过这样的方法继承构造函数的方法和属性。
实例通过__proto__属性指向构造方法的prototype的属性实现方法和属性的继承。
- 原型链
由于__proto__属性是任何对象都有的属性,在js中一切皆对象,所以会形成一条__proto__连接起来的链条,递归访问__proto__必须最终到头,并且值为null。
Function.prototype.a = 'a';
Object.prototype.b = 'b';
function Person(){}
console.log(Person);
let p = new Person();
console.log(p); // Person {} 对象
console.log(p.a); // null
console.log(p.b); // b
说明: p是Person()的实例,是一个Person对象,它拥有一个属性值__proto__,并且__proto__是一个对象,包含两个属性值constructor和__proto__,
会发现p.__proto__===Person.prototype,
p.__proto__.constructor ===Person // true,即p.__proto__.constructor指向了构造函数本身。
Person.prototype的__proto__属性,指向了Object 的prototype属性。即p.b的打印结果为b。则实例w通过__proto__属性继承了Object 的属性b。通过__proto__属性一直向上查找,一直到null为止。
那么构造函数Person的__proto__指向了Function.prototype。
- 查找属性,如果本身没有,则会去__proto__中查找,也就是构造函数的显式原型中查找,如果构造函数中也没有该属性,因为构造函数也是对象,也有__proto__,那么会去它的显式原型中查找,一直到null,如果没有则返回undefined
- p.__proto__.constructor == function Person(){}
- p.__proto__proto__== Object.prototype
- p.__proto____proto__.__proto__== Object.prototype.__proto__== null
- 通过__proto__形成原型链而非protrotype
事实上,js里完全依靠"原型链"模式来实现继承。
再简单说一下__proto__、prototype、constructor
- _ _proto _ _:事实上就是原型链指针
- prototype:这个是指向原型对象的
- constructor:每一个原型对象都包含一个指向构造函数的指针,就是constructor
继承的实现方式:
为了实现继承,_ _proto _ _会指向上一层的原型对象,而上一层的结构依然类似,那么就利用proto一直指向Object的原型对象上!Object.prototype. _ _ proto _ _ = null;表示到达最顶端。如此形成了原型链继承。
下面有个图非常经典:
大家可以自己动手画一下,加深自己的理解
大致总结一下:
1.Object是作为众多new出来的实例的基类 function Object()
2.Function是作为众多function出来的函数的基类 function Function()
3.构造函数的__proto__(包括Function.prototype和Object.prototype)都指向Function.prototype
4.原型对象的__proto__都指向Object.prototype
5.Object.prototype._ _ proto _ _指向null
评论区