浅谈javascript的原型及原型链
浅谈javascript的原型及原型链
这里,我们列出原型的几个概念,如下:
- prototype属性
- [[prototype]]
__proto__
prototype属性
只要创建了一个函数,就会为该函数创建一个prototype
属性,指向该函数的原型对象。实例对象是不会拥有该属性的。
默认情况下,该原型对象
也会获得一个constructor
属性,该属性包含一个指针,指向prototype
属性所在的函数。
Person.prototype.constructor===Person
[[prototype]]和__proto__
javascript中,不可以访问的内部属性都是用[[propertyName]]
这种形式来表示的,比如还有枚举属性[[Enumberable]]。
[[prototype]]
属性只能是对象可以拥有的属性。比如实例化的对象以及原型对象
,而不是构造函数。这个属性指向拥有其属性的对象的构造函数的原型对象。注意,此处的构造函数
指的是使用new
方式的构造函数。并不因为更改了原型对象上的constructor
属性而改变。
__proto__
是个啥呢,就是对[[propertyName]]
的实现。也就是说,你可以在支持该实现的浏览器下(FF,chrome,safari),去访问对象的构造函数的原型对象。比如:
var Person=function(name){
this.name=name;
};
var p1=new Person();
p1.__proto__===Person.prototype;//true
Person.prototype={};
var p2=new Person();
p2.__proto__===Object.prototype;//false
当然,__proto__
只是浏览器的私有实现,目前ECMAScript标准实现方法是Object.getPrototypeOf(object)
。
var Person=function(name){
this.name=name;
};
var p1=new Person();
Object.getPrototypeOf(p1)===Person.prototype;//true
Person.prototype={};
var p2=new Person();
Object.getPrototypeOf(p2)===Object.prototype;//false
另外一种判断实例对象和其原型对象存在指向关系(由实例的[[prototype]]指向其构造函数的原型对象)的方法是:isPrototypeOf
。比如:
Person.prototype.isPrototypeOf(p1);//true
由于原型对象
也是一个对象,所以,它自然而然也拥有[[prototype]]
属性。
Javascript
并没有类继承模型,而是使用原型对象 prototype
进行原型式继承。
尽管人们经常将此看做是 Javascript
的一个缺点,然而事实上,原型式继承比传统的类继承模型要更加强大。举个例子,在原型式继承顶端构建一个类模型很简单,然而反过来则是个困难得多的任务。Javascript
是唯一一个被广泛运用的原型式继承的语言,所以理解两种继承方式的差异是需要时间的。
第一个主要差异就是 Javascript
使用原型链来继承:
function Foo() {
this.value = 42;
}
Foo.prototype = {
method: function() {}
};
function Bar() {}
设置 Bar
的 prototype
为 Foo
的对象实例:
Bar.prototype = new Foo();
Bar.prototype.foo = 'Hello World';
确保 Bar
的构造函数为本身,并新建一个 Bar
对象实例:
Bar.prototype.constructor = Bar;
var test = new Bar();
下面我们来看下整个原型链的组成:
test [instance of Bar]
Bar.prototype [instance of Foo]
{ foo: 'Hello World' }
Foo.prototype
{ method: ... }
Object.prototype
{ toString: ... /* etc. */ }
在上面的例子中,对象 test
将会同时继承 Bar.prototype
和 Foo.prototype
。因此它可以访问定义在 Foo
中的函数 method
。当然,它也可以访问属性 value
。需要提到的是,当 new Bar()
时并不会创建一个新的 Foo
实例,而是重用它原型对象自带的 Foo
实例。同样,所有的 Bar
实例都共享同一个 value
属性。我们举例说明:
test1 = new Bar();
test2 = new Bar();
Bar.prototype.value = 41;
test1.value //41
test2.value//41
原型链查找机制
当访问一个对象的属性时,Javascript
会从对象本身开始往上遍历整个原型链,直到找到对应属性为止。如果此时到达了原型链的顶部,也就是上例中的 Object.prototype
,仍然未发现需要查找的属性,那么 Javascript
就会返回 undefined
值。
原型对象的属性
尽管原型对象的属性被 Javascript
用来构建原型链,我们仍然可以值赋给它。但是原始值复制给 prototype
是无效的,如:
function Foo() {}
Foo.prototype = 1; // no effect
这里讲个本篇的题外话,介绍下什么是原始值:
在 Javascript
中,变量可以存放两种类型的值,分别是原始值和引用值。
1.原始值
(primitive value)
:
原始值是固定而简单的值,是存放在栈stack
中的简单数据段,也就是说,它们的值直接存储在变量访问的位置。
原始类型有以下五种型:Undefined, Null, Boolean, Number, String
。
2.引用值
(reference value)
:
引用值则是比较大的对象,存放在堆heap
中的对象,也就是说,存储在变量处的值是一个指针pointer
,指向存储对象的内存处。所有引用类型都集成自Object
。
原型链性能问题
如果需要查找的属性位于原型链的上端,那么查找过程对于性能而言无疑会带来负面影响。当在性能要求必要严格的场景中这将是需要重点考虑得因素。此外,试图查找一个不存在属性时将会遍历整个原型链。
同样,当遍历一个对象的属性时,所有在原型链上的属性都将被访问。
总结
理解原型式继承是写较为复杂的 Javascript
代码的前提,同时要注意代码中原型链的高度。当面临性能瓶颈时要学会将原型链拆分开来。此外,要将原型对象 prototype
和原型 __proto__
区分开来,这里主要讨论原型对象 prototype
就不阐述关于原型 __proto__
的问题了。
图片来自基友 kzloser
图片说明
1.总共三类对象(蓝色大框)
2.实例对象(通过new XX() 所得到的实例),跟原型链相关的只有 __proto__
属性,指向其对应的原型对象 *.prototype
。
3.构造函数对象分原生和自定义两类。跟原型链相关的有 __proto__
属性,除此之外还有 prototype
属性。它们的 __proto__
属性都是指向 Function.prototype
这个原型对象的。prototype
也是指向对应的原型对象。
4.原型对象除了一样拥有 __proto__
外,也拥有独有的属性 constructor
。它的__proto__
指向的都是 Object.prototype
,除了 Object.prototype
本身,它自己是指向 null
。而 constructor
属性指向它们对应的构造函数对象。
5.原型链是基于 __proto__
的。实例只能通过其对应原型对象的 constructor
才能访问到对应的构造函数对象。构造函数只能通过其对应的 prototype
来访问相应的原型对象。
原型与原型链是javascript里面最最核心的内容,如果不能理解它们之间的存在关系的话,那么我们是不能理解这门语言的。
在JS中,主要有两种创建对象的方法, 分别是对象字面量以及调用构造函数
//对象字面量
var obj1 = {}
//调用构造函数
var obj2 = new Object()
其实上述两种创建对象的方法,本质上是一样的,都是JS引擎调用对象的构造函数来新建出一个对象。构造函数本身也是一个普通的JS函数
下面我们来看一个例子
//创建构造函数
function Person(name){
this.name = name
}
//每个构造函数JS引擎都会自动添加一个prototype属性,我们称之为原型,这是一个对象
//每个由构造函数创建的对象都会共享prototype上面的属性与方法
console.log(typeof Person.prototype) // 'object'
//我们为Person.prototype添加sayName方法
Person.prototype.sayName = function(){
console.log(this.name)
}
//创建实例
var person1 = new Person('Messi')
var person2 = new Person('Suarez')
person1.sayName() // 'Messi'
person2.sayName() // 'Suarez'
person1.sayName === person2.sayName //true
我们借助上面的例子来理解构造函数-原型-实例,三者之间的关系,主要有几个基本概念
构造函数默认会有一个
protoype
属性指向它的原型构造函数的原型会有一个
consctructor
的属性指向构造函数本身, 即Person.prototype.constructor === Person
每一个
new
出来的实例都有一个隐式的__proto__
属性,指向它们的构造函数的原型,即person1.__proto__ === Person.prototype
person1.__proto__.constructor === Person
了解了这些基本概念之后,我们再来看看javascript的一些原生构造函数的关系网,看下列的图
按照我们上面的理解, Oject本身是一个构造函数,它也是一个对象,那么
Object.__proto__ === Function.prototype
为了方便我们记住上图,还有几个需要我们知道的特殊概念:
Function
的原型属性与Function
的原型指向同一个对象. 即Function.__proto__ == Function.prototype
Object.prototype.__proto__ === null
typeof Function.prototype === 'function'
浅谈javascript的原型及原型链的更多相关文章
- 浅谈JavaScript浮点数及其运算
原文:浅谈JavaScript浮点数及其运算 JavaScript 只有一种数字类型 Number,而且在Javascript中所有的数字都是以IEEE-754标准格式表示的.浮点数的精度问题 ...
- 浅谈JavaScript中的闭包
浅谈JavaScript中的闭包 在JavaScript中,闭包是指这样一个函数:它有权访问另一个函数作用域中的变量. 创建一个闭包的常用的方式:在一个函数内部创建另一个函数. 比如: functio ...
- 浅谈javascript函数节流
浅谈javascript函数节流 什么是函数节流? 函数节流简单的来说就是不想让该函数在很短的时间内连续被调用,比如我们最常见的是窗口缩放的时候,经常会执行一些其他的操作函数,比如发一个ajax请求等 ...
- 浅谈 JavaScript 编程语言的编码规范
对于熟悉 C/C++ 或 Java 语言的工程师来说,JavaScript 显得灵活,简单易懂,对代码的格式的要求也相对松散.很容易学习,并运用到自己的代码中.也正因为这样,JavaScript 的编 ...
- 浅谈JavaScript中的null和undefined
浅谈JavaScript中的null和undefined null null是JavaScript中的关键字,表示一个特殊值,常用来描述"空值". 对null进行typeof类型运 ...
- 浅谈JavaScript中的正则表达式(适用初学者观看)
浅谈JavaScript中的正则表达式 1.什么是正则表达式(RegExp)? 官方定义: 正则表达式是一种特殊的字符串模式,用于匹配一组字符串,就好比用模具做产品,而正则就是这个模具,定义一种规则去 ...
- [转载]浅谈JavaScript函数重载
原文地址:浅谈JavaScript函数重载 作者:ChessZhang 上个星期四下午,接到了网易的视频面试(前端实习生第二轮技术面试).面了一个多小时,自我感觉面试得很糟糕的,因为问到的很多问题都 ...
- 浅谈Javascript中的原型、原型链、继承
构造函数,原型,实例三者的关系 构造函数: 构造函数是创建对象的一种常用方式, 其他创建对象的方式还包括工厂模式, 原型模式, 对象字面量等.我们来看一个简单的构造函数: function Produ ...
- 浅谈JavaScript原型与原型链
对于很多前端开发者而言,JavaScript的原型实在是很让人头疼,所以我这边就整理了一下自己对应原型的一点理解,分享给大家,供交流使用 原型 说起原型,那就不得不说prototype.__proto ...
随机推荐
- dynamic-insert和dynamic-update属性
dynamic-insert 作用:设置对象中没有值的字段 insert并不会对其进行插入. 实体类映射配置如下 <!DOCTYPE hibernate-mapping PUBLIC &quo ...
- 微信小程序 - 生命周期 - 参数传递
现在WEB开发门槛越来越高,不想java 会了就可以有工作,前端不行 ,不仅JavaScript要求不低,基础的HTML+CSS还要扎实,jquery也是必须要会,现在的前端框架 Vue Ng R ...
- 关于mysql连接时候出现"error 2003: can't connect to mysql server on 'localhost'(10061)问题的解决
天,在使用navicat Premium 连接数据库时,出现了一个弹出窗口显示: "error 2003: can't connect to mysql server on 'localho ...
- ts包、表、子表、section的关系
我们经常接触到创建 DEMUX,注册 Filter 过滤数据, 通过回调过滤出 section 数据,然后我们对 section 数据做具体的解析或者其他操作. 我们这里说的 section 就是段的 ...
- 清华大学《C++语言程序设计基础》线上课程笔记02---类与对象
类与对象 public是类的对外访问接口: 类内初始值 在定义类时对数据成员写初始值,在创建对象的时候,会使用类内初始值初始化数据成员: class Clock { public: void show ...
- IO复用——epoll系列系统调用
1.内核事件表 epoll是Linux特有的I/O复用函数.epoll把用户关心的文件描述上的事件放在内核里的一个事件表中,并用一个额外的文件描述符来标识该内核事件表.这个额外文件描述符使用函数epo ...
- (数据科学学习手札31)基于Python的网络数据采集(初级篇)
一.简介 在实际的业务中,我们手头的数据往往难以满足需求,这时我们就需要利用互联网上的资源来获取更多的补充数据,但是很多情况下,有价值的数据往往是没有提供源文件的直接下载渠道的(即所谓的API),这时 ...
- 通过transpose和flip实现图像旋转90/180/270度
在fbc_cv库中,提供了对图像进行任意角度旋转的函数rotate,其实内部也是调用了仿射变换函数warpAffine.如果图像仅是进行90度倍数的旋转,是没有必要用warpAffine函数的.这里通 ...
- 管理员常用Windows PowerShell命令Top25
即使Windows PowerShell已经由来已久,但很多管理员并不愿意主动熟悉PowerShell cmdlet命令行.随着微软扩展了PowerShell的功能,管理员应该对其功能及使用烂熟于心. ...
- Hackerrank - The Grid Search
https://www.hackerrank.com/challenges/the-grid-search/forum 今天碰见这题,看见难度是Moderate,觉得应该能半小时内搞定. 读完题目发现 ...