前言

本文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. rest_framework:url控制

    一.基本路由(原始方式) 二.半自动路由(视图继承ModelViewSet) 三.自动路由(自动生成路由) 准备工作: models中创建一张表,默认使用sqlite数据库,更新表 新增测试数据 加载 ...

  2. 【Python】无限循环

  3. 基于Android的在线播放器系统的设计与实现

    文章结构: 1 引言 1.1系统的研究背景 现在的时代是互联网的时代,互联网高速发展的同时,无线网络也接入了互联网.社会的各个领域都已经被无线网络渗透.小的比如手机,电脑,电视.大的比如灯光系统,智能 ...

  4. python3 yield实现假的多并发

    import time def fun1(): while True: print("fun1") time.sleep(0.1) yield def fun2(): while ...

  5. python之pandas简介

    一. Pandas简介 1.Python Data Analysis Library 或 pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的.Pandas 纳入了大量库和 ...

  6. Python - 反向遍历序列(列表、字符串、元组等)的五种方式

    1. reversed() a = [1, 2, 3, 4] for i in reversed(a): print(i) 2. range(len(a)-1, -1, -1) a = [1, 2, ...

  7. Centos7 安装 GitLab 代码管理服务器

    一.安装依赖 yum -y install policycoreutils openssh-server openssh-clients postfix 二.启动postfix,并设置开机自启动 sy ...

  8. html5 标准文档结构

    <!DOCTYPE html> <html lang="zh-cmn-Hans"> <head> <meta charset=" ...

  9. 结合字符串常量池/String.intern()/String Table来谈一下你对java中String的理解

    1.字符串常量池 每创建一个字符串常量,JVM会首先检查字符串常量池,如果字符串已经在常量池中存在,那么就返回常量池中的实例引用.如果字符串不在池中,就会实例化一个字符串放到字符串池中.常量池提高了J ...

  10. 「模板」Splay

    代码说明 对于一些变量进行说明: 变量名 说明 rt 树根 ff[u] 点 \(u\) 的父节点,特别地, ff[rt]=0 ch[u][0|1] 点 \(u\) 的 左/右儿子 siz[u] 点 \ ...