js原型和原型链(用代码理解代码)
众所周知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.prototype
是State
的一个实例那就等同于
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原型和原型链(用代码理解代码)的更多相关文章
- JS基础-该如何理解原型、原型链?
JS的原型.原型链一直是比较难理解的内容,不少初学者甚至有一定经验的老鸟都不一定能完全说清楚,更多的"很可能"是一知半解,而这部分内容又是JS的核心内容,想要技术进阶的话肯定不能对 ...
- 前端【JS】,深入理解原型和原型链
对于原型和原型链,相信有很多伙伴都说的上来一些,但有具体讲不清楚.但面试的时候又经常会碰到面试官的死亡的追问,我们慢慢来梳理这方面的知识! 要理解原型和原型链的关系,我们首先需要了解几个概念:1.什么 ...
- Js中关于构造函数,原型,原型链深入理解
在 ES6之前,在Javascript不存在类(Class)的概念,javascript中不是基于类的,而是通过构造函数(constructor)和原型链(prototype chains)实现的.但 ...
- js原型链的深度理解!
一. 普通对象与函数对象 JavaScript 中,万物皆对象!但对象也是有区别的.分为普通对象和函数对象,Object .Function 是 JS 自带的函数对象.下面举例说明 var o1 = ...
- JS原型与原型链继承的理解
一.原型 先从构造函数开始吧! 构造函数是什么?构造函数与其他函数唯一的区别在于调用方式不同.任何函数只要通过new来调用就可以作为构造函数,它是用来创建特定类型的对象. 下面定义一个构造函数 Fem ...
- 理解js中的原型,原型对象,原型链
目录 理解原型 理解原型对象 实例属性与原型属性的关系 更简单的原型语法 原型的动态性 原型链 理解原型 我们创建的每一个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象, ...
- JS原型、原型链深入理解
原型是JavaScript中一个比较难理解的概念,原型相关的属性也比较多,对象有”prototype”属性,函数对象有”prototype”属性,原型对象有”constructor”属性. 一.初识原 ...
- 理解js中的原型链
对象有”prototype”属性,函数对象有”prototype”属性,原型对象有”constructor”属性. 关于原型 在JavaScript中,原型也是一个对象,通过原型可以实现对象的属性继承 ...
- js原型和原型链理解 constructor 构造函数
一.对象:普通对象 函数对象 二.构造函数特点:1.需要new实例化,内部使用this对象指向即将要生成的实例对象 2.首字母大写,用于区分普通函数 function Person(name){ ...
- JS中关于原型对象与原型链的理解!
1.首先我们先来看一张图 prototype 每个函数都有一个 prototype 属性每一个JavaScript对象(null除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每 ...
随机推荐
- maui BlazorWebView+本地html (vue、uniapp等都可以) 接入微信sdk 开发 Android app
首先添加微信sdk的绑定库 nuget 包:Chi.MauiBinding.Android.WeChat 项目地址:https://github.com/realZhangChi/MauiBindin ...
- python轮流监听多台服务器资源情况
在主动持续监听某台服务器基础上,优化为同时监听多台服务器资源占用情况: 优点:较初版,设备监听范围有了明显提升: 缺点:主动式,轮询方式,实时性较差. #-*- coding: utf-8 -*- # ...
- c#动态执行字符串脚本(优化版)
像javascript中有eval()来执行动态代码,c#中是没有的,于是自己动手丰衣足食, 先来代码 1 using System; 2 using System.Data; 3 using Sys ...
- 开发轻量级REST API样板 基于Node.js、MongoDB 通过Mongoose驱动
ZY.Node.Mongodb https://gitee.com/Z568_568/node.mongodb.git https://github.com/ZHYI-source/ZY.Node.M ...
- 万字详解 | Java 流式编程
概述 Stream API 是 Java 中引入的一种新的数据处理方法.它提供了一种高效且易于使用的方法来处理数据集合.Stream API 支持函数式编程,可以让我们以简洁.优雅的方式进行数据操作, ...
- [GIT]辨析/区别: git reset HEAD 与 git reset --hard HEAD | 版本回撤
1 场景1: 撤销到远程仓库或本地仓库的最新最近一次的正式版本 1.1 文由 时常有这样一种场景,不小心改动了部分文件,或修改了部分文件却发现无用,此时可能还没有git push,也可能push了:又 ...
- ORA-12154: TNS:could not resolve the connect identifier specified--sys密码包含@符号
问题描述:在操作系统登录数据库时,由于忘记了sys密码,重新修改的sys密码包含@符号,登录时报错, ORA-12154: TNS:could not resolve the connect iden ...
- Vite-Admin后台管理系统|vite4+vue3+pinia前端后台框架实例
基于vite4.x+vue3+pinia前端后台管理系统解决方案ViteAdmin. 前段时间分享了一篇vue3自研pc端UI组件库VEPlus.这次带来最新开发的基于vite4+vue3+pinia ...
- Linux(六)进程管理
Linux系统管理 linux中的进程与服务 进程:Linux中正在执行的程序或者命令 服务:Linux中一直存在.常驻内存的进程 守护进程:进程按照运行方式进行划分,又分为前台显示和后台显示的进程( ...
- Vue2的组件中data为什么不能使用对象
当一个组件被定义,data 必须声明为返回一个初始数据对象的函数,因为组件可能被用来创建多个实例. 如果 data 仍然是一个纯粹的对象,则所有的实例将共享引用同一个数据对象!通过提供 data 函数 ...