理解 JavaScript 对象原型、原型链如何工作、如何向 prototype 属性添加新的方法。
JavaScript 常被描述为一种基于原型的语言 (prototype-based language)——每个对象拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推(这里的你可能还是懵的状态,先别管其他的,就知道原型是一层一层的就可以)。这种关系常被称为原型链 (prototype chain),它解释了为何一个对象会拥有定义在其他对象中的属性和方法。
也就是说,我们没有特意去给一个实例对象添加属性和方法,但是这个实例对象却有属性和方法,这是因为这个实例对象继承了定义在构造器函数(我们事先定义好的一个函数,这个函数中有属性和方法,prototype就是特殊的属性,因为特殊所以它叫原型)中的属性和方法以及prototype。
那么这种继承是怎么实现的呢?实例对象和它的构造器之间建立一个链接(它是__proto__属性,是从构造函数的prototype属性派生的),之后通过上溯原型链,在构造器中找到这些属性和方法。
理解对象的原型(可以通过Object.getPrototypeOf(obj)或者已被弃用的__proto__属性获得)与构造函数的prototype属性之间的区别是很重要的。前者是每个实例上都有的属性,后者是构造函数的属性。也就是说,Object.getPrototypeOf(new Foobar())和Foobar.prototype指向着同一个对象。
构造函数、实例、原型(对象)、原型链到底是什么?
1、来看个例子:
创建一个构造函数Person,实例化这个Person为person1:
function Person(name, age) { // 构造函数
this.name = name
this.age = age
};
Person.prototype.sex = "male" // Person.prototype叫原型(对象)
var person1 = new Person('zs', 18) // 实例
console.log(person1.__proto__, person1.__proto__.__proto__)

结果显示两个框中的值是一样的,就是说person1.__proto__继续上溯一层__proto__,会找到上层的__proto__,也就是 person1.__proto__.__proto__,这两个量指向的同一个对象。那么这个上溯的过程就称为原型链。
2、构造函数的prototype(原型)和实例的__proto__的关系是怎么样的呢?
function Person(name, age) { // 构造函数
this.name = name
this.age = age
};
Person.prototype.sex = "male"
var person1 = new Person('zs', 18) // 实例
console.log(person1.__proto__, Person.prototype)

结果如上:person1的__proto__指向的对象,就是Person的prototype(原型)指向的对象,也就是原型(对象)。
3、现在我们看个查找原型链的路径,按原型链的路径找person1.valueOf()方法:
function Person(name, age) {
this. name = name
this.age = age
};
Person.prototype.sex = "male"
var person1 = new Person('zs', 18)
console.log(person1.valueOf())
结果:

valueOf() 这个方法仅仅返回了被调用对象的值。在这个例子中发生了如下过程:
- 浏览器首先检查,
person1对象是否具有可用的valueOf()方法。 - 如果没有,则浏览器检查
person1对象的原型对象(即Person构造函数的prototype属性所指向的对象)是否具有可用的valueof()方法。 - 如果也没有,则浏览器检查
Person()构造函数的prototype属性所指向的对象的原型对象(这个例子指Object构造函数的prototype属性所指向的对象)是否具有可用的valueOf()方法。这里有这个方法,于是该方法被调用。 - 原型链一般往上回溯可以回溯到Object.prototype;Object.prototype的原型对象是null;而null的原型对象不存在!可以用Object.getPrototypeOf()方法检测一个对象的prototype
console.log(Object.getPrototypeOf(Object.prototype)); // null
来尝试获取一下null 和 undefined 的 prototype
console.log(Object.getPrototypeOf(null));//报错 Cannot convert undefined or null to object
注意:读取对象的某个属性时,js引擎会先在对象本身属性上寻找,如果找不到,那么去原型对象上找,一层一层往上"上溯",直至null.所以如果找寻某个不存在的属性,会将完整的原型链遍历一变,对性能影响较大。
我们来验证一下是不是这么去上溯的:
function Person(name, age) {
this. name = name
this.age = age
};
Person.prototype.sex = "male"
var person1 = new Person('zs', 18)
console.log(person1.__proto__, Person.prototype)
结果:

我可以得出结论,实例对象 person1的__proto__指向的就是person1的构造函数(Person)的prototype属性(Person的原型),这两个量指向的是同一个对象,在这个对象中第一层并没有valueOf()方法,所以就找不到,找不到就上溯,Person是从内置对象Object实例出来的(Person是内置对象Object的实例),因此就要继续使用Person的__proto__到Object的prototype中去找valueOf()方法。来看代码:
function Person(name, age) {
this. name = name
this.age = age
};
Person.prototype.sex = "male"
var person1 = new Person('zs', 18)
console.log(person1.__proto__, Person.prototype, Object.prototype)
结果:

这个时候,Object的prototype中有valueOf()方法,找到了!执行并打印出person1的内容:

JavaScript 中到处都是通过原型链继承的例子。比如,你可以尝试从 String、Date、Number和 Array 全局对象的原型中寻找方法和属性。它们都在原型上定义了一些方法,因此当你创建一个字符串时:
var myString = 'This is my string.';
myString 立即具有了一些有用的方法,如 split()、indexOf()、replace() 等。
4、constructor是个什么东东??
prototype原型(对象)有一个constructor属性,默认指向prototype所在的构造函数。
function Person(name, age) { // 构造函数
this.name = name
this.age = age
};
Person.prototype.sex = "male"
var person1 = new Person('zs', 18) // 实例
console.log(Person.prototype.constructor === Person) // true
这段代码证明了构造函数的原型中有个constructor属性,这个属性指向构造函数!是不是又懵了?让我带你飞起来,接着看。
来个图吧,

有了这个图,是不是清楚一些了?
5、继承的实现:
prototype 属性大概是 JavaScript 中最容易混淆的名称之一。你可能会认为,this 关键字指向当前对象的原型对象,其实不是(还记得么?原型对象是一个内部对象,应当使用 __proto__ 访问)。prototype 属性包含(指向)一个对象,你在这个对象中定义需要被继承的成员。
再来看,原型对象的修改:
function Person(name, age) {
this. name = name
this.age = age
};
Person.prototype.sex = "male"
var person1 = new Person('zs', 18)
Person.prototype.farewell = function() {
console.log('this is farewell fn!');
}
person1.farewell()
结果:

这时,我们看到,farewell已经被继承到了person1上。也就是说,旧有对象实例的可用功能被自动更新了。这证明了先前描述的原型链模型。这种继承模型下,上游对象的方法会复制到下游的对象实例中;下游对象本身虽然没有定义这些方法,但浏览器会通过上溯原型链、从上游对象中找到它们。这种继承模型提供了一个强大而可扩展的功能系统。
当然这个person1对象实例拥有了farewell方法,如果我们再实例化出来多个新的对象实例,那这些新的对象实例也都有farewell方法,这个时候如果farewell显示的一个人的姓名,那是不是所有对象实例中的姓名都一样了?但是现实中,不可能所有人都有一样的名字,不过还好,人可以按“姓”划成不同的类,所以我们可以把同一个姓的的这个属性,写在原型中,把名写在实例上,这样用一个构造函数,就能实例化出这个姓氏下的所有人的姓名:
function Person(lastName, age) {
this.lastName = lastName
this.age = age
};
Person.prototype.firstName = 'wang'
var person1 = new Person('wu', 18)
var person2 = new Person('liu', 20)
console.log(person1.fullName = person1.firstName + person1.lastName)
console.log(person2.fullName = person2.firstName + person2.lastName)

好。“姓”这个属性,已经被person1和person2都继承到了。是不是不用每次都定义“姓”这个属性了?省了不少事。
所以我们通过构造函数生成实例化对象,本质其实就是将构造函数的prototype属性赋值给实例对象的原型(对象)。
6、instanceof ()判断对象是否为构造函数的实例
function Person(name, age) { // 构造函数
this.name = name
this.age = age
};
Person.prototype.sex = "male"
var person1 = new Person('zs', 18) // 实例
console.log(person1 instanceof Person) // true : person1 是Person的实例
console.log(Person.prototype.isPrototypeOf(person1)) // true : Person(构造函数)的原型是person1(实例)的原型,指向同一个对象
7、Object.getPrototypeOf() 获取一个对象的原型对象
function Person(name, age) { // 构造函数
this.name = name
this.age = age
};
Person.prototype.sex = "male"
var person1 = new Person('zs', 18) // 实例
console.log(Object.getPrototypeOf(Person))
console.log(Object.getPrototypeOf(person1))

8、Object.setPrototypeOf():第一个参数是现有对象,第二个是原型对象;返回一个新对象
var a = {
name:'apple',
f:function(){
console.log('this is a test function');
}
};
var b = Object.setPrototypeOf({},a);
//b本身是空对象,它的原型对象是a;b能调用a的属性和方法
console.log(b);
console.log(b.name);//apple
b.f(); //this is a test function
9、Object.create()使用场景
有时候,我们不能拿到对象的构造函数,只能取到实例对象;比如如下生成的:
var a = {
name:'apple'
};
var b = Object.create(a);
console.log(b); // {}
console.log(b.name); //apple
console.log(Object.getPrototypeOf(b)===a) // true b的原型是a

10、Object.prototype.isPrototypeOf:判断是否是某个对象的原型对象
console.log(Object.prototype.isPrototypeOf(Array)); // true
水平有限,前端小学生,文章中或有不当之处,欢迎批评指正!
理解 JavaScript 对象原型、原型链如何工作、如何向 prototype 属性添加新的方法。的更多相关文章
- 理解javascript 对象,原型对象、闭包
javascript作为一个面向对象的语言,理解 对象.原型.闭包.模块模式等技术点对于成为一名合格的javascript程序员相当重要,多年没写过blog,今天就先拋个玉,在下基本也不做前端,但颇感 ...
- JavaScript 开发进阶:理解 JavaScript 作用域和作用域链
作用域是JavaScript最重要的概念之一,想要学好JavaScript就需要理解JavaScript作用域和作用域链的工作原理.今天这篇文章对JavaScript作用域和作用域链作简单的介绍,希望 ...
- JavaScript 开发进阶:理解 JavaScript 作用域和作用域链(转载 学习中。。。)
作用域是JavaScript最重要的概念之一,想要学好JavaScript就需要理解JavaScript作用域和作用域链的工作原理.今天这篇文章对JavaScript作用域和作用域链作简单的介绍,希望 ...
- web前端学习(二) javascript对象和原型继承
目录 1. JavaScrpt对象 2. 原型对象和继承 3. 对象的克隆 (1)javascript对象 在JS中,对象是属性的容器.对于单个对象来说,都由属性名和属性值构成:其中属性名需要是标识符 ...
- 理解javascript的闭包,原型,和匿名函数及IIFE
理解javascript的闭包,原型,和匿名函数(自己总结) 一 .>关于闭包 理解闭包 需要的知识1.变量的作用域 例1: var n =99; //建立函数外的全局变量 function r ...
- 深入理解JavaScript作用域和作用域链
前言 JavaScript 中有一个被称为作用域(Scope)的特性.虽然对于许多新手开发者来说,作用域的概念并不是很容易理解,本文我会尽我所能用最简单的方式来解释作用域和作用域链,希望大家有所收获! ...
- 理解JavaScript对象
理解JavaScript对象 对象是JavaScript的基本数据类型.对象是一种复合值:将很多值(原始值或者其他对象)聚合在一起. JavaScript对象不仅可以保持自有的属性,还可以从原型对象继 ...
- 如何理解JavaScript中的原型和原型链
首先是一张关系图,避免抽象化理解时产生的困难 Function对象 函数对象是JavaScript学习中不可避免的一部分,而且这一部分相对重要且抽象 函数的创建方式有2种: 字面量创建 var foo ...
- 深入理解javascript构造函数和原型对象
---恢复内容开始--- 对象,是javascript中非常重要的一个梗,是否能透彻的理解它直接关系到你对整个javascript体系的基础理解,说白了,javascript就是一群对象在搅..(哔! ...
随机推荐
- solr-6.4.2安装+分词器配置
一.solr安装 solr下载地址:http://archive.apache.org/dist/lucene/solr/6.4.2/ 1.解压solr软件包:tar xf solr-6.4.2.tg ...
- LeetCode:螺旋矩阵||【59】
LeetCode:螺旋矩阵||[59] 题目描述 给定一个正整数 n,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的正方形矩阵. 示例: 输入: 3 输出: [ [ 1, 2, 3 ...
- UIPageControl修改圆点大小,根据View大小自适应
遇到了个基本的控件问题,当设置UIPageControl的frame很小时,上面的小圆点会忽视view的frame而将圆点显示到控件外面. 但是如果想要设置小一点的圆点,或改变圆点间的间距,从而实现自 ...
- XML文件结构和基本语法
XML文件的结构性内容,包括节点关系以及属性内容等等.元素是组成XML的最基本的单位,它由开始标记,属性和结束标记组成.就是一个元素的例子,每个元素必须有一个元素名,元素可以若干个属性以及属性值. x ...
- 机器学习算法之:KNN
基于实例的学习方法中,最近邻法和局部加权回归法用于逼近实值或离散目标函数,基于案例的推理已经被应用到很多任务中,比如,在咨询台上存储和复用过去的经验:根据以前的法律案件进行推理:通过复用以前求解的问题 ...
- Docker容器技术-自动化部署
一.用Chef自动化部署Docker 1.为什么需要自动化部署? Docker引擎需要配置很多参数(cgroups.内存.CPU.文件系统等) 识别Docker容器运行在哪个宿主机上 耗时且容易出错, ...
- Java zip 压缩 文件夹删除,移动,重命名,复制
FileUtil.java import java.io.*; import java.util.List; import java.util.zip.ZipEntry; import java.ut ...
- R读取大数据data.table包之fread
>library(data.table)>data=fread("10000000.txt")>Read 9999999 rows and 71 (of 71) ...
- 大话设计模式之PHP篇 - 观察者模式
定义观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,这个主题对象在状态发生改变时,会通知所有观察者对象,使他们能够自动更新自己. <?php /*主题类或称为通知类 ...
- L1范数与L2范数正则化
2018-1-26 虽然我们不断追求更好的模型泛化力,但是因为未知数据无法预测,所以又期望模型可以充分利用训练数据,避免欠拟合.这就要求在增加模型复杂度.提高在可观测数据上的性能表现得同时,又需要兼顾 ...