JavaScript原型及原型链终极详解
JavaScript中的原型及原型链
一、普通对象与函数对象
在JavaScript中,万物皆对象。这些对象可以分为普通对象和函数对象。普通对象是我们通常创建的对象,而函数对象则是特殊的对象,它们可以像函数一样被调用。例如,通过`new Object()`或字面量创建的对象是普通对象,而通过`new Function()`创建的对象是函数对象。值得注意的是,所有的函数对象都有一个特殊的属性——原型(prototype)。
二、原型对象
原型是JavaScript中非常重要的概念。每当我们在JavaScript中定义一个函数时,该函数都会有一个原型对象。这个原型对象是一个普通的对象,用于共享属性和方法。换句话说,原型对象是函数的一个属性,它允许我们为所有使用该函数的实例定义共享的方法和属性。这对于实现继承非常有用。需要注意的是,普通对象并没有prototype属性,但它们有一个内部属性__proto__,指向它的原型对象。
三、原型链
在JavaScript中,每个对象都有一个指向其原型对象的链接(即__proto__属性)。当试图访问一个对象的属性时,如果该对象内部不存在这个属性,那么JavaScript会在对象的原型上寻找这个属性,这就是原型链的概念。如果原型上也没有找到,那么会在原型的原型上寻找,以此类推,直到找到属性或达到原型链的顶端(即null)。这就是JavaScript实现继承的方式。
举一个例子来说明这个概念:假设我们有一个Person函数和一个名为zjh的Person实例。如果我们试图访问zjh的getName方法,JavaScript首先会在zjh自身上寻找这个方法。如果找不到,那么它会在Person的原型(也就是Person.prototype)上寻找这个方法。如果仍然找不到,那么它会在Object的原型(也就是Object.prototype)上寻找这个方法。这就是原型链的工作方式。通过这种方式,我们可以实现函数的继承和功能共享。在理解了JavaScript的原型和原型链之后,我们可以更好地理解JavaScript中的一些高级功能,如模块化、继承等。这也是理解许多高级JavaScript框架和库的基础。原型链与内存结构
在JavaScript的世界里,无论是普通的对象还是函数对象,它们的诞生都伴随着一个特殊的属性——`__proto__`。这个属性指向了创建它的函数对象的原型对象`prototype`。
让我们以一个实例来说明:
当我们创建了一个对象`zjh`基于`person`函数时,`zjh.__proto__`会指向`person`的`prototype`。简单验证一下:
```javascript
console.log(zjh.__proto__ === person.prototype) // 输出:true
```
同样,`person.prototype`也有其自己的`__proto__`属性,它指向了创建`person`函数的原型对象——也就是`Object`的`prototype`。
```javascript
console.log(person.prototype.__proto__ === Object.prototype) // 输出:true
```
但当我们到`Object.prototype`的`__proto__`时,情况变得有些特殊。因为到了这一步,原型链的尽头是`null`。
```javascript
console.log(Object.prototype.__proto__) // 输出:null
```
我们把这一系列通过`__proto__`相互关联的链,直到终点`null`,称为“原型链”。为了更好地直观理解,我们可以绘制一个内存结构图来展示这一原型链。
为了进一步深入解释原型链中的一些疑惑点:
1. 关于`Object.__proto__ === Function.prototype`:Object是一个函数对象,通过`new Function()`创建,所以它的`__proto__`确实指向`Function.prototype`。
2. 对于函数对象如`Function`,它的起源似乎有些不符合常规逻辑。但深入思考,这与现实世界中的许多事物类似——我们都是由我们的父母所生,我们的父母也是由他们的父母所生,一直追溯下去,最终可以追溯到某个起源点。在JavaScript中,这个起源就是`null`。正如《道德经》里所说:“无,名天地之始”。
3. 至于`Function.prototype.__proto__ === Object.prototype`,虽然有些困惑,但可以这样理解:在JavaScript中,函数对象也是对象,所以它被认定为有原型,而这个原型就是指向`Object.prototype`。这保证了原型链能够正常结束。
除了原型链,还有一个重要的概念是`constructor`。原型对象`prototype`中都有一个预定义的`constructor`属性,它引用的是该对象的函数。这是一种循环引用。例如:
```javascript
person.prototype.constructor === person // 输出:true
```
当我们查找一个对象的`constructor`时,我们会在该对象的原型链上寻找,碰到的第一个`constructor`属性所指向的对象就是答案。
要理解的是,原型和原型链是JavaScript实现继承的一种重要模型。原型链的真正形成依赖于`__proto__`属性,而非仅仅是`prototype`。要深入理解这一机制,我们需要通过实际例子来检验和巩固我们的理解。理解JavaScript中的原型链:一个生动的解读
在JavaScript的世界里,我们可以想象这样一个场景:首先有一个名为`animal`的函数对象,它有一个属性叫做`price`,值为2000。接着,我们创建了一个名为`dog`的函数对象,它的原型(prototype)指向了`animal`。这意味着什么呢?
想象一下,你有一个父亲(函数对象),他给你生了一个大哥(prototype)。父亲给你大哥买了很多玩具,这些玩具可以视作属性。当你出生时,你和大哥之间有着亲情纽带(__proto__),这使得你可以自然地继承你大哥的所有玩具。这就是原型链的基本原理。因此当你执行`tidy.price`时,它会沿着这个“链”寻找,最终在`animal`中找到属性`price`,输出结果为2000。这说明了原型链的真正形成依赖于`__proto__`,而不是简单的通过prototype来指定。
现在,如果我们换个方式指定关系,比如让`dog.__proto__ = animal`,会有什么不同呢?这时候可以想象你是直接和你的大哥有了直接的链接关系(通过亲情纽带),而不需要通过父亲来间接获取大哥的玩具。所以当你出生时,你直接继承了大哥的所有玩具(属性),这就是直接访问到`animal`中的属性`price`的方式。在这种情况下,你可以说你是直接从你的大哥那里继承来的玩具。换句话说,“长兄如父”,你通过直接链接获取到了你大哥所拥有的玩具(属性)。这就像你先生了一个大儿子,给他买了很多玩具后,再生的孩子自然会继承大儿子的玩具。至于这些玩具之间是否会互相冲突或者引发一些矛盾,那是另一个话题了。这种比喻虽然不完全准确,但有助于我们理解JavaScript中的原型链和继承机制。理解了这些基本概念后,我们可以更轻松地驾驭这个强大的编程工具。在这个例子中,我们了解了如何通过原型链来访问和继承属性,这是JavaScript中非常重要的一个概念。
网络推广网站
- JavaScript原型及原型链终极详解
- jQuery插件jquery-barcode实现条码打印的方法
- 微信抢红包ASP.NET代码轻松实现
- HTML5+JS+JQuery+ECharts实现异步加载问题
- jQuery zTree加载树形菜单功能
- 原生js结合html5制作简易的双色子游戏
- 表单上传功能实现 ajax文件异步上传
- js实现百度搜索提示框
- 基于javascript实现按圆形排列DIV元素(一)
- SQL Server在AlwaysOn中使用内存表的“踩坑”记录
- Vue.js鼠标悬浮更换图片功能
- 基于js实现投票的实例代码
- ASP.NET 2.0中的数据操作之七:使用DropDownList过滤的
- Javascript 拖拽雏形中的一些问题(逐行分析代码,
- ASP.NET MVC使用RazorEngine解析模板生成静态页
- 谈谈JavaScript自定义回调函数