JavaScript原型和原型链精讲

在了解原型之前,先了解一下对象。

1.对象

1.1 对象的创建

先看看对象的创建:

//第一种: 对象字面量方式
let obj1 = {
  name: "刘德华",
  age: 50,
}
 
//第二种: Object构造函数模式
let obj2 = new Object()
obj2.name = "刘德华"
obj2.age = 50
 
//第三种: 构造函数模式
function Test(name, age){
    this.name = name
    this.age = age
    this.say = function(){
        console.log('说话')
    }
}
let obj3 = new Test('Jack', 26)
let obj4 = new Test('Rose', 25)

1.2 为什么会有构造函数方式创建对象

日常中我们最常用的应该是字面量方式,但为什么会出现第三种方式-----构造函数方式?
当需要有多个obj1,obj2的时候,使用字面量方式时需要重复代码去创建对象;而使用构造函数的方式只需要写一遍属性和方法,就可以通过new关键字,new出多个不同的对象。

如下,在控制台打印如下obj3.say === obj4.say,结果是false。看起来是同一个函数方法,实际并不相等。因为它们的内存地址是不同的,每个new出来的obj都包含一份独立的属性和方法(相信大家会发现,这样子会浪费内存)。

1.3 结论

function Test(){
    this.name = '刘德华'
    this.say = function(){
        console.log('我能说话')
    }
}
let obj3 = new Test()
let obj4 = new Test()
console.log(obj3.say === obj4.say) // false
console.log(obj3.name === obj4.name) // true

上面例子中的obj3和obj4都是Test的实例(也叫实例对象),而Test是obj3和obj4的构造函数。 实例都有一个构造函数属性(constructor)指向构造函数,通过构造函数创建的对象拥有构造函数内部定义的属性和方法。

function Test(name, age){
    this.name = name
    this.age = age
}
 
Test.prototype.say = function(){
    console.log('我能说话')
}
let obj3 = new Test('Jack', 26)
let obj4 = new Test('Rose', 25)
 
// constructor属性指向构造函数
console.log(obj3.constructor === Test)  // true
console.log(obj4.constructor === Test)  // true
console.log(obj4.constructor === obj3.constructor) // true

我们需要知道:

  • Test是构造函数
  • obj3和obj4 是构造函数Test的实例,实例的属性constructor指向构造函数Test

2.原型(原型对象)

如上,obj3和obj4都需要调用Test中的say方法,我们想将公共方法放到一个公共的地方。这时候就需要引出原型(prototype)这个概念。

在JavaScript中,每一个对象(函数也是对象)都有一个特殊的属性叫做原型(prototype),它指向另一个对象,这个对象(Test.prototype)被称为原型对象,原型对象是用来共享属性和方法的

打印Test.prototype可以看到下图中原型对象存在一个constructor属性,指向Test。

在这里插入图片描述

console.log(Test.prototype.constructor === Test)// true)

由上可以得出结论:

  • 原型对象有一个constructor属性指向构造函数本身(Test)。
  • 原型对象是一个普通的对象,它包含属性和方法。
  • 原型对象的属性和方法会被继承到所有通过原型链与它相连的对象。

此时,我们就可以把say方法放到这个原型对象上, obj3 和 obj4 就可以访问这个方法,就不用写到Test中去重复占用内存,所有new出来的实例都可以使用此方法。

打印 obj3.say === obj4.say,为true, 证明obj3和obj4调用的say在内存同一块区域。

function Test(name, age){
    this.name = name
    this.age = age
}
 
Test.prototype.say = function(){
    console.log('我能说话')
}
let obj3 = new Test('Jack', 26)
let obj4 = new Test('Rose', 25)
 
obj3.say()    // 我能说话
obj4.say()    // 我能说话
console.log(obj3.say === obj4.say)    // true

构造函数和实例之间就初步构成了这样一个关系,如图:
在这里插入图片描述

function Test(name, age) {
  this.name = name;
  this.age = age;
}

Test.prototype.say = function () {
  console.log("我能说话");
};
let obj3 = new Test("刘德华", 57);
console.log(Test.prototype);
console.log(obj3.constructor === Test); //true
console.log(Test.prototype.constructor === Test); //true
console.log(obj3.constructor === Test.prototype.constructor); //true
//这个地方要注意,obj3.constructor === Test.prototype.constructor,不代表obj3 === Test.prototype
//只能说obj3和Test中都有一个constructor属性,这个属性都指向同一个地址
console.log(obj3 === Test.prototype); //false

3.隐式原型__proto__

在JavaScript中,每个对象都有一个__proto__属性(左右两边两个短下划线),这个__proto__就被称为隐式原型。

console.log(obj3.__proto__ === Test.prototype)// true

由上可知:

  • 每个JavaScript对象都有一个隐藏的原型对象属性__proto__,它指向创建它的构造函数的原型对象(Test.prototype)
  • __proto__存在的意义在于为原型链查找提供方向,原型链查找靠的是__proto__,而不是prototype
function Test(name, age){
    this.name = name
    this.age = age
}
 
Test.prototype.say = function(){
    console.log('我能说话')
}
let obj3 = new Test('Jack', 26)
 
 
1, 构造函数是? 实例是?
2, obj3.constructor === Test   true or false?
3, obj3.__proto__ === Test ?
4, Test.prototype === obj3.__proto__ ?
5, obj3.__proto__.constructor === Test ?
// 1, Test  obj3  
// 2,true  
// 3,false  
// 4,true
// 5,true
//第五条:obj3.__proto__.constructor=Test.prototype.constructor=Test

4.原型链

4.1 Object.prototype

在上面第二点中,每个JavaScript对象都有一个隐藏的原型对象属性__proto__。所以Test的原型对象Test.prototype也有一个隐式原型__proto__。控制台输出如下:

在这里插入图片描述

console.log(Test.prototype.__proto__=== Object.prototype);//true
  • Test.prototype的隐式原型(__proto__)就是Object.prototype
  • 所有的对象,包括构造函数的原型对象,最终都继承自Object.prototype,这是JavaScript原型链的顶点

Object.prototype同样也会存在属性constructor指回Object。(上面提到过)

在这里插入图片描述

4.2 原型链条

在控制台打印Object.prototype,会发现 Object.prototype也是一个对象。

既然它也是对象,它也存在隐式属性__proto__。想想看,如果Object.prototype.__proto__再去指向某个对象的原型(prototype),那整条线就显得无穷无尽,一直找下去。

我们的开发者当然考虑到了这点,Object.prototype作为原型链的顶端,位于原型链的最末端。因此,它不再有自己的原型,所以Object.prototype.__proto__ 指向null,表示原型链的终点。

在这里插入图片描述

console.log(Object.prototype.__proto__ === null)//true

于是就有以下关系:
在这里插入图片描述

每个对象都有一个原型(prototype),它指向另外一个对象,而指向的对象又存在属性(__proto__)指向另外一个对象。当我们访问对象(obj3)的属性时,会先在对象定义的属性中进行查找,没找到就会沿着__proto__一路向上查找,最终形成一个链式结构,这整个链式结构就叫做原型链。

如果在原型链中找到了这个属性,就返回找到的属性值;如果整个原型链都没找到这个属性值,则返回undefined,没找到方法直接报错(not a function)。

4.3 示例

function Test(name, age){
    this.name = name
    this.age = age
}
Test.prototype.say = function(){
    console.log('我能说话')
}
let obj3 = new Test('Jack', 26)
let obj4 = new Test('Rose', 24)
 
1, Test.prototype === ( ) ?
2, obj3.__proto__.__proto__ === ( ) ?
3, obj3.__proto__ === obj4.__proto__ ?
4, Test.prototype.__proto__ === ( ) ?
5, obj4.__proto__.constructor === ( ) ?
6, Object.prototype.__proto__ === ( ) ?
7, obj3.say === obj4.say ?
 
// 1, obj3.__proto__ 或 obj4.__proto__    
// 2,Object.prototype    
// 3, true (二者都由Test new出来,在原型链上都指向 Test.prototype)
// 4, Object.prototype    
// 5, Test    
// 6, null (终点)    
// 7,true (同问题3)

5.补充

5.1 普通对象与函数对象

在JavaScript中,有两种主要类型的对象:普通对象和函数对象。普通对象最常见,通过"{ }"创建的就是普通对象;通过new Function出来的就是函数对象(函数声明、函数表达式创建的为函数对象),我们可以用typeof来区分。

function f1(){}
 
var f2 = function(){}
 
var f3 = new Function('name')
 
var b1 = new f1()
 
var b2 = {name: 'Rose'}
 
var b3 = new Object()
 
typeof f1    // 'function'
typeof f2    // 'function'
typeof f3    // 'function'
typeof b1    // 'object'
typeof b2    // 'object'
typeof b3    // 'object'
typeof Object // 'Function'

如上,f1,f2,f3都是函数对象,Object也是函数对象,b1,b2,b3为普通对象; 简单理解,普通对象就是我们最常见的 { } 键值对;函数对象通常包含了一个function

在上面我们提到过每一个对象(函数也是对象)都有一个特殊的属性叫做原型(prototype),obj3是对象,但是它没有prototype的这个原型属性。所以这话不够准确,只有函数对象才具有prototype这个原型属性

console.log(obj3.prototype); //undefined

5.2 原型链机制

__proto__存在的意义在于为原型链查找提供方向。

示例1:

function Test(name, age) {
   this.name = name;
   this.age = age;
}
Test.prototype.sex = "男";
let obj = new Test("刘德华", 26);
console.log(Test.sex); //undefined

示例2:

function Test(name, age) {
   this.name = name;
   this.age = age;
}
Object.prototype.sex = "男";

let obj = new Test("刘德华", 26);
console.log(Test.sex); //男

如上:
示例1在构造函数Test的原型对象(Test.prototype)上定义了sex,Test.sex为undefined ( 注意是Test.sex不是obj.sex);实例2在Object.prototype上定义sex,Test.sex能获取到值。为什么在Test.prototype上定义,Test.sex不能通过原型链到Test.prototype上去找到sex属性;而定义到顶点Object.prototype上,又能通过原型链找到了

看了大佬的一些解答分析,定义到Test.prototype上的时候,Test.sex并没有通过原型链查找,而是检查Test自身是否定义该属性,没有sex,所以是undefined。感觉解释会有点说不通,定义到Object.prototype,不也应该是Test自身检查,也会检查不到,但我们能输出值,证明的确是顺着原型链去查找到了Object.prototype上。

既然__proto__才是原型链查找的方向,同时对象都有__proto__这个属性,那构造函数Test是属于函数对象,函数对象也是对象 那是否Test也会存在__proto__这个属性呢? 在上面的原型链图中并没有指出这个属性,请往下看。

5.3 Function的原型

我们之前提到__proto__指向创建它的构造函数的原型对象。

function Test(name, age){
    this.name = name
    this.age = age
}
var obj = new Test('Jack', 26)
 
Test.__proto__ === Function.prototype  // true

(1)构造函数Test的隐式原型( __proto__)指向 Function.prototype, 函数对象的__proto__指向Function.prototype

为什么呢?JavaScript在设计时决定了构造函数本身是函数,当然也可以通过指向Function.prototype来访问原型链上的属性和方法,让构造函数也能参与到原型链中来(虽然不建议通过构造函数访问)

Function会有原型(prototype),当然也有隐式原型(__proto__),打印这两个原型,会发现二者互相相等 Function.prototype === Function.__proto__ 是不是很神奇? 看起来像是自己创造了自己。

console.log(Function.__proto__);
console.log(Function.prototype);
console.log(Function.__proto__ === Function.prototype);

上述代码结果如下:
在这里插入图片描述

对象都拥有隐式原型(__proto__),Function.prototype当然也存在__proto__,打印出来看看。

 console.log(Function.prototype.__proto__);
 console.log(Object.prototype);
 console.log(Function.prototype.__proto__ === Object.prototype);

上述代码结果如下:
在这里插入图片描述再来看原型链关系,这时候就成了这样:
在这里插入图片描述

(2)如果再深究一点,Object也是函数对象,Object.__proto__ 也会指向Function.prototype
(3)构造函数Test也有constructor属性,这个属性指向创建该函数的构造函数;如果自己没有定义构造函数,会指向到 Function (Test.constructor === Function
原型链关系就成了这样:
在这里插入图片描述

那么针对上面 【原型链机制】 中的问题也有了答案,在构造函数Test上访问Object.prototype中的属性时,其实是顺着Test.__proto__这条路径从Function去访问。

看一个例子:

function Test(name, age){
    this.name = name
    this.age = age
}
let obj = new Test('Jack', 26)
 
Object.prototype.price = 2000
 
Function.prototype.price = 300
 
console.log(Test.price)//300

Test在自身没有找到price,顺着Test的__proto__Function.prototype上找到了price = 300,所以直接返回 300;若Function.prototype上没有price,才会进一步顺着__proto__找到Object.prototype

在实际使用中,要获取定义到Test.prototype上的属性,还可以用原型对象Test.prototype.price访问;不过建议还是通过实例(obj)来访问具体的属性(obj.name),而不是构造函数Test.name访问,毕竟实例new出来的目的就是为了调用构造函数上的方法属性

得出结论:__proto__存在的意义在于为原型链查找提供方向,原型链查找靠的是__proto__,而不是prototype

5.4 判断对象是否有某属性

function Test(){
    this.a=1;
}
Test.prototype.b=2;
Object.prototype.c=3;
let test=new Test();
console.log(test.hasOwnProperty("a"))//true
console.log(test.hasOwnProperty("b"))//false
console.log(test.hasOwnProperty("c"))//false

console.log("a" in test)//true
console.log("b" in test)//true
console.log("c" in test)//true

hasOwnProperty表示判断对象本身有没有某属性;in表示判断对象原型链有没有某属性;

6.代码示例

示例1:

function Test(){}
let obj1 = new Test()
 
1, console.log(obj1.__proto__ === Test.prototype )//true
2, obj1.__proto__.__proto__ === ()?//Object.prototype
3, Object.prototype.__proto__ === ()//null
4, console.log( Test.prototype === obj1.__proto__ )//true
5, Test.__proto__ === Function.prototype ?//true
6, Function.prototype.__proto__ === () ?//Object.prototype
7, Funcion.prototype === Function.__proto__ ?//true
 
// 1,true 2,Object.prototype 3,null 4,true 5,true 6,Object.prototype 7,true

示例2:

let Test = function(){}
Test.prototype.name = 'Rose'
let obj1 = new Test()
Test.prototype = {name: 'Rose', age: 26}
let obj2 = new Test()
 
obj1.name//rose
obj1.age//undefined  
obj2.name//'Rose'
obj2.age//26
 
// 'Rose'  undefined  'Rose'  26
// Test.prototype =  {} 将Test.prototype.name 覆盖

示例2:

function Test(){}
Object.prototype.a = function(){
    console.log(1)
}
Function.prototype.b = function(){
    console.log(2)
}
 
let obj = new Test()
 
obj.a()//打印1
obj.b()//报错了
Test.a()//打印1
Test.b()//打印2
 
//  1  (not a function)  1  2

7.总结

  • 每个对象均存在隐式原型(__proto__),函数对象才有prototype属性
  • __proto__存在的意义在于为原型链查找提供方向,原型链查找靠的是__proto__,而不是用prototype来查找原型链
  • 函数对象的__proto__都指向Function.prototype
  • 原型链:
    ‌原型链‌是JavaScript中实现对象继承和属性查找的一种机制。 每个对象都有一个隐式原型属性(__proto__),多个原型通过__proto__链接在一起形成的链式结构就是原型链。当访问一个对象的属性或方法时,如果该对象本身没有该属性或方法,JavaScript引擎会沿着原型链向上查找它的__proto__,直到找到对应的属性或方法或者到达原型链的顶端(通常是Object.prototype)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

太阳与星辰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值