JavaScript之原型和原型链

51 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第5天,点击查看活动详情

前言

JavaScript在设计之初只是一个提供给非专业的开发人员使用的工具,所以为了尽可能的让这个工具简单易学,并没有直接引入的概念,而是借鉴了Self 和 Smalltalk这两门基于原型的语言。

因此,原型和原型链成为 JavaScript这门语言最大的一个特点。

概述

  • 所有的函数都是对象
    • 函数中可以拥有自定义属性
  • 所有的对象都是通过 new 函数创建的

原型链.png

prototype

  • 所有函数都有一个属性:prototype,叫做函数原型
  • 默认情况下,prototype是一个普通的Object对象
  • 默认情况下,prototype中有一个属性,constructor,它也是一个对象,它指向构造函数本身

代码示例:

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

图示:

原型中的constructor指向函数本身.jpg

__proto__

已弃用:  不再推荐使用该特性 --- MDN

该特性可以通过 `Object.prototype` 身上的一些方法实现,这里记录一下,便于理解原型及原型链的概念。
  • 所有对象都有一个属性:__proto__,叫做:隐式原型
  • 默认情况下,对象的__proto__属性指向创建该对象函数原型对象

代码示例:

function Person() {}

const p = new Person();

console.log(p.__proto__); // {constructor: f}
console.log(p.__proto__.constructor === Person); // true

// 实例 p 的 __proto__ 属性 等于 构造函数 Person 的 prototype(函数原型对象) 
console.log(p.__proto__ === Person.prototype); // true

图例:

隐式原型的指向.jpg

原型链

当访问对象的某个属性时:

  • 先查看对象本身是否存在该属性,存在则直接使用
  • 对象本身不存在,则依次查找对象的原型上是否存在该属性
  • 若对象的原型都不存在该属性,则报错

注:这里的依次查看对象原型,指的是顺着对象的原型对象一直找,并不是只查找[对象].proptotype或者[对象].__proto__(看代码和图例会比较好理解)

function Person() {}

Person.prototype.say = function () { 
  console.log('Hello World!');
}

// 在 Function 的原型上定义 func 方法,根据原型链全貌,访问对象 p 时是不会访问到 Function.prototype 的
Function.prototype.func = function() {
  console.log('Function prototype func');
}

const p = new Person();

console.log(p); // Person {}

// 访问 say 函数时:
// 先查找 p 自身,不存在 say 方法
// 再查看 p.__proto__,即:Person.prototype, 存在 say 方法,执行
p.say(); // Hello World!

// 访问 func 函数时:
// 先查找 p 自身,不存在 func 方法
// 再查看 p.__proto__,即:Person.prototype, 不存在 func 方法
// 继续查找 p.__proto__.__proto__,即 Object.prototype,不存在 func 方法
// 再查找 p.__proto__.__proto__.__proto__ , 为 null,依旧找不到 func 方法,报错
p.func(); // p.func is not a function

原型链全貌:

链条的全貌.jpg

测试题:

function User() {}
User.prototype.sayHello = function() {}

var u1 = new User();
var u2 = new User();

console.log('1. ', User.prototype.constructor); // 1. User {}

console.log('2. ', u1.sayHello === u2.sayHello);  // 2. true

// Object.prototype === Object.prototype  true
console.log('3. ', Function.prototype.__proto__ === Object.prototype); // 3. true

// Function.prototype === Function.prototype  true
console.log('4. ', User.__proto__ === Function.__proto__); // 4. true

// User.prototype === Object.prototype   false
console.log('5. ', u1.__proto__ === User.__proto__); // 5. false

// Function.prototype === Function.prototype true
console.log('6. ', User.__proto__ === Function.prototype); // 6. true

// User.prototype === Function.prototype false
console.log('7. ', User.prototype === Function.prototype); // 7. false 

// User.prototype === User.prototype true
console.log('8. ', u1.__proto__ === u2.__proto__); // 8. true

// Object.prototype === null false
console.log('9. ', Function.prototype.__proto__ === Object.prototype.__proto__);  // 9. false

// Function.prototype === Function.prototype true
console.log('10. ', Function.__proto__ === Object.__proto__); // 10. true 

原型及原型链相关知识

  1. Object.setPrototypeOf() 方法设置一个指定的对象的原型(即,内部 [[Prototype]] 属性)到另一个对象或 null。可以替代 [object].__proto__ = xxx

建议使用 Object.create 创建一个新对象,而不是直接修改某个对象的原型

如果你担心性能问题,则应该避免设置对象的 [[Prototype]]。相反,你应该使用Object.create()来创建带有你想要的 [[Prototype]] 的新对象。 --- MDN

  1. Object.create() 方法用于创建一个新对象,使用现有的对象来作为新创建对象的原型

  2. Object.getPrototypeOf()  方法返回指定对象的原型(内部[[Prototype]]属性的值),可以替代 [object].__proto__

原型链的应用

js实现继承