聊一聊JS的原型链之高级篇
首先呢JS的继承实现是借助原型链,原型链即__proto__形成的链条。
下面一个例子初步认识下原型链:
function Animal (){ }
var cat = new Animal()
也就是说这个实例化对象cat的__proto__指向的是Animal里面的prototype这个原型对象,因为prototype是一个对象因此里面肯定也会有一个__proto__。而这个__proto__指向的是创建Animal这个构造函数的对象,可以想象一下谁创建了Animal?
肯定是一个对象创建了Animal。而这个对象就是 Function。有对象那么肯定就会有__proto__.那么我们可以想象一下还有什么可以创建出来对象吗?这个时候我们就不得不说一句"万物皆是对象".因此对象的顶端也就是null。
那么__proto__与prototype有什么区别呢?
prototype属性也叫原型对象, prototype只有函数才有的属性, _proto_是所有对象都有的属性(null和undefined除外),而且指向创造该obj
对象的函数对象的prototype属性,但是_proto_不是标准的属性,只有部分浏览器实现了,对应的标准的属性是[[Prototype]],大多数情况下,大多数情况下,__proto__可以理解为'构造器的原型',即Animal.__proto__===Animal.constructor.prototype(通过Object.create创建对象不适用此对象);
下面看一下各种情况下_proto_的指向;
一、Object.create()
var p={};
var q=Object.create(p)
q.__proto__===q.constructor.prototype//false
q.__proto__===p;//true
二、字面量
var a={}
a.__proto__===Object.prototype//true
三、构造器(proto指向构造器的prototype)
function Animal(){
}
var cat = new Animal()
cat.__proto__===Animal.prototype//true
var obj = {name: 'jack'}
var arr = [1,2,3]
var reg = /hello/g
var date = new Date
var err = new Error('exception') console.log(obj.__proto__ === Object.prototype) // true
console.log(arr.__proto__ === Array.prototype) // true
console.log(reg.__proto__ === RegExp.prototype) // true
console.log(date.__proto__ === Date.prototype) // true
console.log(err.__proto__ === Error.prototype) // true
cat.__proto__
是什么?Animal.__proto__
是什么?Animal.prototype.__proto__
是什么?Object.__proto__
是什么?Object.prototype__proto__
是什么?
1、cat.__proto__===Animal.prototype;
2、Animal.__proto__===Function.prototype//Animal是函数对象
3、Animal.prototype.__proto__===Object.prototype//Animal.prototype是普通对象
4、Object.__proto__===Function.prototype//Object是函数对象
5、Object.prototype.__proto__===null
四、函数对象
所有的函数对象的__proto__都指向Function.prototype,它是一个空函数(empty function)
// 所有的构造器都来自于Function.prototype,甚至包括根构造器Object及Function自身 Number.__proto__ === Function.prototype // true
Number.constructor == Function //true Boolean.__proto__ === Function.prototype // true
Boolean.constructor == Function //true String.__proto__ === Function.prototype // true
String.constructor == Function //true Object.__proto__ === Function.prototype // true
Object.constructor == Function // true // 所有的构造器都来自于Function.prototype,甚至包括根构造器Object及Function自身
Function.__proto__ === Function.prototype // true
Function.constructor == Function //true Array.__proto__ === Function.prototype // true
Array.constructor == Function //true RegExp.__proto__ === Function.prototype // true
RegExp.constructor == Function //true Error.__proto__ === Function.prototype // true
Error.constructor == Function //true Date.__proto__ === Function.prototype // true
Date.constructor == Function //true
Math.__proto__ === Object.prototype // true
Math.construrctor == Object // true JSON.__proto__ === Object.prototype // true
JSON.construrctor == Object //true
再看看自定义的构造器,这里定义了一个 Person
:
function Person(name) {
this.name = name;
}
var p = new Person('jack')
console.log(p.__proto__ === Person.prototype) // true
p
是 Person
的实例对象,p
的内部原型总是指向其构造器 Person
的原型对象 prototype
。
Function.prototype
,甚至包括根构造器Object
及Function
自身。所有构造器都继承了·Function.prototype·的属性及方法。如length、call、apply、bindFunction.prototype
也是唯一一个typeof Funtion.prototype
为 function
的prototype
。其它的构造器的prototype
都是一个对象
知道了所有构造器(含内置及自定义)的__proto__
都是Function.prototype
,那Function.prototype
的__proto__
是谁呢?
Function.prototype.__proto__ === Object.prototype // true
这说明所有的构造器也都是一个普通 JS 对象,可以给构造器添加/删除属性等。同时它也继承了Object.prototype上的所有方法:toString、valueOf、hasOwnProperty等。
最后Object.prototype的proto指向null,到顶了。
什么是Prototype
我们看一下熟知的函数的原型对象
Function.prototype;//function() {}
Object.prototype;//Object {}
Number.prototype;//Number {[[PrimitiveValue]]: 0}
Boolean.prototype;//Boolean {[[PrimitiveValue]]: false}
Array.prototype;//[]
String.prototype;//String {length: 0, [[PrimitiveValue]]: ""}
说道这里,必须提的是所有函数对象的原型对象都继承制原始对象,即fn.prototype.__proto__为原始对象(原始对象在继承属性__proto__中有定义)。这其中比较特别的是Object函数,他的原型对象就是原始对象,即Object.prototype。
var f1 = new Function();
var f2 = Function();
var fn3 = function(){} console.log(f1.prototype.__proto__ === Object.prototype);//true
console.log(f2.prototype.__proto__ === Object.prototype);//true
console.log(fn3.prototype.__proto__ === Object.prototype);//true console.log(Number.prototype.__proto__ === Object.prototype);//true
console.log(Boolean.prototype.__proto__ === Object.prototype);//true
实际上js没有继承这个东东,但是__proto__却起到了类似继承的作用。我们所知的所有的对象起源都是一个空对象,我们把这个空对象叫做原始对象。所有的对象通过__proto__回溯最终都会指向(所谓的指向类似C中的指针,这个原始对象是唯一的,整个内存中只会存在一个原始对象)这个原始对象。用下面的例子佐证
var o = new Object();
o.__proto__;//Object {}
o.prototype;//undefined
Object.prototype;//Object {}
Object.__proto__;//function(){}
Object.__proto__.__proto__;//Object {}
Object.__proto.prototype;//undefined
var f = new Function();
f.__proto__;//function(){}
f.prototype;//Object {} 新的实例对象非原始对象
Function.prototype;//function(){}
Function.__proto__;//function(){}
Function.__proto__.__proto__;//Object {}
Function.prototype.__proto__;//Object {}
Function.prototype.__proto__.__proto__//null
原始对象的__proto__属性为null,并且没有原型对象。
所有的对象都继承自原始对象;Object比较特殊,他的原型对象也就是原始对象;所以我们往往用Object.prototype表示原始对象
//所有的对象都继承自原始对象
//Object比较特殊,他的原型对象也就是原始对象
//所以我们往往用Object.prototype表示原始对象
Object.prototype === o.__proto__;//true
Object.prototype === Object.__proto__.__proto__;//true
Object.prototype === Function.__proto__.__proto__;//true
所有的函数对象都继承制原始函数对象;Function比较特殊,他的原型对象也就是原始函数对象;所以我们往往用Function.prototype表示原始函数对象;
而原始函数对象又继承自原始对象。
//所有的函数对象都继承制原始函数对象,
//Function比较特殊,他的原型对象也就是原始函数对象
Function.prototype === f.__proto__
Function.prototype === Object.__proto__ ;//true
Function.prototype === Function.__proto__;//true
//所以我们往往用Function.prototype表示原始函数对象 //而原始函数对象又继承自原始对象
Function.prototype.__proto__ === Object.prototype;
prototype
属性了。对于 ECMAScript 中的引用类型而言,prototype
是保存着它们所有实例方法的真正所在。换句话所说,诸如 toString()
和 valuseOf()
等方法实际上都保存在 prototype
名下,只不过是通过各自对象的实例访问罢了。 ——《JavaScript 高级程序设计》第三版 P116当我们创建一个数组时:
var num = new Array()
num
是 Array
的实例,所以 num
继承了Array
的原型对象Array.prototype
上所有的方法:
我们可以用一个 ES5 提供的新方法:Object.getOwnPropertyNames
获取所有(包括不可枚举的属性)的属性名不包括 prototy
中的属性,返回一个数组:
Object.getOwnPropertyNames(Array.prototype)
["length", "constructor", "toString", "toLocaleString", "join", "pop", "push", "reverse", "shift", "unshift", "slice", "splice", "sort", "filter", "forEach", "some", "every", "map", "indexOf", "lastIndexOf", "reduce", "reduceRight", "copyWithin", "find", "findIndex", "fill", "includes", "keys", "entries", "concat"]
细心的你肯定发现了Object.getOwnPropertyNames(Array.property)
输出的数组里并没有 constructor/hasOwnPrototype
等对象的方法。但是随便定义的数组也能用这些方法
因为Array.prototype
虽然没这些方法,但是它有原型对象(__proto__
):
Array.prototype.__proto__ == Object.prototype//Array.prototype是普通对象
Array.prototype
继承了对象的所有方法,当你用num.hasOwnPrototype()
时,JS 会先查一下它的构造函数 (Array
) 的原型对象 Array.prototype
有没有有hasOwnPrototype()
方法,没查到的话继续查一下 Array.prototype
的原型对象 Array.prototype.__proto__
有没有这个方法。Object.getOwnPropertyNames(Function.prototype)
["length", "name", "arguments", "caller", "apply", "bind", "call", "toString", "constructor"]
这些属性和方法所有的函数对象都可以用。
function Animal(){ }
var cat = new Animal() cat.__proto__===Animal.prototype;//true cat.constructor.prototype===Animal.prototype//true
cat.__proto__===cat.constructor.prototype;//true
如果改写
function Animal(){ } Animal.prototype={
getName:function(){}
}
var cat = new Animal();
cat.__proto__===Animal.prototype;//true
cat.constructor.prototype===Animal.prototype//false cat.__proto__===cat.constructor.prototype;//false
这样重写了Animal的prototype属性,cat.constructor.prototype不在等于Animal.prototype,
这也很好理解,给Animal.prototype
赋值的是一个对象直接量{getName: function(){}}
,使用对象直接量方式定义的对象其构造器(constructor
)指向的是根构造器Object
,Object.prototype
是一个空对象{}
,{}
自然与{getName: function(){}}
不等。如下:
cat.constructor===Object; cat.constructor.prototype===Object.prototype;
疑惑点
Function.prototype.__proto__ === Object.prototype //true
再来看下面的:
//原型和原型链是JS实现继承的一种模型。
//原型链的形成是真正是靠__proto__ 而非prototype var animal = function(){};
var dog = function(){}; animal.price = 2000;
dog.prototype = animal;
var tidy = new dog();
console.log(dog.price) //undefined
console.log(tidy.price) // 2000 var dog = function(){};
dog.prototype.price = 2000;
var tidy = new dog();
console.log(tidy.price); // 2000
console.log(dog.price); //undefin var dog = function(){};
var tidy = new dog();
tidy.price = 2000;
console.log(dog.price); //undefined
实例tidy和 原型对象dog.prototype存在一个连接。不过,要明确的真正重要的一点就是,这个连接存在于实例tidy与构造函数的原型对象dog.prototype之间,而不是存在于实例tidy与构造函数dog之间。
constructor 属性返回对创建此对象的函数对象的引用。
function a(){};
console.log(a.constructor===Function); //true
console.log(a.prototype.constructor===a); //true
函数a
是由Function创造出来,那么它的constructor指向的Function,a.prototype
是由new a()
方式创造出来,那么a.prototype.constructor
理应指向a
1、组合继承
// 组合继承
function Animal(){
this.name=name||'Animal';
this.sleep=function(){
console.log(this.name+'sleep');
}
}
Animal.prototype.eat=function(food){
console.log(this.name+'eat'+food);
} function Cat(name){
Animal.call(this);//继承实例属性/方法,也可以继承原型属性/方法
this.name=name||'tom';//调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)
}
Cat.prototype=new Animal();
Cat.prototype.constructor=Cat;//组合继承也是需要修复构造函数指向的。
var cat = new Cat();//既是子类的实例,也是父类的实例
console.log(Cat.prototype.constructor);
console.log(cat.name)
console.log(cat.eat('haha'))//可传参
特点:
可以继承实例属性/方法,也可以继承原型属性/方法
- 既是子类的实例,也是父类的实例
- 不存在引用属性共享问题
- 可传参
- 函数可复用
缺点:
- 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)
推荐指数:★★★★(仅仅多消耗了一点内存)
2、寄生组合继承
寄生组合继承
function Animal(){
this.name=name||'Animal';
this.sleep=function(){
console.log(this.name+'sleep');
}
}
Animal.prototype.eat=function(food){
console.log(this.name+'eat'+food);
} function Cat(name){
Animal.call(this);
this.name=name||'tom';
} (function(){
var Super=function(){};// 创建一个没有实例方法的类
Super.prototype=Animal.prototype;
Cat.prototype=new Super(); //将实例作为子类的原型
})()
Cat.prototype.constructor = Cat;
var cat=new Cat();
console.log(cat.eat('haha'))
特点:
- 堪称完美
缺点:
- 实现较为复杂
推荐指数:★★★★(实现复杂,扣掉一颗星)
聊一聊JS的原型链之高级篇的更多相关文章
- Javascripte的原型链之基础讲解
一.函数对象与普通对象 var o1 = {}; var o2 =new Object(); var o3 = new f1(); function f1(){}; var f2 = function ...
- 从零开始学Axure原型设计(高级篇)
如果你熟悉了Axure的部件库,那么你可以得心应手地画出心目中产品的线框图:如果你会用Axure的母版.动态面板功能,那么你应该能够画出一些简单网站的原型图:但只有你精通了Axure的条件逻辑.变量. ...
- JavaScript--我发现,原来你是这样的JS(基础概念--灵魂篇,一起来学js吧)
介绍 这是红宝书(JavaScript高级程序设计 3版)的读书笔记第三篇(灵魂篇介绍),有着剩下的第三章的知识内容,当然其中还有我个人的理解.红宝书这本书可以说是难啃的,要看完不容易,挺厚的,要看懂 ...
- js javascript 原型链详解
看了许多大神的博文,才少许明白了js 中原型链的概念,下面给大家浅谈一下,顺便也是为了巩固自己 首先看原型链之前先来了解一下new关键字的作用,在许多高级语言中,new是必不可少的关键字,其作用是为了 ...
- 使用Vue.js制作仿Metronic高级表格(一)静态设计
Metronic高级表格是Metonic框架中自行实现的表格,其底层是Datatables.本教程将主要使用Vue实现交互部分,使用Bootstrap做样式库.jQuery做部分用户交互(弹窗). 使 ...
- JS:面向对象(基础篇)
面向对象(Object-Oriented,OO)的语言有一个标志,那就是它们都有类的概念.long long ago,js是没有类的概念(ES6推出了class,但其原理还是基于原型),但是它是基于原 ...
- Vue2和Vue3技术整理3 - 高级篇
3.高级篇 前言 基础篇链接:https://www.cnblogs.com/xiegongzi/p/15782921.html 组件化开发篇链接:https://www.cnblogs.com/xi ...
- Vue2技术整理3 - 高级篇 - 更新完毕
3.高级篇 前言 基础篇链接:https://www.cnblogs.com/xiegongzi/p/15782921.html 组件化开发篇链接:https://www.cnblogs.com/xi ...
- 《JavaScript网页特效经典300例-高级篇》
<Javascript网页经典特性300例> 高级篇 第18章:ajax应用 Ajax传输JSON数据实例定义一套自己的Ajax框架 第19章:面向对象的特性 定义一个类利用prototy ...
随机推荐
- Hadoop1.x原理
将这种单机的工作进行分拆,变成协同工作的集群,这就是分布式计算框架设计.使得计算机硬件类似于应用程序中资源池的资源,使用者无需关心资源的分配情况,从而最大化了硬件资源的使用价值.分布式计算也是如此,具 ...
- java 集合框架(三)Collection
一.概述 Collection是集合框架的根接口.不同的集合具有不同的特性,比如有的集合可以有重复元素,有的不可以,有的可以排序,有的不可排序,如此等等,而Collection作为集合的根接口,它规范 ...
- 工作中常用的linux命令(2)
1.find :查找指定文件名的路径: 列出当前目录以及子目录中的所有文件: 在当前目录下寻找特定文件名的文件: 列出长度为零的文件: 2.ps :查看某个程序的进程,例如查询mongodb和mysq ...
- VxWorks:添加自己组件到Tornado
项目要求将cpci的驱动做成Tornado组件,尝试了一下! Folder FOLDER_CPCI { //上层组件设置 NAME cpci componen ...
- linux命令之 ifconfig
许多windows非常熟悉ipconfig命令行工具,它被用来获取网络接口配置信息并对此进行修改.Linux系统拥有一个类似的工具,也就是ifconfig(interfaces config).通常需 ...
- VxWorks各部分初始化流程
一)configAll.h中定义所有定置系统配置的宏 INCLUDED SOFTWARE FACILITIES:定义了基本组件: EXCLUDED FACILITIES:定义了扩充组件,缺省不包括: ...
- 将搜狗词库(.scel格式)转化为txt格式
参考:http://blog.csdn.net/zhangzhenhu/article/details/7014271 #!/usr/bin/python # -*- coding: utf-8 -* ...
- 在CYGWIN下编译和运行软件Bundler ,以及PMVS,CMVS的编译与使用
本人按照 http://blog.csdn.net/zzzblog/article/details/17166869 http://oliver.zheng.blog.163.com/blog/sta ...
- Deadlock found when trying to get lock; try restarting transaction
1.错误描述 [ERROR:]2015-06-09 16:56:19,481 [抄送失败] org.hibernate.exception.LockAcquisitionException: erro ...
- 挖一挖不常用到而又很实用的重载-Split
Split这个基本上所有的程序开发人员都用到,一般使用单字符和长字符串拆分字符串的较多,其实还有一个重载非常好用,那就是多种组合字符来进行拆分. 例如: "aaaaaaaaaa{@}bbbb ...