notes
  • Introduction
  • 语言
    • JS
      • JS原型到原型链
      • JS继承的实现
      • this对象
      • Promise基本概念
      • Promise实现
      • Promise实战
      • JS的EventLoop
      • JS容易出现误区的运算符
      • JS容易出现误区的操作符
      • JS深拷贝
      • JS节流与防抖
      • ES5实现原生/ES6方法
    • TS
      • 声明文件
      • 项目配置
    • CSS
      • CSS引入方式以及优先级计算
      • BFC神奇背后的原理-文摘
      • 回流reflow与重绘repaint
      • 三栏式布局
      • 垂直居中
      • 清除浮动的方法
      • 移动端适配方案
      • 纯CSS斜切角实现
      • CSS揭秘
      • 背景图片轮播
      • CSS绘制序列帧动画
      • transform实现一个多面体
    • HTML
  • ES6
  • Node.js
    • xxx
  • 前端框架
    • Vue.js
      • Vue双向数据绑定原理
      • Vue-Router原理及实现
    • React
    • AngularJS
  • 工程构建
    • Webpack
      • 01-webpack概述及背景
      • 03-webpack热更新原理
      • 04-splitChunks原理分析
      • 05-webpack工作流程
      • 07-webpack构建流程
      • 07-webpack构建流程
      • 10-webpack 如何进行性能分析
    • Gulp
    • Lint
      • ESLint
      • TSLint
    • Bable
  • 工程化
    • 模块化
    • 组件化
    • 规范化
      • 编码规范
    • 自动化
  • 运维
    • Nginx
    • GIT
    • CDN
    • VPS
    • DBA
  • 小程序
  • 跨端
  • 测试
  • 计算机技术
    • 计算机网络
      • 同源策略到前端跨域解决方案
      • TCP数据传输
      • TCP和UDP的区别
      • HTTP协议概述
      • HTTP缓存详解
      • HTTPS以及SSL建立过程
      • HTTPS的七个误解(译文)
      • cookie与Session机制区别以及Cookie SessionStorage LocalStorage异同
      • HTTP状态码Status
      • DNS原理入门
      • 网络攻击与防御
      • HTTP转发与重定向
      • 登录认证设计
    • 操作系统
      • shell脚本命令
    • Linux
      • Linux命令大全
      • vim命令
    • 浏览器
      • 浏览器内核、JS 引擎、页面呈现原理及其优化
      • 九种浏览器端缓存机制概览
      • 性能优化-网络请求方面
      • webkit解析CSS过程详解
  • 前端面试
    • 算法
Powered by GitBook
On this page
  • 原型
  • 构造函数
  • 原型对象(实例原型)
  • 实例
  • 原型链
  • 原型链查找
  • 原型操作
  • 查看原型
  • 修改原型
  • 参考
  1. 语言
  2. JS

JS原型到原型链

PreviousJSNextJS继承的实现

Last updated 3 years ago

简单点用一句话描述,理清楚实例、构造函数、原型对象(也称实例原型) 三者的关系即可。他们之间关系如图:

下面我们来具体聊一聊

原型

构造函数

通俗一点理解,生成对象的基础,并描述对象的基本结构的函数。

与普通函数典型区别

  • 需要使用new关键字生成对象实例才可以使用。

  • 构造函数内this指向生成的对象实例。

  • 命令上一般使用首字母大写。

示例:

var Person = function() {
    this.name = 'aoao';
};
//两种写法相同。
function Person() {
    this.name = 'aoao';
}

我们用到的Array,String等内置对象都是构造函数。

原型对象(实例原型)

构造函数的 prototype 属性指向了一个对象,这个对象正是调用该构造函数而创建的实例的原型,也称为原型对象。关系如下:

同时,每个原型都有一个 constructor 属性指向关联的构造函数。

为了验证这一点,我们可以尝试:

function Person() {
}
console.log(Person === Person.prototype.constructor); // true

实例

实例是通过 new 一个构造函数创建的,称之为实例对象。

实例有一个有一个私有属性,叫__proto__,这个属性会指向该对象的原型对象(prototype)。关系图如下:

为了证明这一点,我们测试:

function Person() {

}
var person = new Person();
console.log(person.__proto__ === Person.prototype); // true

最终我们得到关于篇首关于这三者的关系图:

既然实例对象和构造函数都可以指向原型,那么原型对象是否有属性指向构造函数或者实例呢?

原型链

上面我们知道,实例对象有一个私有属性__proto__指向它的构造函数的原型对象prototype,那么原型对象也有一个私有属性__proto__指向自己的原型对象,即该原型的原型,如此下去,就是所谓的原型链(prototype chain)。

上面图中看到Person原型对象的原型是Object.prototype,这是因为所有的引用类型默认都继承了Object,这个继承也是通过原型链实现的,因此默认原型都会包含一个内部指针,指向Object.prototype。

而Object.prototype的原型是原型链的结尾,指向了null。

我们可以验证一下

function Person() {
}
var person = new Person();
console.log(person.__proto__ === Person.prototype); // true
console.log(person.__proto__.__proto__ === Object.prototype); // true
console.log(person.__proto__.__proto__.__proto__); // null

原型链查找

我们在读取实例对象属性的时候,要注意搜索顺序,

归纳一下查找顺利就是:

  1. 先在实例对象上查看

  2. 再在原型对象上查找

  3. 沿着原型链上一直往上查找,没有的话返回undefined.

我们可以看个例子

function Person() {
}
Person.prototype.name = 'Nicholas';
var person1 = new Person();
var person2 = new Person();

person1.name='Mack';
console.log(person1.name) // Mack
//此时执行两次搜索
console.log(person2.name) // Nicholas

原型操作

查看原型

Object.getPrototypeOf

该方法返回指定对象的原型(也就是该对象内部属性[Prototype]的值)。

console.log(Object.getPrototypeOf({}))
//Object.prototype

Object.prototype.__proto__

一个对象的__proto__ 属性和自己的内部属性[Prototype]指向一个相同的值 (通常称这个值为原型),原型的值可以是一个对象值也可以是null(比如说Object.prototype.__proto__的值就是null)。

此方法已经从 Web 标准中删除, 尽量少用, 推荐使用Object.getProtoTypeOf

({}).__proto__
Object.prototype

// 这两者的结果是一样的,都是实例原型

修改原型

直接在prototype增删改

修改构造函数的prototype属性从而达到操作对象原型的目的。

Person上原来并没有name和add

function Person () {}
Person.prototype.name = 'Nike';
Person.prototype.add = function(name) {
    this.name = name;
}

const person = new Person();
console.log(person.name); // Nike;

警告: 由于现代 JavaScript 引擎优化属性访问所带来的特性的关系,更改对象的 [[Prototype]]在各个浏览器和 JavaScript 引擎上都是一个很慢的操作。其在更改继承的性能上的影响是微妙而又广泛的,这不仅仅限于 obj.proto = ... 语句上的时间花费,而且可能会延伸到任何代码,那些可以访问任何[[Prototype]]已被更改的对象的代码。如果你关心性能,你应该避免设置一个对象的 [[Prototype]]。相反,你应该使用 Object.create()来创建带有你想要的[[Prototype]]的新对象。

Object.setPrototypeOf

目前推荐使用

var a = {};
var b = {};

Object.setPrototypeOf(a, b);
Object.getPrototypeOf(a) === b;
// true
// a ---> b

__proto__

ES6还带来了一个属性,通过这个属性也可以直接操作原型

var a = {};
var b = {};

a.__proto__ = b;
Object.getPrototypeOf(a) === b;
// true
// a ---> b

注意这个属性在ES6规范的附录中,也就意味着不是所有的环境都会有这个属性。

class的extends

ES6引入了以class语法糖,通过extends关键字我们也可以实现继承,但是无法直接操作对象的原型,而是要借助“类”,其实就是构造函数和函数的prototype属性。

class B {}

class A extends B {}

var a = new A();

Object.getPrototypeOf(a) === A.prototype;
// true
// a ---> A.prototype === B的实例

参考

  • 《高程》

这个方法是目前主流的方法,在查看setPrototypeOf时发现MDN官网已经将直接修改prototype标记为。

警告
继承与原型链
Object.setPrototypeOf()
Object.prototype.proto
JavaScript深入之从原型到原型链
详解JavaScript中的原型和继承
全面理解面向对象的 JavaScript