原型与原型链不应该仅仅是概念性的,而应该是具有逻辑性推进的,本篇文章带你了解原型链并深入原型链,希望能够帮到大家
读完这篇文章你能收获到什么?
- 理清原型和原型链的概念和逻辑
- 理解原型链的常见用法
- 有关原型链相关方法的深度解析
我们直接开始:
首先,什么是原型?什么是原型链?
原型:
在JavaScript中,每个对象都有一个关联的原型(prototype)。原型是一个对象,它自身拥有属性和方法,并且其他对象可以通过原型进行属性和方法的继承
原型链:
原型链是JavaScript中实现对象继承的一种机制。它通过对象的原型属性(prototype
)在对象之间形成一个链式的关系。
思考?
对象是怎么通过原型属性prototype在对象之间形成了一个链式的关系?
一个实例对象又是怎么连接到原型链呢?
答案就是: 通过__proto__
让我们先来看看__proto__的概念:
__proto__:
一个对象的__proto__属性指向的就是这个对象的构造函数的prototype属性所以__proto__可以让实例对象直接访问到构造函数的prototype属性!!!(那么我们可以联想一下创建实例对象的时候都发生什么了)
我们都知道new方法可以创建一个实例,并使该实例获取到构造函数原型上的方法以及函数内使用this声明的一些属性
那new方法做了什么呢? 我们一起来看一下!
function New () {var obj = new Object ();//获得构造函数(代码使用会传入参数,第一个参数是构造函数)var constructor = Array.prototype.shift.call(arguments);//连接原型,新对象可以访问原型中的属性obj._proto_ = constructor.prototype;-------------------------------------------------**这段代码实际上建立了原型链 使得实例对象可以访问到构造函数的prototype**// 执行构造函数,即绑定 this,并且为这个新对象添加属性var result = constructor.apply(obj,arguments);-------------------------------------------------绑定this指向,对象构造函数内使用this写的一些属性可以绑定到当前对象上return typeof result === "object" ? result :obj ;}function New () { var obj = new Object (); //获得构造函数(代码使用会传入参数,第一个参数是构造函数) var constructor = Array.prototype.shift.call(arguments); //连接原型,新对象可以访问原型中的属性 obj._proto_ = constructor.prototype; ------------------------------------------------- **这段代码实际上建立了原型链 使得实例对象可以访问到构造函数的prototype** // 执行构造函数,即绑定 this,并且为这个新对象添加属性 var result = constructor.apply(obj,arguments); ------------------------------------------------- 绑定this指向,对象构造函数内使用this写的一些属性可以绑定到当前对象上 return typeof result === "object" ? result :obj ; }function New () { var obj = new Object (); //获得构造函数(代码使用会传入参数,第一个参数是构造函数) var constructor = Array.prototype.shift.call(arguments); //连接原型,新对象可以访问原型中的属性 obj._proto_ = constructor.prototype; ------------------------------------------------- **这段代码实际上建立了原型链 使得实例对象可以访问到构造函数的prototype** // 执行构造函数,即绑定 this,并且为这个新对象添加属性 var result = constructor.apply(obj,arguments); ------------------------------------------------- 绑定this指向,对象构造函数内使用this写的一些属性可以绑定到当前对象上 return typeof result === "object" ? result :obj ; }
这就是一个实例对象是如何获取到构造函数的原型对象的prototype属性里面的方法
我们再来看一下prototype的定义
prototype:
在 JavaScript 中,每个函数都有一个特殊的属性叫做prototype
。这个prototype
属性是一个对象,它被用作构造函数创建的对象的原型(也称为原型对象)。
每一个函数都有一个特殊的属性叫prototype,我们看一下里面到底是什么?
我们创建一个函数
function testOne(){}console.log('testOne.prototype',testOne.prototype)// 函数的原型对象function testOne(){} console.log('testOne.prototype',testOne.prototype)// 函数的原型对象function testOne(){} console.log('testOne.prototype',testOne.prototype)// 函数的原型对象
我们把他打印出来,看看他里面有什么属性,他里面的prototype是什么?
看,我们得到了一个对象,里面有一个constructor属性,这个constructor属性里面是什么呢?
我们知道 我们的prototype属性是这个函数的原型对象,我们也可以手动创建,比如:函数.prototype去赋值,那么prototype本身就是该函数的一个属性罢了。 那么他的constructor属性的指向也应该就是本身了。我们来验证一下。
console.log(testOne.prototype.constructor === testOne)console.log(testOne.prototype.constructor === testOne)console.log(testOne.prototype.constructor === testOne)
那么 我们就有了结论:
函数的原型对象的构造函数指向该函数
还有呢?对象是怎么通过原型属性prototype在对象之间形成了一个链式的关系?是怎么使用prototype的属性的呢?
const testOne = function(){}testOne.prototype.methods = function(){console.log('this is a funtion in prototype')}const instance = new testOne()console.log(instance.__proto__ === testOne.prototype)instance.methods()const testOne = function(){ } testOne.prototype.methods = function(){ console.log('this is a funtion in prototype') } const instance = new testOne() console.log(instance.__proto__ === testOne.prototype) instance.methods()const testOne = function(){ } testOne.prototype.methods = function(){ console.log('this is a funtion in prototype') } const instance = new testOne() console.log(instance.__proto__ === testOne.prototype) instance.methods()
这样 对象就通过原型属性的prototype访问到了原型上面的方法!
(注意:testOne不能直接拿到methods方法,例如testOne.methods,这样得到的值是undefined,因为js并没有对这个处理过,需要通过属性prototype去调用,即 testOne.prototype.methods)
函数和对象是怎么实现原型链继承的,Function和Object之间有什么关系
为什么声明一个对象或者函数之后,就可以直接访问原型链上的方法了
函数
我们需要想一下?
那函数到底是如何实现原型链的继承的呢,其中发生了什么,他为什么可以调用apply、bind等这些方法呢?是和实例对象一样,继承的东西都是在__proto__里面么?
那么 我通过这样的方法创建的函数,他的__proto__是什么呢?我们直接看结果(这里我使用console.dir(testOne.__proto__)输出,为了更好的查看输出的结构和细节)
这里我们要去思考了,我们声明一个函数的时候到底发生了什么,这个函数继承了来自哪里的方法?
其实在JavaScript中,”Function”是一个内置的构造函数,用于创建函数对象。它是所有函数的父对象,包括声明的普通函数。
当你声明一个普通函数时,实际上是创建了一个函数对象,并将其赋值给一个变量或将其作为属性赋值给一个对象。这个函数对象具有”Function”构造函数提供的所有方法和特性,可以被调用和执行。
// 声明一个普通函数function greet(name) {console.log("Hello, " + name + "!");}// 使用 Function 构造函数创建函数对象var greetFunction = new Function("name", "console.log("Hello, " + name + "!");");console.log(greet.__proto__ === greetFunction.__proto__) //trueconsole.log(greet.__proto__=== Function) //false// 谨记__proto__的定义 获取的是该对象构造函数的原型对象 Function是一个特殊的内置的构造函数console.log(greet.__proto__=== Function.prototype ) // true// 声明一个普通函数 function greet(name) { console.log("Hello, " + name + "!"); } // 使用 Function 构造函数创建函数对象 var greetFunction = new Function("name", "console.log("Hello, " + name + "!");"); console.log(greet.__proto__ === greetFunction.__proto__) //true console.log(greet.__proto__=== Function) //false // 谨记__proto__的定义 获取的是该对象构造函数的原型对象 Function是一个特殊的内置的构造函数 console.log(greet.__proto__=== Function.prototype ) // true// 声明一个普通函数 function greet(name) { console.log("Hello, " + name + "!"); } // 使用 Function 构造函数创建函数对象 var greetFunction = new Function("name", "console.log("Hello, " + name + "!");"); console.log(greet.__proto__ === greetFunction.__proto__) //true console.log(greet.__proto__=== Function) //false // 谨记__proto__的定义 获取的是该对象构造函数的原型对象 Function是一个特殊的内置的构造函数 console.log(greet.__proto__=== Function.prototype ) // true
这两种方法本质是一样的,我们看看这两种方法的__proto__属性是否一样?
打印结果均为true false true
为什么第二个值返回false
在JavaScript中,
Function.prototype
是一个函数对象的原型,而Function
是一个特殊的内置函数构造函数。
当我们声明一个普通函数时,它的__proto__
属性指向的是该函数的原型对象,而不是构造函数本身。
备注: 比较特殊的是 console.log(typeof Function.prototype === function) //返回值为true
我们可以得到 声明一个函数为什么可以调用一些Function原型对象上的方法,因为声明的时候就类似于 new Function()这样,我们继承了Function原型对象上面的方法。
对象呢?
那么声明一个对象,为什么可以调用Object实例上的方法呢?答案是显然易见的,我们直接放代码,测试一下
let obj = {}console.dir(obj.__proto__)console.log(obj.__proto__ === Object.prototype)let obj = {} console.dir(obj.__proto__) console.log(obj.__proto__ === Object.prototype)let obj = {} console.dir(obj.__proto__) console.log(obj.__proto__ === Object.prototype)
我们可以得到 声明一个对象为什么可以调用一些Object原型对象上的方法,因为声明的时候就类似于 new Function()这样,我们继承了Object原型对象上面的方法。
Function 和 Object 有什么关系呢?
我们这里用的instanceof 检测对象之间实例的关系
console.log(Function instanceof Object)console.log(Object instanceof Function)console.log(Function instanceof Object) console.log(Object instanceof Function)console.log(Function instanceof Object) console.log(Object instanceof Function)
大家猜猜这个结果是什么?
答案是:返回了两个true
是不是,突然就懵掉了。
Object.prototype
是 Function
函数的原型对象,并由 Function
构造函数创建。Function
本身是一个函数对象,同时也拥有 Object.prototype
提供的属性和方法。它们之间存在关系,反映了 JavaScript 中的原型链继承机制。所以他们两者的话是相互关联的。
那么就有人要问了?博主啊,到底是先有Function还是先有Object呢
我们直接判断一波:
Function.prototype.__proto__ === Object.prototypeFunction.prototype.__proto__ === Object.prototypeFunction.prototype.__proto__ === Object.prototype
返回结果是true Object的原型对象是Function的原型对象的构造函数的原型对象
那么显而易见的 是先有的Object.prototype,那么Object本身是一个构造函数,所以Object就是由Function构造出来的,也就是Object继承了Function的原型对象上的方法。口说无凭,我们来判断一下
Object.constructor===FunctionObject.__proto__ === Function.prototypeObject.constructor===Function Object.__proto__ === Function.prototypeObject.constructor===Function Object.__proto__ === Function.prototype
返回结果都是true 我们的结论就成立了!
都有什么方法跟原型链有关呢? 我们要注意什么呢?
- for…in
检查属性是否为对象自身的属性:由于
for...in
循环会遍历对象继承的属性,因此在处理属性时,你需要使用hasOwnProperty()
方法来检查属性是否为对象自身的属性。这是为了避免遍历到继承的属性或方法。
for (var key in xxx) {// 检查属性是否为对象自身的属性if (xxx.hasOwnProperty(key)) {console.log(key + ": " + xxx[key]);}}for (var key in xxx) { // 检查属性是否为对象自身的属性 if (xxx.hasOwnProperty(key)) { console.log(key + ": " + xxx[key]); } }for (var key in xxx) { // 检查属性是否为对象自身的属性 if (xxx.hasOwnProperty(key)) { console.log(key + ": " + xxx[key]); } }
- Object.create()
思考object.create到底做了什么
//源码function create(prototype) {// 创建一个临时的构造函数function Temp() {}// 将临时构造函数的原型设置为指定的原型对象Temp.prototype = prototype;// 创建一个新对象实例var obj = new Temp();// 清空临时构造函数的引用Temp.prototype = null;// 返回新对象实例return obj;}//源码 function create(prototype) { // 创建一个临时的构造函数 function Temp() {} // 将临时构造函数的原型设置为指定的原型对象 Temp.prototype = prototype; // 创建一个新对象实例 var obj = new Temp(); // 清空临时构造函数的引用 Temp.prototype = null; // 返回新对象实例 return obj; }//源码 function create(prototype) { // 创建一个临时的构造函数 function Temp() {} // 将临时构造函数的原型设置为指定的原型对象 Temp.prototype = prototype; // 创建一个新对象实例 var obj = new Temp(); // 清空临时构造函数的引用 Temp.prototype = null; // 返回新对象实例 return obj; }
我们测试一个案例
let obj = {name:'zbz', age:18}let a = Object.create(obj)console.log(a.__proto__) //返回值是 {name:'zbz', age:18}function A(){//构造函数}A.prototype = Object.create(obj)console.log(A.prototype.__proto__) //返回值是 {name:'zbz', age:18}let obj = {name:'zbz', age:18} let a = Object.create(obj) console.log(a.__proto__) //返回值是 {name:'zbz', age:18} function A(){//构造函数 } A.prototype = Object.create(obj) console.log(A.prototype.__proto__) //返回值是 {name:'zbz', age:18}let obj = {name:'zbz', age:18} let a = Object.create(obj) console.log(a.__proto__) //返回值是 {name:'zbz', age:18} function A(){//构造函数 } A.prototype = Object.create(obj) console.log(A.prototype.__proto__) //返回值是 {name:'zbz', age:18}
所以 object.create所做的事情就是连接原型链
- instanceof源码简化版
function myInstanceof(obj, constructorFunc) {let prototype = constructorFunc.prototype;//拿到实例对象let proto = Object.getPrototypeOf(obj);//拿到构造函数的实例对象while(proto !== null) {//循环判断是否相等 若实例对象相等//且构造函数的实例对象的constructor属性与构造函数相等//证明该对象是构造函数实例if(proto === prototype || proto.constructor === constructorFunc) {return true;}proto = Object.getPrototypeOf(proto); //获取下一个构造函数的实例对象,类似于取__proto__属性,代码中建议使用getPrototypeOf这种方法}return false;}function myInstanceof(obj, constructorFunc) { let prototype = constructorFunc.prototype; //拿到实例对象 let proto = Object.getPrototypeOf(obj); //拿到构造函数的实例对象 while(proto !== null) { //循环判断是否相等 若实例对象相等 //且构造函数的实例对象的constructor属性与构造函数相等 //证明该对象是构造函数实例 if(proto === prototype || proto.constructor === constructorFunc) { return true; } proto = Object.getPrototypeOf(proto); //获取下一个构造函数的实例对象,类似于取__proto__属性,代码中建议使用getPrototypeOf这种方法 } return false; }function myInstanceof(obj, constructorFunc) { let prototype = constructorFunc.prototype; //拿到实例对象 let proto = Object.getPrototypeOf(obj); //拿到构造函数的实例对象 while(proto !== null) { //循环判断是否相等 若实例对象相等 //且构造函数的实例对象的constructor属性与构造函数相等 //证明该对象是构造函数实例 if(proto === prototype || proto.constructor === constructorFunc) { return true; } proto = Object.getPrototypeOf(proto); //获取下一个构造函数的实例对象,类似于取__proto__属性,代码中建议使用getPrototypeOf这种方法 } return false; }
默认的prototype其实是有constructor属性的,该属性指向本构造函数
我们以构造函数A举例,即 A.prototype.constructor === A
但是:当我们对一个构造函数的prototype赋值的时候,请记得给他负一个constructor属性,这个属性指向该
构造函数。
如不手动赋值,则默认的constructor会指向Object构造函数
举个栗子:
function A(){}console.log(A.prototype.constructor === A) //返回truelet a = new A()console.log(a instanceof A) // trueA.prototype = Object.create({b:1})console.log(A.prototype.constructor === A) //falseconsole.log(A.prototype.constructor === Object) //true//这时constructor默认指向Object 这会影响instanceof的判断console.log(a instanceof A) // falseconsole.log(a instanceof Object) // trueA.prototype.constructor = Aconsole.dir(A)console.log(A.prototype.constructor === A) //truelet b = new A()console.log(b instanceof A) // trueconsole.log(b instanceof Object) // trueconsole.log(a instanceof A)// false (下一行描述原因)//原型链在对象构造的时候已经确定即使在之后我改变了 但是也无法生效-------------------------------------------------------------------所以要在赋值给原型对象之后 先进行原型对象构造函数的赋值 在去实例话对象function A(){ } console.log(A.prototype.constructor === A) //返回true let a = new A() console.log(a instanceof A) // true A.prototype = Object.create({b:1}) console.log(A.prototype.constructor === A) //false console.log(A.prototype.constructor === Object) //true //这时constructor默认指向Object 这会影响instanceof的判断 console.log(a instanceof A) // false console.log(a instanceof Object) // true A.prototype.constructor = A console.dir(A) console.log(A.prototype.constructor === A) //true let b = new A() console.log(b instanceof A) // true console.log(b instanceof Object) // true console.log(a instanceof A)// false (下一行描述原因) //原型链在对象构造的时候已经确定即使在之后我改变了 但是也无法生效 ------------------------------------------------------------------- 所以要在赋值给原型对象之后 先进行原型对象构造函数的赋值 在去实例话对象function A(){ } console.log(A.prototype.constructor === A) //返回true let a = new A() console.log(a instanceof A) // true A.prototype = Object.create({b:1}) console.log(A.prototype.constructor === A) //false console.log(A.prototype.constructor === Object) //true //这时constructor默认指向Object 这会影响instanceof的判断 console.log(a instanceof A) // false console.log(a instanceof Object) // true A.prototype.constructor = A console.dir(A) console.log(A.prototype.constructor === A) //true let b = new A() console.log(b instanceof A) // true console.log(b instanceof Object) // true console.log(a instanceof A)// false (下一行描述原因) //原型链在对象构造的时候已经确定即使在之后我改变了 但是也无法生效 ------------------------------------------------------------------- 所以要在赋值给原型对象之后 先进行原型对象构造函数的赋值 在去实例话对象
- Object.prototype.toString.call()
Object的原型链上的toString方法被所有的对象继承,它返回一个表示对象类型的字符串。!!
当你使用
Object.prototype.toString.call(value)
时,它会返回一个字符串,格式为”[object Xxx]”,其中”Xxx”表示具体的类型。
- 数组:`Object.prototype.toString.call([])` 返回 “[object Array]”- 对象字面量:`Object.prototype.toString.call({})` 返回 “[object Object]”- 字符串:`Object.prototype.toString.call("hello")` 返回 “[object String]”- 数字:`Object.prototype.toString.call(123)` 返回 “[object Number]”- 布尔值:`Object.prototype.toString.call(true)` 返回 “[object Boolean]”- 函数:`Object.prototype.toString.call(function(){})` 返回 “[object Function]”- 正则表达式:`Object.prototype.toString.call(/regex/)` 返回 “[object RegExp]”- 数组:`Object.prototype.toString.call([])` 返回 “[object Array]” - 对象字面量:`Object.prototype.toString.call({})` 返回 “[object Object]” - 字符串:`Object.prototype.toString.call("hello")` 返回 “[object String]” - 数字:`Object.prototype.toString.call(123)` 返回 “[object Number]” - 布尔值:`Object.prototype.toString.call(true)` 返回 “[object Boolean]” - 函数:`Object.prototype.toString.call(function(){})` 返回 “[object Function]” - 正则表达式:`Object.prototype.toString.call(/regex/)` 返回 “[object RegExp]”- 数组:`Object.prototype.toString.call([])` 返回 “[object Array]” - 对象字面量:`Object.prototype.toString.call({})` 返回 “[object Object]” - 字符串:`Object.prototype.toString.call("hello")` 返回 “[object String]” - 数字:`Object.prototype.toString.call(123)` 返回 “[object Number]” - 布尔值:`Object.prototype.toString.call(true)` 返回 “[object Boolean]” - 函数:`Object.prototype.toString.call(function(){})` 返回 “[object Function]” - 正则表达式:`Object.prototype.toString.call(/regex/)` 返回 “[object RegExp]”
结尾: 如果这篇文章有帮助大家加深对原型链的理解,请点个赞支持一下 ?