快醒醒,带你穿过原型和原型链的迷雾

开发 前端
对于初学js的继承机制--”原型“和”原型链“这两个概念的理论时,总是忘了记、记了忘。所以死记硬背真的是没得用的,得深入理解其背后的设计思想,得理解加记忆,如虎添翼。至于为什么这样说,就随着这篇文章去揭开珍妮的面纱,如剥洋葱般去探究它的本质。

1.写在前面

我们知道在面向对象编程的语言中,有一句统筹全局的中心句--”万物皆对象“,原型和原型链也是基于这个基础理解的。

对于初学js的继承机制--”原型“和”原型链“这两个概念的理论时,总是忘了记、记了忘。所以死记硬背真的是没得用的,得深入理解其背后的设计思想,得理解加记忆,如虎添翼。

至于为什么这样说,就随着这篇文章去揭开珍妮的面纱,如剥洋葱般去探究它的本质。

来不及解释,快上车。

2.JS继承的设计思想

我们知道创建对象有两种方式:一种是最常见的对象字面量,一种就是常说的通过new来创建对象实例。其实这两种方式描述的对象都是等价的,属性和方法都是一致的。

// 字面量对象
let obj = {
name:"yichuan",
age:18,
sayName(){
console.log("name: ", this.name);
}
}

// new创建对象实例
let obj2 = new Object();
obj2.name = "pingping";
obj2.sayName = function(){
console.log("name: ", this.name);
}

使用对象字面量或者Object构造函数可以轻松创建对象,但是在创建具有同样接口的多个对象时,会重复编写很多代码。那么,我们想可不可以创建一个容器,将共享的属性和方法存在里面,这样就可以在多个对象中使用。

在es6之前没有正式支持类和继承的结构,但是能够通过原型链继承进行模仿实现类和继承。事实上,es6的类也的确是封装了构造函数和原型继承的语法糖。

工厂模式

工厂模式可以用于抽象创建特定对象过程,解决创建多个类似对象的问题,但是没有解决对象标识的问题,不能设置新创建对象的类型。

// 工厂模式
function createPerson(name, age, city){
let obj = new Object();
obj.name = name;
obj.age = age;
obj.city = city;
obj.sayName = function(){
console.log("my name is : ", this.name);
}
return obj;
}

let preson1 = createPerson("yichuan",18,"BeiJing");
let preson2 = createPerson("onechuan",28,"GuangZhou");

构造函数模式

在js中构造函数是用于创建特定类型对象的,在前面使用了Object原生构造函数创建对象,运行时可以直接在执行环境中使用。但其实,我们也可以进行自定义构造函数,用函数的形式为自己的对象定义属性和方法。


// 构造函数模式
function Person(name, age, city){
this.name = name;
this.age = age;
this.city = city;
this.sayName = function(){
console.log(this.name);
}
}

let p1 = new Person("yichuan",18,"Beijing")
let p2 = new Person("onechuan",19,"Guangzhou")
p1.sayName()//yichuan
p2.sayName()//onechuan

构造函数模式相比于工厂模式而言,没有显式创建对象,属性和方法都是直接赋给this,也没有return返回任何值。

那么使用new构造函数的方式创建对象,具体会发生什么?

会有如下操作:

1)在内存中开辟新的空间创建新对象。

2)这个新对象内部的_proto_特性被赋值为构造函数的prototype属性

3)构造函数内部的this指向新对象

4)给新对象添加属性

5)如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象

构造函数是什么?

其实构造函数也是函数,和普通函数没啥区别,只是调用的方式不同而已,通过new调用的函数是构造函数。构造函数定义的方法会在每个实例上都创建一遍,每次定义函数时,都会初始化一个对象。

function Person(name, age, city){
this.name = name;
this.age = age;
this.city = city;
this.sayName = new Function("console.log(this.name);");
}

原型模式

每个函数都会创建一个prototype属性,这个属性是一个对象,包含应该由特定引用类型的实例共享的属性和方法,而这个对象就是通过调用构造函数创建的实例对象的原型,那么这个对象就叫做原型对象。

原型对象的作用是:在原型对象上定义的属性和方法可以被对象实例所共享,即对象原型相当于一个存储公共属性和方法的容器。

等等,这不就是前面所说的构造器中直接赋值给对象实例的值吗?

其实不是,其实在进行构造函数Person定义时,构造函数内部是个空对象,没有任何属性和方法。然而,可以通过在Person的prototype上直接定义属性和方法,来挂载到Person对象的原型上。这样通过new Person()得到的对象实例是可以共享Person.prototype上的属性和方法。如下所示:

function Person(){}

Person.prototype.name = "yichuan";
Person.prototype.age = 18;
Person.prototype.city = "Beijing";
Person.prototype.sayName = function(){
console.log(this.name);
}

let p1 = new Person();
p1.sayName();
let p2 = new Person();
p2.sayName();

3.原型和原型链

上面的原型模式中,已经将原型和原型对象的概念引出来了,那么我们重新整理下思路:

构造函数的prototype属性指向的原型对象中,定义了所有实例对象都能够共享的属性和方法,而不需要共享的属性和方法则直接定义在构造函数上。

通过构造函数创建的实例对象,会自动拥有原型对象上共享的属性或方法。

function Person(name){
this.name = name;
}
Person.prototype.address = "earth";
const p = new Person("yichuan");//{name:age}
const p1 = new Person("onechuan");

在上面代码中,在构造函数Person的prototype原型上定义了一个公共属性 Person.prototype.address="earth";,那么通过new出来的实例对象p和p1都会天生继承属性address,而p和p1各自的name值分别为"yichuan"和"onechuan"。

函数与对象的关系

  • 函数是对象,对象都是通过函数创建的
  • 函数与对象并不是简单的包含与被包含的关系

原型的类别

  • 显式原型:prototype,是每个函数function独有的属性
  • 隐式原型:_proto_,是每个对象都具有的属性

原型和原型链

  • 原型:一个函数可以看做一个类,原型是所有类都有的一个属性,prototype原型的作用就是给这个类的每个对象都添加一个统一的方法。
  • 原型链:每个对象都有一个_proto_,它指向它的prototype原型对象;它的prototype原型对象又有一个_proto_,指向它的prototype原型对象,就这样向上查找原型,直到顶级对象Object.prototype,最终指向是null

用图片描述原型链:

我们看到原型链的最终归属都是对象,而Object.prototype的_proto_指向的是null,这是为了避免死循环而设置的,所以一切皆空。

4.参考文章

【重点】图解:告诉面试官什么是 JS 原型和原型链?

面不面试的,你都得懂原型和原型链

《Javascript高级程序设计》

5.写在最后

所有实例对象的_proto_都指向该构造函数的prototype原型对象 (即:p._proto_ === Person.prototype)。

所有函数(包括构造函数)都是Function的实例,所有函数的_proto_都指向Function的原型对象

所有的原型对象(包括 Function的原型对象)都是Object的实例,所以_proto_都指向 Object(构造函数)的原型对象。而Object构造函数的 _proto_指向 null。

Function构造函数本身就是Function的实例,所以_proto_指向Function的原型对象。

责任编辑:武晓燕 来源: 前端一码平川
相关推荐

2020-02-20 14:00:15

JavaScript原型原型链

2020-09-10 07:04:30

JSJavaScript 原型链

2020-10-20 08:35:34

JS基础进阶

2012-01-05 15:07:11

JavaScript

2012-11-08 10:40:47

JavaScript原型链

2019-02-27 16:00:48

JS原型原型链对象

2017-04-07 11:15:49

原型链原型Javascript

2023-08-28 07:12:54

2022-05-26 09:20:01

JavaScript原型原型链

2022-03-29 09:15:55

Javascript函数属性

2016-06-07 14:28:39

Javascript原型

2017-05-05 10:31:35

JavaScriptprototype__proto__

2016-12-27 09:10:29

JavaScript原型链继承

2016-05-06 14:02:18

JavaScript原型链

2015-06-09 10:55:58

JavaScriptinstanceof运

2022-06-20 09:22:55

js原型链前端

2023-04-07 09:07:11

2011-08-31 14:48:33

JavaScript

2023-05-30 15:06:21

JavaScript属性开发

2022-04-14 20:43:24

JavaScript原型链
点赞
收藏

51CTO技术栈公众号