首页 web前端 js教程 详解JS原型和原型链(三)

详解JS原型和原型链(三)

Mar 22, 2018 am 11:17 AM
javascript 原型 详解

这次接着上篇跟小编一起来看看JS原型和原型链以及注意事项有哪些,下面就是实战案例,一起来看一下。

七. 函数对象 (复习一下前面的知识点)

所有函数对象的proto都指向Function.prototype,它是一个空函数(Empty 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
// 所有的构造器都来自于Function.prototype,甚至包括根构造器Object及Function自身
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
登录后复制

JavaScript中有内置(build-in)构造器/对象共计12个(ES5中新加了JSON),这里列举了可访问的8个构造器。剩下如Global不能直接访问,Arguments仅在函数调用时由JS引擎创建,Math,JSON是以对象形式存在的,无需new。它们的proto是Object.prototype。如下

Math.__proto__ === Object.prototype  // true
Math.construrctor == Object // true
JSON.__proto__ === Object.prototype  // true
JSON.construrctor == Object //true
登录后复制

上面说的函数对象当然包括自定义的。如下

// 函数声明
function Person() {}
// 函数表达式
var Perosn = function() {}
console.log(Person.__proto__ === Function.prototype) // true
console.log(Man.__proto__ === Function.prototype)    // true
登录后复制

这说明什么呢?

** 所有的构造器都来自于 Function.prototype,甚至包括根构造器Object及Function自身。所有构造器都继承了·Function.prototype·的属性及方法。如length、call、apply、bind**

(你应该明白第一句话,第二句话我们下一节继续说,先挖个坑:))
Function.prototype也是唯一一个typeof XXX.prototype为 function的prototype。其它的构造器的prototype都是一个对象(原因第三节里已经解释过了)。如下(又复习了一遍):

console.log(typeof Function.prototype) // function
console.log(typeof Object.prototype)   // object
console.log(typeof Number.prototype)   // object
console.log(typeof Boolean.prototype)  // object
console.log(typeof String.prototype)   // object
console.log(typeof Array.prototype)    // object
console.log(typeof RegExp.prototype)   // object
console.log(typeof Error.prototype)    // object
console.log(typeof Date.prototype)     // object
console.log(typeof Object.prototype)   // object
登录后复制

噢,上面还提到它是一个空的函数,console.log(Function.prototype) 下看看(留意,下一节会再说一下这个)

知道了所有构造器(含内置及自定义)的__proto__都是Function.prototype,那Function.prototype的__proto__是谁呢?
相信都听说过JavaScript中函数也是一等公民,那从哪能体现呢?如下

console.log(Function.prototype.__proto__ === Object.prototype) // true
登录后复制

这说明所有的构造器也都是一个普通 JS 对象,可以给构造器添加/删除属性等。同时它也继承了Object.prototype上的所有方法:toString、valueOf、hasOwnProperty等。(你也应该明白第一句话,第二句话我们下一节继续说,不用挖坑了,还是刚才那个坑;))

最后Object.prototype的proto是谁?
Object.prototype.__proto__ === null // true
已经到顶了,为null。(读到现在,再回过头看第五章,能明白吗?)

八. Prototype

在 ECMAScript 核心所定义的全部属性中,最耐人寻味的就要数 prototype 属性了。对于 ECMAScript 中的引用类型而言,prototype 是保存着它们所有实例方法的真正所在。换句话所说,诸如 toString()和 valuseOf() 等方法实际上都保存在 prototype 名下,只不过是通过各自对象的实例访问罢了。

——《JavaScript 高级程序设计》第三版 P116

我们知道 JS 内置了一些方法供我们使用,比如:
对象可以用 constructor/toString()/valueOf() 等方法;
数组可以用 map()/filter()/reducer() 等方法;
数字可用用 parseInt()/parseFloat()等方法;
Why ???

当我们创建一个函数时:

var Person = new Object()
Person 是 Object 的实例,所以 Person 继承了Object 的原型对象Object.prototype上所有的方法:

360截图20180322110903897.jpg

Object.prototype

Object 的每个实例都具有以上的属性和方法。
所以我可以用 Person.constructor 也可以用 Person.hasOwnProperty。

当我们创建一个数组时:

var num = new Array()
num 是 Array 的实例,所以 num 继承了Array 的原型对象Array.prototype上所有的方法:

360截图20180322110947436.jpg

Array.prototype

Are you f***ing kidding me? 这尼玛怎么是一个空数组???

我们可以用一个 ES5 提供的新方法:Object.getOwnPropertyNames
获取所有(包括不可枚举的属性)的属性名不包括 prototy 中的属性,返回一个数组:

360截图20180322111019957.jpg

var arrayAllKeys = Array.prototype; // [] 空数组// 只得到 arrayAllKeys 这个对象里所有的属性名(不会去找 arrayAllKeys.prototype 中的属性)console.log(Object.getOwnPropertyNames(arrayAllKeys));
/* 输出:
["length", "constructor", "toString", "toLocaleString", "join", "pop", "push",
"concat", "reverse", "shift", "unshift", "slice", "splice", "sort", "filter", "forEach",
"some", "every", "map", "indexOf", "lastIndexOf", "reduce", "reduceRight",
"entries", "keys", "copyWithin", "find", "findIndex", "fill"]
*/

这样你就明白了随便声明一个数组,它为啥能用那么多方法了。

细心的你肯定发现了Object.getOwnPropertyNames(arrayAllKeys) 输出的数组里并没有 constructor/hasOwnPrototype等对象的方法(你肯定没发现)。
但是随便定义的数组也能用这些方法

360截图20180322111059753.jpg

var num = [1];console.log(num.hasOwnPrototype()) // false (输出布尔值而不是报错)

Why ???

因为Array.prototype 虽然没这些方法,但是它有原型对象(__proto__):

// 上面我们说了 Object.prototype 就是一个普通对象。
Array.prototype.__proto__ == Object.prototype
登录后复制

所以 Array.prototype 继承了对象的所有方法,当你用num.hasOwnPrototype()时,JS 会先查一下它的构造函数 (Array) 的原型对象 Array.prototype 有没有有hasOwnPrototype()方法,没查到的话继续查一下 Array.prototype 的原型对象 Array.prototype.__proto__有没有这个方法。

当我们创建一个函数时:

var f = new Function("x","return x*x;");
//当然你也可以这么创建 f = function(x){ return x*x }
console.log(f.arguments) // arguments 方法从哪里来的?
console.log(f.call(window)) // call 方法从哪里来的?
console.log(Function.prototype) // function() {} (一个空的函数)
console.log(Object.getOwnPropertyNames(Function.prototype)); 
/* 输出
["length", "name", "arguments", "caller", "constructor", "bind", "toString", "call", "apply"]
*/
登录后复制

我们再复习第八小节这句话:

所有函数对象proto都指向 Function.prototype,它是一个空函数(Empty function)

嗯,我们验证了它就是空函数。不过不要忽略前半句。我们枚举出了它的所有的方法,所以所有的函数对象都能用,比如:

360截图20180322111305529.jpg

九. 复习一下

第八小节我们总结了:

所有函数对象的 __proto__ 都指向 Function.prototype,它是一个空函数(Empty function)
登录后复制

但是你可别忘了在第三小节我们总结的:

所有对象的 __proto__ 都指向其构造器的 prototype
登录后复制

我们下面再复习下这句话。

先看看 JS 内置构造器:

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
登录后复制

再看看自定义的构造器,这里定义了一个 Person:

function Person(name) { 
 this.name = name;
}
var p = new Person('jack')
console.log(p.__proto__ === Person.prototype) // true
登录后复制

p 是 Person 的实例对象,p 的内部原型总是指向其构造器 Person 的原型对象 prototype。

每个对象都有一个 constructor 属性,可以获取它的构造器,因此以下打印结果也是恒等的:

function Person(name) {
    this.name = name
}
var p = new Person('jack')
console.log(p.__proto__ === p.constructor.prototype) // true
登录后复制

上面的Person没有给其原型添加属性或方法,这里给其原型添加一个getName方法:

function Person(name) { 
   this.name = name
}
// 修改原型
Person.prototype.getName = function() {}
var p = new Person('jack')
console.log(p.__proto__ === Person.prototype) // true
console.log(p.__proto__ === p.constructor.prototype) // true
登录后复制

可以看到p.__proto__与Person.prototype,p.constructor.prototype都是恒等的,即都指向同一个对象。

如果换一种方式设置原型,结果就有些不同了:

function Person(name) {  
  this.name = name
}
// 重写原型Person.prototype = {  
  getName: function() {}
}
var p = new Person('jack')
console.log(p.__proto__ === Person.prototype) // true
console.log(p.__proto__ === p.constructor.prototype) // false
登录后复制

这里直接重写了 Person.prototype(注意:上一个示例是修改原型)。输出结果可以看出p.__proto__仍然指向的是Person.prototype,而不是p.constructor.prototype。

这也很好理解,给Person.prototype赋值的是一个对象直接量{getName: function(){}},使用对象直接量方式定义的对象其构造器(constructor)指向的是根构造器Object,Object.prototype是一个空对象{},{}自然与{getName: function(){}}不等。如下:

var p = {}
console.log(Object.prototype) // 为一个空的对象{}
console.log(p.constructor === Object) // 对象直接量方式定义的对象其constructor为Objectconsole.log(p.constructor.prototype === Object.prototype) // 为true,
不解释(๑ˇ3ˇ๑)
登录后复制

十. 原型链(再复习一下:)

下面这个例子你应该能明白了!

function Person(){
}var person1 = new Person();
console.log(person1.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype) //true
console.log(Object.prototype.__proto__) //null
Person.__proto__ == Function.prototype; //true
console.log(Function.prototype)// function(){} (空函数)
var num = new Array()console.log(num.__proto__ == Array.prototype) // true
console.log( Array.prototype.__proto__ == Object.prototype) // true
console.log(Array.prototype) // [] (空数组)
console.log(Object.prototype.__proto__) //null
console.log(Array.__proto__ == Function.prototype)// true
登录后复制

疑点解惑:

Object.__proto__ === Function.prototype // true
登录后复制

Object 是函数对象,是通过new Function()创建的,所以Object.__proto__指向Function.prototype。(参照第八小节:「所有函数对象的__proto__都指向Function.prototype」)

Function.__proto__ === Function.prototype // true
登录后复制

Function 也是对象函数,也是通过new Function()创建,所以Function.__proto__指向Function.prototype。

自己是由自己创建的,好像不符合逻辑,但仔细想想,现实世界也有些类似,你是怎么来的,你妈生的,你妈怎么来的,你姥姥生的,……类人猿进化来的,那类人猿从哪来,一直追溯下去……,就是无,(NULL生万物)
正如《道德经》里所说“无,名天地之始”。

Function.prototype.__proto__ === Object.prototype //true
登录后复制

其实这一点我也有点困惑,不过也可以试着解释一下。
Function.prototype是个函数对象,理论上他的__proto__应该指向 Function.prototype,就是他自己,自己指向自己,没有意义。
JS一直强调万物皆对象,函数对象也是对象,给他认个祖宗,指向Object.prototype。Object.prototype.__proto__ === null,保证原型链能够正常结束。

十一 总结

原型和原型链是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); //undefined
 var dog = function(){}; var tidy = new dog();
 tidy.price = 2000; console.log(dog.price); //undefined
登录后复制

这个明白吧?想一想我们上面说过这句话:

实例(tidy)和 原型对象(dog.prototype)存在一个连接。不过,要明确的真正重要的一点就是,这个连接存在于实例(tidy)与构造函数的原型对象(dog.prototype)之间,而不是存在于实例(tidy)与构造函数(dog)之间。

聪明的你肯定想通了吧 :)

以上是详解JS原型和原型链(三)的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

Win11管理员权限获取详解 Win11管理员权限获取详解 Mar 08, 2024 pm 03:06 PM

Windows操作系统是全球最流行的操作系统之一,其新版本Win11备受瞩目。在Win11系统中,管理员权限的获取是一个重要的操作,管理员权限可以让用户对系统进行更多的操作和设置。本文将详细介绍在Win11系统中如何获取管理员权限,以及如何有效地管理权限。在Win11系统中,管理员权限分为本地管理员和域管理员两种。本地管理员是指具有对本地计算机的完全管理权限

Oracle SQL中的除法运算详解 Oracle SQL中的除法运算详解 Mar 10, 2024 am 09:51 AM

OracleSQL中的除法运算详解在OracleSQL中,除法运算是一种常见且重要的数学运算操作,用于计算两个数相除的结果。除法在数据库查询中经常用到,因此了解OracleSQL中的除法运算及其用法是数据库开发人员必备的技能之一。本文将详细讨论OracleSQL中除法运算的相关知识,并提供具体的代码示例供读者参考。一、OracleSQL中的除法运算

PHP模运算符的作用及用法详解 PHP模运算符的作用及用法详解 Mar 19, 2024 pm 04:33 PM

PHP中的模运算符(%)是用来获取两个数值相除的余数的。在本文中,我们将详细讨论模运算符的作用及用法,并提供具体的代码示例来帮助读者更好地理解。1.模运算符的作用在数学中,当我们将一个整数除以另一个整数时,会得到一个商和一个余数。例如,当我们将10除以3时,商为3,余数为1。模运算符就是用来获取这个余数的。2.模运算符的用法在PHP中,使用%符号来表示模

linux系统调用system()函数详解 linux系统调用system()函数详解 Feb 22, 2024 pm 08:21 PM

Linux系统调用system()函数详解系统调用是Linux操作系统中非常重要的一部分,它提供了一种与系统内核进行交互的方式。其中,system()函数是一个常用的系统调用函数之一。本文将详细介绍system()函数的使用方法,并提供相应的代码示例。系统调用的基本概念系统调用是用户程序与操作系统内核交互的一种方式。用户程序通过调用系统调用函数来请求操作系统

简易JavaScript教程:获取HTTP状态码的方法 简易JavaScript教程:获取HTTP状态码的方法 Jan 05, 2024 pm 06:08 PM

JavaScript教程:如何获取HTTP状态码,需要具体代码示例前言:在Web开发中,经常会涉及到与服务器进行数据交互的场景。在与服务器进行通信时,我们经常需要获取返回的HTTP状态码来判断操作是否成功,根据不同的状态码来进行相应的处理。本篇文章将教你如何使用JavaScript获取HTTP状态码,并提供一些实用的代码示例。使用XMLHttpRequest

Linux的curl命令详解 Linux的curl命令详解 Feb 21, 2024 pm 10:33 PM

Linux的curl命令详解摘要:curl是一种强大的命令行工具,用于与服务器进行数据通信。本文将介绍curl命令的基本用法,并提供实际的代码示例,帮助读者更好地理解和应用该命令。一、curl是什么?curl是一个命令行工具,用于发送和接收各种网络请求。它支持多种协议,如HTTP、FTP、TELNET等,并提供了丰富的功能,如文件上传、文件下载、数据传输、代

深入了解Promise.resolve() 深入了解Promise.resolve() Feb 18, 2024 pm 07:13 PM

Promise.resolve()详解,需要具体代码示例Promise是JavaScript中一种用于处理异步操作的机制。在实际开发中,经常需要处理一些需要按顺序执行的异步任务,而Promise.resolve()方法就是用来返回一个已经Fulfilled状态的Promise对象。Promise.resolve()是Promise类的一个静态方法,它接受一个

numpy版本查询方法详解 numpy版本查询方法详解 Jan 19, 2024 am 08:20 AM

Numpy是一款Python科学计算库,提供了丰富的数组操作函数和工具。升级Numpy版本时需要查询当前版本以保证兼容性,本文将详细介绍Numpy版本查询的方法,并提供具体的代码示例。方法一:使用Python代码查询Numpy版本使用Python代码可以轻松查询Numpy的版本,以下是实现方法和示例代码:importnumpyasnpprint(np

See all articles