前言

本文2088字,阅读大约需要13分钟。

总括: 结合实例阐述了原型和原型链的概念并总结了几种创建对象的方法,扩展原型链的方法。

禄无常家,福无家门。

正文

原型

Javascript中有一句话,叫一切皆是对象,当然这句话也不严谨,比如nullundefined就不是对象,除了这俩完全可以说Javascript一切皆是对象。而Javascript对象都有一个叫做原型的公共属性,属性名是_proto_。这个原型属性是对另一个对象的引用,通过这个原型属性我们就可以访问另一个对象所有的属性和方法。比如:

let numArray = [1, 2, -8, 3, -4, 7];

Array对象就有一个原型属性指向Array.prototype,变量numArray继承了Array.prototype对象所有的属性和方法。

这就是为什么可以直接调用像sort()这种方法:

console.log(numArray.sort()); // -> [-4, -8, 1, 2, 3, 7]

也就是说:

numArray.__proto__ === Array.prototype // true

对于其他对象(函数)也是一样(比如Date(),Function(), String(),Number()等);

当一个构造函数被创建后,实例对象会继承构造函数的原型属性,这是构造函数的一个非常重要的特性。在Javascript中使用new关键字来对构造函数进行实例化。看下面的例子:

const Car = function(color, model, dateManufactured) {
this.color = color;
this.model = model;
this.dateManufactured = dateManufactured;
};
Car.prototype.getColor = function() {
return this.color;
};
Car.prototype.getModel = function() {
return this.model;
};
Car.prototype.carDate = function() {
return `This ${this.model} was manufactured in the year ${this.dateManufactured}`
}
let firstCar = new Car('red', 'Ferrari', '1985');
console.log(firstCar);
console.log(firstCar.carDate());

上面的例子中,方法getColor,carDate,getModel都是对象(函数)Car的方法,而Car的实例对象firstCar可以继承Car原型上的一切方法和属性。

结论:每一个实例对象都有一个私有属性_proto_,指向它的构造函数的原型对象(prototype)。

原型链

在Javascript中如果访问一个对象本身不存在的属性或是方法,就首先在它的原型对象上去寻找,如果原型对象上也不存在,就继续在原型对象的原型对象上去寻找,直到找到为止。那么原型对象有尽头么?所有对象的原型尽头是Object.prototype,那么Object.prototype这个对象的_proto_指向啥呢?答案是null。我们日常开发中用到的绝大多数对象的_proto_基本不会直接指向Object.prototype,基本都是指向另一个对象。比如所有的函数的_proto_都会指向Function.prototype,所有数组的_proto_都会指向Array.prototype

let protoRabbit = {
color: 'grey',
speak(line) {
console.log(`The ${this.type} rabbit says ${line}`);
}
};
let killerRabbit = Object.create(protoRabbit);
killerRabbit.type = "assassin";
killerRabbit.speak("SKREEEE!");

上面代码中变量protoRabbit设置为所有兔子对象的公有属性对象集,killerRabbit这只兔子通过Object.create方法继承了protoRabbit的所有属性和方法,然后给killerRabbit赋值了一个type属性,再看下面的代码:

let mainObject = {
bar: 2
};
// create an object linked to `anotherObject`
let myObject = Object.create( mainObject );
for (let k in myObject) {
console.log("found: " + k);
}
// found: bar
("bar" in myObject);

如上变量myObject本身并没有bar属性,但这里会循着原型链一层一层往上找,直到找到或者原型链结束为止。如果到原型链尽头还是没找到该属性,那么访问该属性的时候就会返回undefined了。

使用for...in关键字对对象进行迭代的过程,和上面访问某个属性循着原型链查找类似,会去遍历所有原型链上的属性(不论属性是否可枚举)。

let protoRabbit = {
color: 'grey',
speak(line) {
console.log(`The ${this.type} rabbit says ${line}`);
}
};
let killerRabbit = Object.create(protoRabbit);
killerRabbit.type = "assassin";
killerRabbit.speak("SKREEEE!");

上面的代码中访问speak的效率很高,但如果我们想创建很多个Rabbit对象,就必须要重复写很多代码。而这正是原型和构造函数的真正用武之地。

let protoRabbit = function(color, word, type) {
this.color = color;
this.word = word;
this.type = type;
};
protoRabbit.prototype.getColor = function() {
return this.color;
}
protoRabbit.prototype.speak = function() {
console.log(`The ${this.type} rabbit says ${this.word}`);
}
let killerRabbit = new protoRabbit('grey', 'SKREEEEE!', 'assassin');
killerRabbit.speak();

如上代码,使用构造函数的方式就可以节省很多的代码。

结论:每一个实例对象都有一个私有属性_proto_,指向它的构造函数的原型对象(prototype)。原型对象也有自己的_proto_,层层向上直到一个对象的原型对象为null。这一层层原型就是原型链。

附赠一张原型链的图:

创建对象的四种方法

字面量对象

这是比较常用的一种方式:

let obj = {};
构造函数创建

构造函数创建的方式更多用来在Javascript中实现继承,多态,封装等特性。

function Animal(name) {
this.name = name;
}
let cat = new Animal('Tom');
class创建

class关键字是ES6新引入的一个特性,它其实是基于原型和原型链实现的一个语法糖。

class Animal {
constructor(name) {
this.name = name;
}
}
let cat = new Animal('Tom');

扩展原型链的四种方法

构造函数创建

上面例子有用到使用构造函数创建对象的例子,我们再来看一个实际的例子:

function Animal(name) {
this.name = name;
}
Animal.prototype = {
run() {
console.log('跑步');
}
}
let cat = new Animal('Tom');
cat.__proto__ === Animal.prototype; // true
Animal.prototype.__proto__ === Object.prototype; // true

优点:支持目前以及所有可想象到的浏览器(IE5.5都可以使用). 这种方法非常快,非常符合标准,并且充分利用JIST优化。

缺点:为使用此方法,这个问题中的函数必须要被初始化。另外构造函数的初始化,可能会给生成对象带来并不想要的方法和属性。

Object.create

ECMAScript 5 中引入了一个新方法: Object.create()。可以调用这个方法来创建一个新对象。新对象的原型就是调用 create 方法时传入的第一个参数:

var a = {a: 1};
// a ---> Object.prototype ---> null
var b = Object.create(a);
b.__proto__ === a; // true

优点: 支持当前所有非微软版本或者 IE9 以上版本的浏览器。允许一次性地直接设置 __proto__ 属性,以便浏览器能更好地优化对象。同时允许通过 Object.create(null) 来创建一个没有原型的对象。

缺点:不支持 IE8 以下的版本;这个慢对象初始化在使用第二个参数的时候有可能成为一个性能黑洞,因为每个对象的描述符属性都有自己的描述对象。当以对象的格式处理成百上千的对象描述的时候,可能会造成严重的性能问题。

Object.setPrototypeOf

语法:

Object.setPrototypeOf(obj, prototype)

参数:

参数名 含义
obj 要设置其原型的对象。
prototype 该对象的新原型(一个对象 或 null).
var a = { n: 1 };
var b = { m : 2 };
Object.setPrototypeOf(a, b);
a.__proto__ === b; // true

优点:支持所有现代浏览器和微软IE9+浏览器。允许动态操作对象的原型,甚至能强制给通过 Object.create(null) 创建出来的没有原型的对象添加一个原型。

缺点:这个方式表现并不好,应该被弃用;动态设置原型会干扰浏览器对原型的优化;不支持 IE8 及以下的浏览器版本。

_proto_
var a = { n: 1 };
var b = { m : 2 };
a.__proto__ = b;
a.__proto__ === b; // true

使用_proto_也可以动态设置对象的原型。

优点:支持所有现代非微软版本以及 IE11 以上版本的浏览器。将 __proto__ 设置为非对象的值会静默失败,并不会抛出错误。

缺点:应该完全将其抛弃因为这个行为完全不具备性能可言;干扰浏览器对原型的优化;不支持 IE10 及以下的浏览器版本。

以上。


能力有限,水平一般,欢迎勘误,不胜感激。

订阅更多文章可关注公众号「前端进阶学习」,回复「666」,获取一揽子前端技术书籍

理解Javascript的原型和原型链的更多相关文章

  1. 【JavaScript】深入理解JavaScript之强大的原型和原型链

    由于JavaScript是唯一一个被广泛使用的基于原型继承的语言,所以理解两种继承模式的差异是需要一定时间的,今天我们就来了解一下原型和原型链. AD: hasOwnProperty函数: hasOw ...

  2. 深入理解Javascript中构造函数和原型对象的区别

    在 Javascript中prototype属性的详解 这篇文章中,详细介绍了构造函数的缺点以及原型(prototype),原型链(prototype chain),构造函数(constructor) ...

  3. 深入理解Javascript中构造函数和原型对象的区别(转存)

    Object是构造函数,而Object.prototype是构造函数的原型对象.构造函数自身的属性和方法无法被共享,而原型对象的属性和方法可以被所有实例对象所共享. 首先,我们知道,构造函数是生成对象 ...

  4. 理解Javascript的动态语言特性

    原文:理解Javascript的动态语言特性 理解Javascript的动态语言特性 Javascript是一种解释性语言,而并非编译性,它不能编译成二进制文件. 理解动态执行与闭包的概念 动态执行: ...

  5. 深入理解javascript原型和闭包(14)——从【自由变量】到【作用域链】

    先解释一下什么是“自由变量”. 在A作用域中使用的变量x,却没有在A作用域中声明(即在其他作用域中声明的),对于A作用域来说,x就是一个自由变量.如下图 如上程序中,在调用fn()函数时,函数体中第6 ...

  6. <深入理解JavaScript>学习笔记(5)_强大的原型和原型链

    前言 JavaScript 不包含传统的类继承模型,而是使用 prototypal 原型模型. (prototypal :原型.学好英语还是很重要的) 虽然这经常被当作是 JavaScript 的缺点 ...

  7. 深入理解javascript原型链

    在javascript中原型和原型链是一个很神奇的东西,对于大多数人也是最难理解的一部分,掌握原型和原型链的本质是javascript进阶的重要一环.今天我分享一下我对javascript原型和原型链 ...

  8. 深入理解JavaScript系列(5):强大的原型和原型链

    前言 JavaScript 不包含传统的类继承模型,而是使用 prototypal 原型模型. 虽然这经常被当作是 JavaScript 的缺点被提及,其实基于原型的继承模型比传统的类继承还要强大.实 ...

  9. javascript系列--认识并理解构造函数,原型和原型链

    一.前言 介绍构造函数,原型,原型链.比如说经常会被问道:symbol是不是构造函数:constructor属性是否只读:prototype.[[Prototype]]和__proto__的区别:什么 ...

  10. 如何理解JavaScript的原型和原型链

    在现在的业务开发中,应该很少人在写原生JavaScript了,大家都一股脑地扑在各个框架上.本来,这些框架对于业务和开发者来说是一种福音,减少了各种各样的开发痛点,但是带来的负面问题就是对于开发者来说 ...

随机推荐

  1. malloc函数动态分配内存

    #include <stdio.h> #include <stdlib.h> //malloc free #include <windows.h> //sleep ...

  2. Linux終端一行命令发送邮件

    近期由于经常需要给别人发送邮件,每次都要打开QQ邮箱觉得非常麻烦.想到Linux终端可以自定义命令,加上python可以实现邮件发送功能,于是自己写了一个终端send + 文件地址的命令. 首先贴上p ...

  3. C++-POJ2159-Candies[spfa][栈优化][邻接表]

    #include <cstdio> ,N=; struct edge{int v,w,next;}e[M];int head[N],cnt; void add(int u,int v,in ...

  4. Java TreeSet集合 比较器排序Comparator的使用

    比较器排序Comparator的使用 存储学生对象,并遍历,创建TreeSet集合使用带参构造方法 要求,按照学生年龄从小到大排序,如果年龄相同,则按照姓名的字母循序排序 结论 用TreeSet集合存 ...

  5. Windows启动项更改

    笔者遇到的问题: 之前新装了Windows10系统,但没注意到的是竟然是deepin和Windows10双系统,一是用不到deepin系统,二是占用C盘空间太多,就重新装回了Windows7系统,但重 ...

  6. python 百万级别类实例实现节省内存

    # 案例: ''' 某网络游戏中,定义了玩家类Player(id,name,status) 每当有一个玩家,就会在服务器创建一个Player实例 当在线人数过多时,将产生大量实例(百万级别),消耗内存 ...

  7. java基础(三)之面向对象编程

    对象的创建方法 语法: class 类名{ 属性; 方法; } 生成对象的方法 类名 对象名 = new 类名(); Dog dog = new Dog(); 对象的使用方法1.对象.变量;2.对象. ...

  8. MANIFEST.MF详解及配置的注意事项

    一.详解 打开Java的JAR文件我们经常可以看到文件中包含着一个META-INF目录, 这个目录下会有一些文件,其中必有一个MANIFEST.MF,这个文件描述了该Jar文件的很多信息,下面将详细介 ...

  9. ieee-explore文献免费下载办法

    假设文献网址为:http://ieeexplore.ieee.org/document/xxxxxxx/ 下载保存的话,改为http://ieeexplore.ieee.org.sci-hub.tw/ ...

  10. 题解【CJOJ1071/UVA】硬币问题

    P1071 - [Uva]硬币问题 Description 有n种硬币,面值分别为v1, v2, ..., vn,每种都有无限多.给定非负整数S,可以选用多少个硬币,使得面值之和恰好为S?输出硬币数目 ...