一、前言
原型和原型链是 JavaScript中不可避免需要碰到的知识点,在刚开始学习 JS 时,原型和原型链都是一个需要克服的困难。
网络上有很多讲解原型与原型链的就是放一张图,然后指来指去,很容易绕糊涂,现在我总结我对其的认识,如有错误之处,请指出。
二、前置知识
2.1构造函数
出自《你不知道的js》:在js中, 实际上并不存在所谓的'构造函数',只有对于函数的'构造调用'。
所谓的构造函数,实际上就是通过关键字new来调用的函数。
let newObj=new someFunc() //构造调用函数
构造/new调用函数的时候做了什么new做了什么,简单说:
- 创建一个全新的对象。
- 这个新对象的原型(Object.getPrototypeOf(target))指向构造函数的prototype对象。
- 该函数的this会绑定在新创建的对象上。
- 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。
- 我们称这个新对象为构造函数的实例。
2.2普通对象和函数对象
在JavaScript中一切皆对象,函数的本质也是一个对象,只不过函数的能力很强大罢了!Object和Function就是JavaScript中典型的函数对象。
如何区分函数对象和普通对象呢?
var obj={};
var func=function(){};
console.log( obj.constructor ); //f Object() { }
console.log( func.constructor ); //f Function() { }
结论:普通对象的构造函数是 Object() , 函数对象的构造函数是 Function();
三、原型
3.1什么是原型
在ES2019中prototype 的定义为
object that provides shared properties for other objects
给其它对象提供共享属性的对象。
也就是说,prototype 自己也是对象,只是被用以承担某个职能罢了
先看例子:
由上图可以得出以下结论:
- 函数对象的构造函数是 Function,普通对象的构造函数是 Object
- 函数对象 有 原型 ( prototype ),普通对象 没有 原型 prototype 的
每声明一个函数,就会有一个产生一个原型,这个原型的 引用 就保存在 函数对象的 func.prototype 上面
3.1为什么要用原型
JS通过new来生成对象,但是仅靠构造函数,每次生成的对象都不一样。
有时候需要在两个对象之间共享属性,由于JS在设计之初没有类的概念,所以JS使用函数的prototype来处理这部分需要被共享的属性,通过函数的prototype来模拟类:
当创建一个函数时,JS会自动为函数添加prototype属性,值是一个有constructor的对象。
简单来说就是共享属性。
由demo可以看出原型是对象的共同属性,当其中一个对象修改其值时不会影响到原型上的值
这里同样也有一个疑问,为什么People1在给age赋值之后就不能访问原型上age的值,而People2没有age,却能访问原型上的age的值?这就需要引如原型链的概念了。
四、原型链
4.1认识proto属性
看一个例子:由demo可以得出结论:
- __proto__是对象实例和它的构造函数之间建立的链接,它的值是:构造函数的prototype。也就是说:proto的值是它所对应的原型对象,是某个函数的prototype
-
每一个对象,不管是函数对象或者普通对象,都会有 __proto__ 属性。
注:这个属性现在已经不推荐使用了原因
,应该使用应该使用:Object.getPrototypeOf(target)(读操作)、Object.setPrototypeOf(target)(写操作)、Object.create(target)(生成操作)代替。
4.2原型链是什么
看个例子:
如是以前的语法,从newObj查找func的原型,是这样的:
newObj.__proto__.__proto__ // 这种关系就是原型链
那么什么是原型链?用以下三句话理解:
- 每个对象都拥有一个原型对象: newObj的原型是func.prototype。
- 对象的原型可能也是继承其他原型对象的: func.prototype也有它的原型Object.prototype。
- 一层一层的,以这种方式查找:在访问一个对象的某个属性/方法时,若在当前对象上找不到,则会尝试访问 newObj.__proto__, 也就是访问该对象的构造函数的原型 func.prototype,若仍找不到,会继续查找 func.prototype.__proto__,像依次查找下去。若在某一刻,找到了该属性,则会立刻返回值并停止对原型链的搜索,若找不到,则返回 undefine,这种关系就是原型链
4.2.1判断一个对象是否在另一个对象的原型链上
如果一个对象存在另一个对象的原型链上,我们可以说:它们是继承关系,关于JavaScript的继承,请参考JavaScript继承。
方法有以下两种:
1.instanceof: 用于测试构造函数的prototype属性是否出现在对象的原型链中的任何位置
2.isPrototypeOf:测试一个对象是否存在于另一个对象的原型链上
4.2.2原型链的终点: Object.prototype
Object.prototype是原型链的终点,所有对象都是从它继承了方法和属性。
console.log(Object.getPrototypeOf(Object.prototype));// null
4.2.3原型链图
有以上可以引出这张图
看懂这图,基本上所有之前的疑问都可以解答了。
4.2.4原型链的作用
从什么是原型链的介绍中已经知道原型链的作用是什么了
属性查找,如果试图访问对象(实例instance)的某个属性,会首先在对象内部寻找该属性,直至找不到,然后才在该对象的原型(instance.prototype)里去找这个属性,以此类推
当你访问test的某个属性时,是这样进行查找的:
1.浏览器首先查找stringtest 本身
2.接着查找它的原型对象:String.prototype
3.最后查找String.prototype的原型对象:Object.prototype
4.一旦在原型链上找到该属性,就会立即返回该属性,停止查找。
5.原型链上的原型都没有找到的话,返回undefiend
4.2.4.1拒绝查找原型链
hasOwnProperty: 指示对象自身属性中是否具有指定的属性
let test ={ 'name': 'agamgn' }
test.hasOwnProperty('name'); // true
test.hasOwnProperty('toString'); // false test本身没查找到toString
总结
参考
- 三张图搞懂JavaScript的原型对象与原型链
- 万物皆空之 JavaScript 原型
- MDN继承与原型链
- 《你不知道的JavaScript 上卷》