【前端Talkking】JS-一步一步掌握Javascript中的原型与原型链

0.写在前面

如果大家想深入学习Javascript编程语言,Javascript中的原型及原型链是必须掌握的。当初我在学习原型及原型链的时候,就遇到过不少阻碍,希望通过我的这篇文章,能够让你真正的掌握JavaScript中的原型及原型链。好啦,开始我们的原型及原型链的旅途吧~
在介绍Javascript原型之前,我们先来了解一段历史。

1.Javascript继承机制的设计思想

1994年,网景公司发布了Navigator浏览器0.9版,当时这个版本的浏览器只能用来浏览,并不具有与用户用户进行互动的功能,比如说,要判断用户是否填写了表单数据,只能通过服务器来进行判断,这样会带来一个弊端:极大的浪费了带宽以及服务器资源。在这种情况下,就需要一种脚本语言,这种语言能够与浏览器进行交互,Brendan Eich负责开发这种脚本语言(也就是Javascript),当时,这位工程师认为这种脚本语言不需要设计的太复杂,只需完成简单操作即可,比如判断用户是否填写了表单数据。此时,我们还要了解下当时的编程语言背景,在1994年的时候,C++是最兴盛的面向对象的编程语言,Java1.0也将于第二年推出,Brendan Eich也将Javascript设计为面向对象的语言,在Javascript中一切皆对象。当时,他遇到了一个难题,到底需不需要将继承机制引入Javascript中?最终的结果是,也许他受到了C++和Java的影响,继承机制最终被引入到Javascript编程语言中。

下面我们来看看在C++中生成一个对象的方法:

A *a=new A(param);

而在Java中生成一个对象的方法:

Foo foo=new Foo();

我们再来看看Javascript生成一个对象的方法:

function Dog(name){
    this.name=name;
}

var dogA = new Dog("旺旺");
alert(dogA.name);//旺旺
我们再来看看Javascript中另外一种写法:

function Dog(name){
    this.name=name;
    this.species ="犬科";
}
var dogA = new Dog("旺旺");
dogA.species="猫科";
var dogB = new Dog("旺旺2");
alert(dogB.species);//犬科

在这个例子中,生成了两个对象dogA与dogB,dogA修改了species,但是我们访问dogB的species,还是原来的值。于是,我们可以看出通过这种方法生成的实例对象,每个对象都有自己的属性和方法的副本,实例对象间不能做到属性的方法的共享,这样带来的一个缺点就是极大的浪费了系统的资源。有没有改进的方法呢?有,肯定有!

2.Javascript中原型(prototype)的引入

考虑到上面的不足,这位工程师决定给每个构造函数添加一个prototype属性,这个属性指向一个对象,称为prototype对象。在Javascript中,一切皆对象,对象可以分为三类:实例对象(通过new和构造函数创建出来的对象)、函数对象(一般也称为函数)、原型对象(函数对象的prototype属性所指向的对象)。我们首先来看下实例对象中的属性和方法,如下图:

图片描述

实例一旦创建,将自动引用prototype对象的方法和属性,也就是说,针对实例对象而言,它的属性和方法可以分为两种:一种是本地的,另外一种是引用的。

prototype、_proto_和constructor三角关系

我将用下面的一副图来描述三者的关系:

图片描述

我将上幅图总结为下面几点内容:

  • 任何函数对象都有prototype属性,它指向对应的原型对象,表示其实例对象的原型对象;
  • 任何原型对象都有一个constructor属性,它指向对应的函数对象;

任何对象都有一个隐藏的_proto属性,它是对原型对象的引用;

  • _proto_属性不是一个规范的属性,只是部分浏览器实现了此属性(如chrome和Firefox),如果想访问对象的原型,可以使用Object.getPrototype(object)访问。

一定要牢记上面的几点内容,它将对后面的内容非常重要。

3 原型实例分析

说明:后面的实例,如果看不懂,可以再看看前面的内容,好好理解下前面的内容,一定可以理解后面的例子的。同时,下面的例子运行结果我会有一定的解释。
step1:查看对象的原型

function Person(name, age){
    this.name = name;
    this.age = age; 
    this.getInfo = function(){
        console.log(this.name + " is " + this.age + " years old");
    }
}

var will = new Person("Will", 28);
console.log(will.__proto__);
console.log(will.constructor);

运行结果:
图片描述

解释:will对象本身并没有"constructor"这个属性,但是通过原型链查找,找到了will原型(will.__proto__)的"constructor"属性,并得到了Person函数
对象之间的关系:

step2:查看对象will原型的原型

function Person(name, age){
    this.name = name;
    this.age = age; 
    this.getInfo = function(){
        console.log(this.name + " is " + this.age + " years old");
    }
}

console.log(will.__proto__ === Person.prototype);
console.log(Person.prototype.__proto__);
console.log(Person.prototype.constructor);
console.log(Person.prototype.constructor === Person);

运行结果:
图片描述

对象之间的关系

图片描述

step3:查看对象Object的原型

function Person(name, age){
    this.name = name;
    this.age = age; 
    this.getInfo = function(){
        console.log(this.name + " is " + this.age + " years old");
    }
}

console.log(Person.prototype.__proto__ === Object.prototype);
console.log(typeof Object);
console.log(Object);
console.log(Object.prototype);
console.log(Object.prototype.__proto__);
console.log(Object.prototype.constructor);

运行结果:
图片描述
对象之间的关系:
图片描述

step4:查看函数对象的原型

function Person(name, age){
    this.name = name;
    this.age = age; 
    this.getInfo = function(){
        console.log(this.name + " is " + this.age + " years old");
    }
}

console.log(Person.__proto__ === Function.prototype);
console.log(Person.constructor === Function)
console.log(typeof Function);
console.log(Function);
console.log(Function.prototype);
console.log(Function.prototype.__proto__);
console.log(Function.prototype.constructor);

运行结果:
图片描述

对象之间的关系:

图片描述

4 通过原型改进实例-实现继承

step1:最老的方式

function Person(name, age){
    this.name = name;
    this.age = age; 
    this.getInfo = function(){
        console.log(this.name + " is " + this.age + " years old");
    }
}

var will = new Person('Will',28);
var wilber = new Person("Will", 20);

对象之间的关系:

step2:通过原型prototype实现继承

function Person(name, age){
    this.name = name;
    this.age = age; 
   
}
Person.prototype.getInfo = function(){
    console.log(this.name + " is " + this.age + " years old");
}

对象之间的关系:
图片描述

5 原型链

什么是原型链?

由于_proto_是任何对象都有的属性,而Javascript中万物皆对象,所以会形成一条_proto_连接起来的链条,递归访问_proto_必须最终到头,并且值为null。
原型链有什么作用?
属性查找与隐藏:当Javascript引擎查找对象的属性时,先查找对象本身是否存在该属性,如果不存在,再沿着_proto_这条链向上查找,但不会查找自身的prototype。

var A = function(){};
var a = new A();

以上面的这幅图为例,查找某个属性的时候,会沿着这条原型链进行查找,直到为null。
原型链之属性查找

  function Person(name, age){
        this.name = name;
        this.age = age; 
    }
    Person.prototype.MaxNumber = 9999;
    Person.__proto__.MinNumber = -9999;
    var will = new Person("Will", 28);
    console.log(will.MaxNumber);// 9999
    console.log(will.MinNumber);//undefined


原型链之属性隐藏


function Person(name, age){
    this.name = name;
    this.age = age; 
}
Person.prototype.getInfo = function(){
    console.log(this.name + " is " + this.age + " years old");
    
}

var will = new Person("Will", 28);
will.getInfo = function(){
    console.log("getInfo method from will instead of prototype");//
};
will.getInfo();

对象创建方式影响原型链的构成

var July = {
    name: "July",
    age: 28,
    getInfo: function(){
        console.log(this.name + " is " + this.age + " years old");
    },
}
console.log(July.getInfo());


hasOwnProperty

var will = new Person('Will',28);
    var wilber = new Person("Will", 20);

    for(var attr in will){
        console.log(attr);
    }    
    console.log('---------------');
    for(var attr in wilber){
        if(will.hasOwnProperty(attr)){
            console.log(attr);
        }
    }

6.总结

  • 在Javascript中,通过原型(prototype)实现了对象的继承;
  • 在Javascript中,一切皆对象,prototype、_proto_与constructorJavascript中所有的对象关联起来;
  • 原型链可以实现对象属性的查找和隐藏; hasOwnProterty函数可以用来判断属性是对象本地属性还是原型链上的属性;
    相信到这里,你已经掌握了Javascript中的原型和原型链的知识点了。

欢迎关注我的微信公众号。您的支持将鼓励我继续创作!

图片描述

阅读 1.3k

推荐阅读
前端Talkking
用户专栏

一个热爱前端开发的程序员,用户记录学习、开发过程中的点点经历

60 人关注
13 篇文章
专栏主页