若是说JavaScript是一本武学典籍,那么原型链就是九阳神功。在金庸的武侠小说里面,对九阳神功是这样描述的:
"练成「九阳神功」后,会易筋洗髓;生出氤氲紫气;内力自生速度奇快,无穷无尽,普通拳脚也能使出绝大攻击力;防护力无可匹敌,自动护体,反弹外力攻击,成就金刚不坏之躯;习者速度将受到极大加成;更是疗伤圣典,百病不生,诸毒不侵。至阳热气全力施展可将人焚为焦炭,专门克破全部寒性和阴毒内力。"可见其功法强大。
那么,如何修炼好js中的九阳神功呢?真正的功法大成的技术是从底层上去理解,那种工程师和码农的区别就在于对底层的理解,当你写完一行代码,或者你碰见一个bug,解决的速度取决于你对底层的理解。什么是底层?我目前我的的理解是“在你写每一行代码的时候,它将如何在相应的虚拟机或者V8引擎中是如何运行的,更厉害的程序员甚至知道每条数据的操做是在堆里面仍是在栈里面,都作到了然于胸,这是JavaScript的内功最高境界(反正我如今是作不到,我不知道大家能不能,哈哈哈)”。程序员
**理解原型链以前首先要了解js的基本类型和引用类型:
一、基本类型
基本类型有Undefined、Null、Boolean、Number 和String。这些类型在内存中分别占有固定大小的空间,他们的值保存在栈空间,
咱们经过按值来访问的。
基本类型:简单的数据段,存放在栈内存中,占据固定大小的空间。
二、引用类型
引用类型,值大小不固定,栈内存中存放地址指向堆内存中的对象。是按引用访问的。
存放在堆内存中的对象,变量实际保存的是一个指针,这个指针指向另外一个位置。每一个空间大小不同,要根据状况开进行特定的分配。
当咱们须要访问引用类型(如对象,数组,函数等)的值时,首先从栈中得到该对象的地址指针,而后再从堆内存中取得所需的数据。**web
js的原型链说简单也简单,说难也难。编程
首先说明:函数(Function)才有prototype属性,对象(除了Object)拥有_proto_.
原型链的顶层就是Object.prototype,而这个对象的是没有原型对象的。
能够在Chrome输入:数组
Object.__proto__
输出的是:浏览器
ƒ () { [native code] }
能够看到这个没有.prototype属性。函数
咱们知道原型是一个对象,其余对象能够经过它实现属性继承。spa
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() {}
/*一、字面量方式*/ var a = {}; console.log(a.__proto__); //Object {} console.log(a.__proto__ === a.constructor.prototype); //true /*二、构造器方式*/ var A = function(){}; var a = new A(); console.log(a.__proto__); //A {} console.log(a.__proto__ === a.constructor.prototype); //true /*三、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只是检测一个对象是不是另个对象new出来的实例(例如var a = new Object(),a instanceof Object返回true),但实际instanceof的运算规则上比这个更复杂。prototype
//假设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的运算规则为:code
instanceof检测左侧的__proto__原型链上,是否存在右侧的prototype原型。
咱们再配合代码来看一下就明白了:
//①构造器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
Function.__proto__.__proto__ === Object.prototype;//true Object.__proto__ === Function.prototype;//true
若是看完以上,你还以为上面的关系看晕了的话,只须要记住下面两个最重要的关系,其余关系就能够推导出来了:
一、全部的构造器的constructor都指向Function
二、Function的prototype指向一个特殊匿名函数,而这个特殊匿名函数的__proto__指向Object.prototype
咱们知道,在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和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好文