资讯专栏INFORMATION COLUMN

js内功修炼之九阳神功--原型链

morgan / 2537人阅读

摘要:写在前面如果说是一本武学典籍,那么原型链就是九阳神功。那么,如何修炼好中的九阳神功呢真正的功法大成的技术是从底层上去理解,那种工程师和码农的区别就在于对底层的理解,当你写完一行代码,或者你遇见一个解决的速度取决于你对底层的理解。

写在前面

如果说JavaScript是一本武学典籍,那么原型链就是九阳神功。在金庸的武侠小说里面,对九阳神功是这样描述的:
"练成「九阳神功」后,会易筋洗髓;生出氤氲紫气;内力自生速度奇快,无穷无尽,普通拳脚也能使出绝大攻击力;防御力无可匹敌,自动护体,反弹外力攻击,成就金刚不坏之躯;习者速度将受到极大加成;更是疗伤圣典,百病不生,诸毒不侵。至阳热气全力施展可将人焚为焦炭,专门克破所有寒性和阴毒内力。"可见其功法强大。
那么,如何修炼好js中的九阳神功呢?真正的功法大成的技术是从底层上去理解,那种工程师和码农的区别就在于对底层的理解,当你写完一行代码,或者你遇见一个bug,解决的速度取决于你对底层的理解。什么是底层?我目前个人的理解是“在你写每一行代码的时候,它将如何在相应的虚拟机或者V8引擎中是如何运行的,更厉害的程序员甚至知道每条数据的操作是在堆里面还是在栈里面,都做到了然于胸,这是JavaScript的内功最高境界(反正我现在是做不到,我不知道你们能不能,哈哈哈)”。

一、Js原型链的简单理解

**理解原型链之前首先要了解js的基本类型和引用类型:
1、基本类型
基本类型有Undefined、Null、Boolean、Number 和String。这些类型在内存中分别占有固定大小的空间,他们的值保存在栈空间,
我们通过按值来访问的。
基本类型:简单的数据段,存放在栈内存中,占据固定大小的空间。
2、引用类型
引用类型,值大小不固定,栈内存中存放地址指向堆内存中的对象。是按引用访问的
存放在堆内存中的对象,变量实际保存的是一个指针,这个指针指向另一个位置。每个空间大小不一样,要根据情况开进行特定的分配。
当我们需要访问引用类型(如对象,数组,函数等)的值时,首先从栈中获得该对象的地址指针,然后再从堆内存中取得所需的数据。**

js的原型链说简单也简单,说难也难。

首先说明:函数(Function)才有prototype属性,对象(除了Object)拥有_proto_.
原型链的顶层就是Object.prototype,而这个对象的是没有原型对象的。
可以在Chrome输入:

Object.__proto__

输出的是:

ƒ () { [native code] }

可以看到这个没有.prototype属性。

二、prototype和_proto_的区别

我们知道原型是一个对象,其他对象可以通过它实现属性继承。

var a = {};
console.log(a.prototype);  //undefined
console.log(a.__proto__);  //Object {}

var b = function(){}
console.log(b.prototype);  //b {}
console.log(b.__proto__);  //function() {}

/*1、字面量方式*/
var a = {};
console.log(a.__proto__);  //Object {}

console.log(a.__proto__ === a.constructor.prototype); //true

/*2、构造器方式*/
var A = function(){};
var a = new A();
console.log(a.__proto__); //A {}

console.log(a.__proto__ === a.constructor.prototype); //true

/*3、Object.create()方式*/
var a1 = {a:1}
var a2 = Object.create(a1);
console.log(a2.__proto__); //Object {a: 1}

console.log(a.__proto__ === a.constructor.prototype); //false(此处即为图1中的例外情况)

var A = function(){};
var a = new A();
console.log(a.__proto__); //A {}(即构造器function A 的原型对象)
console.log(a.__proto__.__proto__); //Object {}(即构造器function Object 的原型对象)
console.log(a.__proto__.__proto__.__proto__); //null
instanceof究竟是运算什么的?

我曾经简单理解instanceof只是检测一个对象是否是另个对象new出来的实例(例如var a = new Object(),a instanceof Object返回true),但实际instanceof的运算规则上比这个更复杂。

//假设instanceof运算符左边是L,右边是R
L instanceof R 
//instanceof运算时,通过判断L的原型链上是否存在R.prototype
L.__proto__.__proto__ ..... === R.prototype ?

//如果存在返回true 否则返回false
注意:instanceof运算时会递归查找L的原型链,即L.__proto__.__proto__.__proto__.__proto__...直到找到了或者找到顶层为止。

所以一句话理解instanceof的运算规则为:

instanceof检测左侧的__proto__原型链上,是否存在右侧的prototype原型。

图解构造器Function和Object的关系

我们再配合代码来看一下就明白了:

//①构造器Function的构造器是它自身
Function.constructor=== Function;//true

//②构造器Object的构造器是Function(由此可知所有构造器的constructor都指向Function)
Object.constructor === Function;//true



//③构造器Function的__proto__是一个特殊的匿名函数function() {}
console.log(Function.__proto__);//function() {}

//④这个特殊的匿名函数的__proto__指向Object的prototype原型。
Function.__proto__.__proto__ === Object.prototype//true

//⑤Object的__proto__指向Function的prototype,也就是上面③中所述的特殊匿名函数
Object.__proto__ === Function.prototype;//true
Function.prototype === Function.__proto__;//true
当构造器Object和Function遇到instanceof
Function.__proto__.__proto__ === Object.prototype;//true
Object.__proto__ === Function.prototype;//true

如果看完以上,你还觉得上面的关系看晕了的话,只需要记住下面两个最重要的关系,其他关系就可以推导出来了:

1、所有的构造器的constructor都指向Function

2、Function的prototype指向一个特殊匿名函数,而这个特殊匿名函数的__proto__指向Object.prototype

function、Function、Object和{}

我们知道,在Js中一切皆为对象(Object),但是Js中并没有类(class);Js是基于原型(prototype-based)来实现的面向对象(OOP)的编程范式的,但并不是所有的对象都拥有prototype这一属性:

var a = {}; 
console.log(a.prototype);  //=> undefined
 
var b = function(){}; 
console.log(b.prototype);  //=> {}
 
var c = "Hello"; 
console.log(c.prototype);  //=> undefined

prototype是每个function定义时自带的属性,但是Js中function本身也是对象,我们先来看一下下面几个概念的差别:
function是Js的一个关键词,用于定义函数类型的变量,有两种语法形式:

function f1(){ 
  console.log("This is function f1!");
}
typeof(f1);  //=> "function"
 
var f2 = function(){ 
  console.log("This is function f2!");
}
typeof(f2);  //=> "function"

如果用更加面向对象的方法来定义函数,可以用Function:

var f3 = new Function("console.log("This is function f3!");"); 
f3();        //=> "This is function f3!" 
typeof(f3);  //=> "function"
 
typeof(Function); //=> "function"

实际上Function就是一个用于构造函数类型变量的类,或者说是函数类型实例的构造函数(constructor);与之相似有的Object或String、Number等,都是Js内置类型实例的构造函数。比较特殊的是Object,它用于生成对象类型,其简写形式为{}:

var o1 = new Object(); 
typeof(o1);      //=> "object"
 
var o2 = {}; 
typeof(o2);     //=> "object"
 
typeof(Object); //=> "function"
prototype VS_proto_

prototype和length是每一个函数类型自带的两个属性,而其它非函数类型并没有(开头的例子已经说明),这一点之所以比较容易被忽略或误解,是因为所有类型的构造函数本身也是函数,所以它们自带了prototype属性:

除了prototype之外,Js中的所有对象(undefined、null等特殊情况除外)都有一个内置的[[Prototype]]属性,指向它“父类”的prototype,这个内置属性在ECMA标准中并没有给出明确的获取方式,但是许多Js的实现(如Node、大部分浏览器等)都提供了一个__proto__属性来指代这一[[Prototype]],我们通过下面的例子来说明实例中的__proto__是如何指向构造函数的prototype的:

var Person = function(){}; 
Person.prototype.type = "Person"; 
Person.prototype.maxAge = 100;
 
var p = new Person(); 
console.log(p.maxAge); 
p.name = "rainy";
 
Person.prototype.constructor === Person;  //=> true 
p.__proto__ === Person.prototype;         //=> true 
console.log(p.prototype);                 //=> undefined

图示解释上面的代码:

Person是一个函数类型的变量,因此自带了prototype属性,prototype属性中的constructor又指向Person本身;通过new关键字生成的Person类的实例p1,通过__proto__属性指向了Person的原型。这里的__proto__只是为了说明实例p1在内部实现的时候与父类之间存在的关联(指向父类的原型),在实际操作过程中实例可以直接通过.获取父类原型中的属性,从而实现了继承的功能。

核心图解
var Obj = function(){}; 
var o = new Obj(); 
o.__proto__ === Obj.prototype;  //=> true 
o.__proto__.constructor === Obj; //=> true
 
Obj.__proto__ === Function.prototype; //=> true 
Obj.__proto__.constructor === Function; //=> true
 
Function.__proto__ === Function.prototype; //=> true 
Object.__proto__ === Object.prototype;     //=> false 
Object.__proto__ === Function.prototype;   //=> true
 
Function.__proto__.constructor === Function;//=> true 
Function.__proto__.__proto__;               //=> {} 
Function.__proto__.__proto__ === o.__proto__.__proto__; //=> true 
o.__proto__.__proto__.__proto__ === null;   //=> true


从上面的例子和图解可以看出,prototype对象也有__proto__属性,向上追溯一直到null

new关键词的作用就是完成上图所示实例与父类原型之间关系的串接,并创建一个新的对象;instanceof关键词的作用也可以从上图中看出,实际上就是判断__proto__(以及__proto__.__proto__...)所指向是否父类的原型:

var Obj = function(){}; 
var o = new Obj();
 
o instanceof Obj; //=> true 
o instanceof Object; //=> true 
o instanceof Function; //=> false
 
o.__proto__ === Obj.prototype; //=> true 
o.__proto__.__proto__ === Object.prototype; //=> true 
o.__proto__.__proto__ === Function;  //=> false

原型链的结构
1.原型链继承就是利用就是修改原型链结构( 增加、删除、修改节点中的成员 ), 从而让实例对象可以使用整个原型链中的所有成员( 属性和方法 )
2.使用原型链继承必须满足属性搜索原则

属性搜索原则
1.构造函数 对象原型链结构图

function Person (){}; var p = new Person();

2.{} 对象原型链结构图

3.数组的原型链结构图

4.Object.prototype对应的构造函数

总结:
从本质上理解:对象和函数都是保存在堆当中的引用类型,后面一系列的操作都是为了使用或者访问其属性,那么无论是prototype还是_proto_都是函数或者Object自带的指针,允许外界的其他一些函数或者Object去使用自己的一些属性。

更多的文章请关注公众号:码客小栈,每天不定时的更新web好文

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/104693.html

相关文章

  • js内功修炼九阳神功--原型

    摘要:写在前面如果说是一本武学典籍,那么原型链就是九阳神功。那么,如何修炼好中的九阳神功呢真正的功法大成的技术是从底层上去理解,那种工程师和码农的区别就在于对底层的理解,当你写完一行代码,或者你遇见一个解决的速度取决于你对底层的理解。 写在前面 如果说JavaScript是一本武学典籍,那么原型链就是九阳神功。在金庸的武侠小说里面,对九阳神功是这样描述的:练成「九阳神功」后,会易筋洗髓;生出...

    苏丹 评论0 收藏0
  • js内功修炼九阳神功--原型

    摘要:写在前面如果说是一本武学典籍,那么原型链就是九阳神功。那么,如何修炼好中的九阳神功呢真正的功法大成的技术是从底层上去理解,那种工程师和码农的区别就在于对底层的理解,当你写完一行代码,或者你遇见一个解决的速度取决于你对底层的理解。 写在前面 如果说JavaScript是一本武学典籍,那么原型链就是九阳神功。在金庸的武侠小说里面,对九阳神功是这样描述的:练成「九阳神功」后,会易筋洗髓;生出...

    Profeel 评论0 收藏0
  • 耗时一周整理的Python资料,包含各阶段所需网站、项目,收藏了?慢慢来

    摘要:希望能够帮助到大家,减少在起步阶段的油耗,集中精神突破技术。在平时写代码的时候你不一定会用到,但是他却是你解决问题的思想源泉如果说算法是一个程序员的九阳神功,那么设计模式就是你的乾坤大挪移。 showImg(https://segmentfault.com/img/remote/1460000019249986); 不知怎么的,最近不少关注我的读者都开始私信我怎么学好python?零基...

    wean 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<