众所周知js原型及原型链是很多开发者的一个疼点(我也不例外),我也曾多次被问起,也问过不少其他人,如果在自己没有真正的去实践和理解过;那么突然之间要去用最简单的话语进行概述还真不是一件容易的事情;

其实工作中看似神秘的js原型也并不是那么难以理解,最终其目的无非是为了达到方法、属性共享代码重用的目的;在我所了解的编程语言中都会用到object这个最顶层对象,然而生命的法则最终是从无到有,就如同世界先有鸡还是先有蛋一样。

一、 Class

先来看一个简单的es6的例子吧

假如我们定义一个类


/*用于全局状态管理*/
class State{}

然后在某个页面为其State.draw = true 然后在项目的任何地方都能使用State.draw来获取其值;而且当你一个地方更改,其它页面也会同时获取到更改后的值


class State{
constructor(draw){
this.draw = draw;
}
}

然而使用对象属性方式就不一样了;


var state1 = new State('bar');
var state2 = new State('pie');
state2.draw = 'hello'; // 不管state2 的draw 怎么改都不会影响到 state1 的属性值

为啥扯上了这么一大圈,这个和原型好像没关系,其实是有的,State.draw 就等同于 State.prototype.draw = true

this.draw = draw;就等同于 普通的函数赋值

 function Sate(){
this.draw = draw;
}

二、普通 function


function Sate(draw){
this.draw = draw;
}
var state1 = new State('bar');
var state2 = new State('pie');
state2.draw = 'hello'; // 不管state2 的draw 怎么改都不会影响到 state1 的属性值

使用node运行测试

C:\Users\Lenovo>node
> function State(draw){
... this.draw = draw;
... }
undefined
> var state1 = new State('bar');
undefined
> var state2 = new State('pie');
undefined
> console.log(state1.draw);
bar
undefined
> console.log(state2.draw);
pie
undefined
> state2.draw = 'circle';
'circle'
> console.log(state1.draw);
bar
undefined
> console.log(state2.draw);
circle
undefined
>

柑橘和第一个Class案例是有点相同了,在题中state1 和 state2 完全是两个不同的对象,他们各自维护自己的属性和方法和其他人没得关系

> console.log(state1);
State { draw: 'bar' }
undefined
> console.log(state2);
State { draw: 'pie' }
undefined
>

三 、原型(prototype)

function State(){}
State.prototype.draw = 'pie'; var state1 = new State();
var state2 = new State(); console.log(state1.draw);
console.log(state2.draw); state2.draw = 'bar'; console.log(state1.draw);
console.log(state2.draw);

使用node运行测试

C:\Users\Lenovo>node
> function State(){}
undefined
> State.prototype.draw = 'pie';
'pie'
> var state1 = new State();
undefined
> var state2 = new State();
undefined
> console.log(state1.draw);
pie
undefined
> console.log(state2.draw);
pie
undefined
> console.log(state1); // 看:打印的 state1和 state2 是一个空的 State {} 那上面却能正常打印draw
State {}
undefined
> console.log(state2);
State {}
undefined
>

看:打印的 state1和 state2 是一个空的 State {} 那上面却能正常打印draw;其实这里的state1 和state2 只是State 的一个引用,在实例本身是没有任何属性的,但是他可以通过自身的__proto__关联到Sate这个构造函数


> console.log(state1.__proto__);
State { draw: 'pie' }
undefined
> console.log(state2.__proto__);
State { draw: 'pie' }
undefined
>

而注意的是这个属性并没有直接关联构造函数;只是关联了构造函数的prototype属性(原型对象)


> console.log(State);
[Function: State]
undefined
> console.log(State.prototype);
State { draw: 'pie' }
undefined
>

从打印可以得出 State.prototype 就等同于了state2.__proto__

so state2.proto == State.prototype


> console.log(state2.__proto__ == State.prototype);
true
undefined
>

四、原型的用途

然而说了半天,这个原型的用处究竟在哪里呢?

其实他的主要用途就是 继承(extends),代码重用

如:


function State(){}
State.prototype.draw = 'pie';
State.prototype.drawGraphic = function(){
let height = 100;
let width = 100;
// draw a graphics...
console.log(`Draw a graphic with a height of ${height}px and a width of ${width}px`);
}

上面定义了绘图的构造函数和方法,现在就可以开始使用了


var state1 = new State();
var state2 = new State();
var state3 = new State();
var state4 = new State(); // 使用实例化的四个实例,调用drawGraphic进行图形绘制
state1.drawGraphic();
state2.drawGraphic();
state3.drawGraphic();
state4.drawGraphic();

如上:实例化的四个实例,调用drawGraphic进行图形绘制,

然而他们并没有去创建各自的方法,只是直接从原型引用了State 上的drawGraphic,这样就极大的节约了开销;

如果不使用原型的方式,这个四个对象将会创建四个对应的方法,这就是一种极大浪费。

如果不明白可以来看看看开始的例子

4.1 普通function, 实例化多个对象

C:\Users\Lenovo>node
> function State(draw){
... this.drawGraphic = function(){
..... let height = 100;
..... let width = 100;
..... // draw a graphics...
..... console.log(`Draw a graphic with a height of ${height}px and a width of ${width}px`);
..... }
... };
undefined
> var state1 = new State('bar');
undefined
> var state2 = new State('pie');
undefined
> console.log(state1);
State { drawGraphic: [Function] }
undefined
> console.log(state2);
State { drawGraphic: [Function] }
undefined
>

从运行可以看出每个new出来的实例都会创建属于自己的实例方法和属性

4.2 使用原型方式

只用从State 上面调用drawGraphic 方法,而不会自己再去创建,就同java 的继承一个意思,直接从父类继承方法,属性,然后使用。看:

C:\Users\Lenovo>node
> function State(){}
undefined
> State.prototype.drawGraphic = function(){
... let height = 100;
... let width = 100;
... // draw a graphics...
... console.log(`Draw a graphic with a height of ${height}px and a width of ${width}px`);
... }
[Function]
> var state1 = new State();
undefined
> var state2 = new State();
undefined
> console.log(state1);
State {}
undefined
> console.log(state2);
State {}
undefined
> state1.drawGraphic();
Draw a graphic with a height of 100px and a width of 100px
undefined
> state2.drawGraphic();
Draw a graphic with a height of 100px and a width of 100px
undefined
>

4.3 实例原型重写

那有的童鞋就会问了你这个只能调一个方法打印同样的图形,太死板了;其实不然,原型也支持重写(override)

改改刚才的案例

function State(){}
State.prototype.draw = 'pie';
State.prototype.drawGraphic = function(){
let height = 100;
let width = 100;
// draw a graphics...
console.log(`Draw a ${this.draw} with a height of ${height}px and a width of ${width}px`);
}

使用node运行测试

C:\Users\Lenovo>node
> function State(){}
undefined
> State.prototype.draw = 'pie';
'pie'
> State.prototype.drawGraphic = function(){
... let height = 100;
... let width = 100;
... // draw a graphics...
... console.log(`Draw a ${this.draw} with a height of ${height}px and a width of ${width}px`);
... };
[Function]
> var state1 = new State();
undefined
> var state2 = new State();
undefined
> var state3 = new State();
undefined
> state1.drawGraphic();
Draw a pie with a height of 100px and a width of 100px
undefined
> state2.drawGraphic();
Draw a pie with a height of 100px and a width of 100px
undefined
> state3.drawGraphic();
Draw a pie with a height of 100px and a width of 100px
undefined
> state2.draw = 'circle';
'circle'
> state1.drawGraphic();
Draw a pie with a height of 100px and a width of 100px
undefined
> state2.drawGraphic();
Draw a circle with a height of 100px and a width of 100px
undefined
> state3.drawGraphic();
Draw a pie with a height of 100px and a width of 100px
undefined
>

看运行结果:state2将 draw 重新设置为了 circle ;再次调用打印 Draw a circle with a height of 100px and a width of 100px;然而他并没有影响到其他的实例(其实说白了就是在执行state2.draw = 'circle';操作时重写了构造函数的draw这个属性)


为了能更好理解继承和重写:来用我们小学老师教我的语文解释一哈【

继承如同你父亲开了一家xxx公司,你就可以直接找财务开个20万,今天要去约个女朋友吃饭,然后财务一看原来是少爷呀,大笔一挥给你了,然后你再开上你父亲的法拉利愉快的约会去了。

当某一天你发现自己也该干一番事业了,于是开始模仿你父亲建起来了同样的公司和相同的经验模式,也许有人会想干嘛不直接把父亲公司改为自己的?

那肯定不行啦,因为那样老大,姥二,老三……不会把你打死呀。最后只好自己模拟了个和你父亲相同的xxx子公司,现在出去约会就直接叫自己财务开单了,然后开着自己的法拉利愉快的且。

假如哪天经验不当(女朋友太多,哈哈),又得去找你父亲的财务了,那就不一样了现在得明确指定是去父亲的财务那里(super.财务)还是自己的财务那里开支票;

否则你直接给你手下说去叫财务给我20万,他第一反应当然是去找你自己公司的财务了,啊哈哈。】


C:\Users\Lenovo>node
> console.log(state2);
State { draw: 'circle' }
undefined
> console.log(state2.__proto__);
State { draw: 'pie', drawGraphic: [Function] }
undefined
>

如果上面啥子重写依然搞不清楚那可以把它看成(虽然不是太准确,为了理解还是可以的)

在执行state2.draw = ‘circle’;是为该state2实例对象新增了一个draw的属性,

那你可能会迷惑了打印是为啥会是circle 而不是pie呢,不是说好的state2 的原型指向State.prototype吗,

State中的draw并没有改变呀,其实问题在于js 原型加载机制,

首先获取属性值会优先从自己对象上面查找,当对象没有该属性才会会通过__proto到原型对象上面去找那个;

假如该State.prototype上也没有该属性,他会再根据State.prototype.__proto__继续向上,直到Object;最后如果都没有;那就给你个undefined了

4.4 原型对象重写

那你说假如我就是想更改所有对象的draw咋办呢。当然有办法(你想翻天谁都拦不住,,哈哈)

> state2.__proto__.draw = 'trinagle';
'trinagle'
> console.log(state3.drawGraphic());
Draw a trinagle with a height of 100px and a width of 100px
undefined
undefined
> console.log(state2.drawGraphic());
Draw a circle with a height of 100px and a width of 100px
undefined
undefined
> console.log(state1.drawGraphic());
Draw a trinagle with a height of 100px and a width of 100px
undefined
undefined
>

看到没有state2.__proto__.draw = 'trinagle'一执行,其他state1和state3就瞬间改变了打印为Draw a trinagle ...

state2没有遭更改因为最开始他就重写了一次(他自己有了自己的子公司)

五、 原型链

说了半天没讲到原型链,不地道,其实上面都已经出现过n次了,只是你没注意(世上最遥远的距离是:我就在你眼前你却不认识)哈哈扯远了。

想想一个实例对象和函数对象是怎么取得联系的,不就是通过__proto__这个属性(这个属性会在函数对象或是普通对象一降生就会自带而来)吗?

那就对了其实他作用就是建立实例与函数对象之间的一条链子简称原型链

注意这个__proto__ 是所有人都有(一视同仁);然而prototype这就就不一样了(只有大佬才具备,哈哈)

C:\Users\Lenovo>node
> function State(){}
undefined
> var state1 = new State();
undefined
> console.log(State.prototype);
State {}
undefined
> console.log(state1.prototype);
undefined
undefined
>

看到了吧。


5.1 函数对象

现在来揭开原型链的神秘面纱了

接着看

C:\Users\Lenovo>node
> function State(){}
undefined
> var state1 = new State();
undefined
> console.log(State.prototype);
State {}
undefined
> console.log(state1.__proto__);
State {}
undefined
> console.log(State.prototype == state1.__proto__);
true
undefined

从上得出State.prototype == state1.__proto__ 证明 State.prototype也就如同State的一个实例

佐证一下:每一个实例对象都会默认携带一个constructor属性指向其构造函数,那么原型对象为什么也会有一个constructor属性呢,其实他也是构造函数的一个实例

undefined
> console.log(State.prototype.constructor);
[Function: State]
undefined
> console.log(state1.constructor)
[Function: State]
undefined
> console.log(state1.constructor == State.prototype.constructor)
true
undefined
>

ok State和他的实例之间的爱恨情仇算是基本清楚了吧

不清楚再简单画一哈:

State ----------prototype------------------->State.prototype

函数对象(State {})通过prototype属性指向它的原型对象(State.prototype

State.prototype ---------constructor----------------> State

state1---------------------constructor----------------> State

然而原型对象和实例对象都会有个一个constructor指向其构造函数([Function: State]

state1 -----------------__proto__·-----------------> State.prototype

实例对象会通过原型链属性__proto__指向其构造函数的原型对象


5.2 构造函数

因为State是通过new function来的所有他是一个构造函数,而State.prototype则是该State的一个实例对象,就是一个普通的函数State {},从运行可以看出

> console.log(State)
[Function: State]
> console.log(State.prototype);
State {}
> console.log(state1)
State {}

看看这个定一个匿名函数

> var fun3 = new Function();
undefined
> console.log(fun3);
[Function: anonymous]
undefined
>

再回头去看看上面的 function State(){} 定义函数是否理解了呢

上面说State的实例对象就是一个普通对象,怎么理解,

在工作中是不是常常会var obj = {};这样来定义一个对象呢,应该都有过吧,这就是定于了一个普通对象,然而State.prototype的原型对象也是一个{}

这就和好的论证了他是一个普通对象

> var obj = {};
undefined
> console.log(obj.prototype);
undefined
undefined
> console.log(obj.__proto__);
{}
undefined
>

这里的obj.prototype == undefined也充分证明其只是一个普通对象,只有函数对象才会有prototype

> console.log(State.prototype);
State {}
undefined
> console.log(State.prototype.__proto__);
{}

看来这个是否理解了上面说的【函数对象(State {})通过prototype属性指向它的原型对象(State.prototype)】

5.3 构造函数的由来

那么构造函数最终来之哪里呢

> console.log(State.__proto__);
[Function]
undefined
>

他的原型对象是Function;那么就会问Function的原型对象又是谁呢?(Object)? no 他是他自己。。。

> console.log(Function.prototype);
[Function]
undefined
>

why?

再来回顾下

function State(){}的 State.prototypeState 的一个实例那就等同于

State.prototype = new State();

以此类推

Function.prototype 也是Function 的一个实例;那么

Function.prototype = new Function();

而 上面曾经定义匿名函数时提到使用new Function()创建的函数就是构造函数

那么Function.prototype 也是通过new Function()创建的,那么他是不是也该是个构造函数呢,也就是等于了他自己

那么Function的__proto__就很简单了,

Function.__proto__ == Function.prototype == Function

那最后Function.prototype 的原型对象又指向了谁,不可能还是Function吧?

那倒不是了。那起步成死循环了

所以它依然遵循了万物法则,一切皆从无中来

> console.log(Function.prototype.__proto__);
{}
undefined

也就是说

Function.prototype.__proto__ == {}

var obj = {};

var obj2 = new Object();

使用node运行测试

> var obj2 = new Object();
undefined
> console.log(obj2.prototype);
undefined
undefined
> console.log(obj2.__proto__);
{}
undefined
>

是否圆满了所有对象都继承之Object的论题!

js原型和原型链(用代码理解代码)的更多相关文章

  1. JS基础-该如何理解原型、原型链?

    JS的原型.原型链一直是比较难理解的内容,不少初学者甚至有一定经验的老鸟都不一定能完全说清楚,更多的"很可能"是一知半解,而这部分内容又是JS的核心内容,想要技术进阶的话肯定不能对 ...

  2. 前端【JS】,深入理解原型和原型链

    对于原型和原型链,相信有很多伙伴都说的上来一些,但有具体讲不清楚.但面试的时候又经常会碰到面试官的死亡的追问,我们慢慢来梳理这方面的知识! 要理解原型和原型链的关系,我们首先需要了解几个概念:1.什么 ...

  3. Js中关于构造函数,原型,原型链深入理解

    在 ES6之前,在Javascript不存在类(Class)的概念,javascript中不是基于类的,而是通过构造函数(constructor)和原型链(prototype chains)实现的.但 ...

  4. js原型链的深度理解!

    一. 普通对象与函数对象 JavaScript 中,万物皆对象!但对象也是有区别的.分为普通对象和函数对象,Object .Function 是 JS 自带的函数对象.下面举例说明 var o1 = ...

  5. JS原型与原型链继承的理解

    一.原型 先从构造函数开始吧! 构造函数是什么?构造函数与其他函数唯一的区别在于调用方式不同.任何函数只要通过new来调用就可以作为构造函数,它是用来创建特定类型的对象. 下面定义一个构造函数 Fem ...

  6. 理解js中的原型,原型对象,原型链

    目录 理解原型 理解原型对象 实例属性与原型属性的关系 更简单的原型语法 原型的动态性 原型链 理解原型 我们创建的每一个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象, ...

  7. JS原型、原型链深入理解

    原型是JavaScript中一个比较难理解的概念,原型相关的属性也比较多,对象有”prototype”属性,函数对象有”prototype”属性,原型对象有”constructor”属性. 一.初识原 ...

  8. 理解js中的原型链

    对象有”prototype”属性,函数对象有”prototype”属性,原型对象有”constructor”属性. 关于原型 在JavaScript中,原型也是一个对象,通过原型可以实现对象的属性继承 ...

  9. js原型和原型链理解 constructor 构造函数

    一.对象:普通对象   函数对象 二.构造函数特点:1.需要new实例化,内部使用this对象指向即将要生成的实例对象  2.首字母大写,用于区分普通函数 function Person(name){ ...

  10. JS中关于原型对象与原型链的理解!

    1.首先我们先来看一张图 prototype 每个函数都有一个 prototype 属性每一个JavaScript对象(null除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每 ...

随机推荐

  1. 如何使用Github创建一个仓库

    创建仓库(对我来说,这是新建) 点击这里的Create repository: 进入到这样一个界面: 其中,Repository name,是我们即将创建完成的仓库名称: 而这里: 需要填写的是对仓库 ...

  2. 基于 RocketMQ Connect 构建数据流转处理平台

    本文作者:周波,阿里云智能高级开发工程师, Apache RocketMQ Committer . 01 从问题中来的RocketMQ Connect 在电商系统.金融系统及物流系统,我们经常可以看到 ...

  3. 从零开始,开启属于你的 RTE 漫游之旅!丨漫游指南 x 即将启航

    什么是「开发者漫游指南」? 「开发者漫游指南」邀请热爱前端开发.关心音视频领域发展.希望进入音视频行业.乐于和大家一起交流成长的小伙伴,通过「开发者漫游指南」与社区共同成长,帮助更多的开发者在实时音视 ...

  4. Resistance distance 图上2个节点的等效电阻求解算法

    目录 如何计算正方体网络中(乃至更一般的图)2个节点间的等效电阻? 公式的正确性很容易得到验证 如何计算Weighted matrix的Resistance matrix 我验证了特例,是对的,但是对 ...

  5. HTTP TCP UDP WEBSOCKET

    概念: TCP和UDP:传输层协议:(卡车) HTTP:应用层协议:(货物).HTTP(超文本传输协议)是利用TCP在两台电脑(通常是Web服务器和客户端)之间传输信息的协议.客户端使用Web浏览器发 ...

  6. fastjson很好,但不适合我

    记者:大爷您有什么特长呀? fastjson:我很快. 记者:23423乘以4534等于多少? fastjson:等于2343. 记者:?? fastjson:你就说快不快吧! 这个略显马丽苏的标题, ...

  7. ACM-NEFUOJ-P209湖南修路

    思路 prim的最小生成树,套上肝就完事了 代码 #include<iostream> #include<cstdio> #include<string.h> #d ...

  8. CTF-Web-PHP反序列化

    概念解释 PHP 反序列化漏洞又叫做 PHP 对象注入漏洞,我觉得这个表达很不直白,也不能说明根本的问题,不如我们叫他 PHP 对象的属性篡改漏洞好了(别说这是我说的~~) 反序列化漏洞的成因在于代码 ...

  9. 机器学习(五):混合高斯聚类(求聚类标签)+PCA降维(3维降2维)习题

    使用混合高斯模型 GMM,计算如下数据点的聚类过程: \(Data = np.array([1,2,6,7])\) 均值初值为: \(\mu_1, \mu_2 = 1, 5\) 权重初值为: \(w_ ...

  10. 虚拟内存与malloc/new原理详解

    malloc malloc()函数并不是系统调用,而是 C 库里的函数,用于动态分配内存.malloc() 分配的是虚拟内存,而不是物理内存.如果分配后的虚拟内存没有被访问的话,是不会将虚拟内存映射到 ...