放肆青春的博客
首页
前端
算法
网络
面试
技术
后端
运维
杂项
数据库
工具
网址
电脑
个人
文章
  • 分类
  • 标签
  • 归档
github (opens new window)
gitee (opens new window)

放肆青春

一个前端菜鸟的技术成长之路
首页
前端
算法
网络
面试
技术
后端
运维
杂项
数据库
工具
网址
电脑
个人
文章
  • 分类
  • 标签
  • 归档
github (opens new window)
gitee (opens new window)
  • 前端

    • 前端 概览
    • 前端汇总

    • front 博文

    • front 项目总结

    • front 高级

    • front tools

  • vue

    • vue 概览
    • vue 汇总

    • vue 博文

    • vue 项目总结

    • vue 高级

  • html

    • html 概览
    • html 汇总

    • html 博文

  • css

    • css 概览
    • css 汇总

    • css 博文

    • sass

    • less

  • js

    • javascript 概览
    • JS 汇总

    • ES6

    • JS 博文

      • js 基础语法
      • js 数据类型
      • js 字符串
      • js 数组
      • js 对象
      • js 变量
      • js 函数
      • js 事件
      • js 循环
      • js 浅拷贝和深拷贝
      • js 动画
      • js DOM
      • js 防抖节流
      • js 原型及原型链
        • 概念
        • 构造函数
          • Symbol 是构造函数吗
          • constructor 值只读吗
          • 模拟实现 new
        • 原型
          • __proto__ 注意点
        • 原型链
      • js this
      • js 作用域
      • js 继承
      • js 闭包
      • js 内存
      • js垃圾回收
    • JS 工具

  • node

    • node 概览
    • node 汇总

    • node 框架

    • node 博文

  • react

    • react 概览
    • react 汇总

    • react 博文

    • react 高级

  • 微信小程序

    • 微信小程序 概览
    • 微信小程序总结
    • 微信小程序文章
    • 微信小程序 博文

    • 微信小程序 高级

  • 微信公众号

    • 微信公众号 概览
    • 微信公众号总结
    • 微信公众号文章
  • 多端开发

    • 多端开发
    • dsbridge 概览
    • jsbridge 概览
    • webview
    • uniapp

      • uniapp 概览
    • taro

      • taro 概览
    • flutter

      • flutter 概览
      • flutter 环境搭建
    • electron

      • electron 概览
  • front
放肆青春
2021-03-22

js 原型及原型链

# js 原型及原型链

# 概念

对象都是通过函数来创建的

对象是属性的集合

每个函数 function 都有一个 prototype

每个对象都有一个__proto__

每个对象都有一个__proto__属性,指向创建该对象的函数的 prototype

# 构造函数

constructor 返回创建实例对象时构造函数的引用。此属性的值是对函数本身的引用,而不是一个包含函数名称的字符串。

function Parent(age) {
  this.age = age;
}

var p = new Parent(50);
p.constructor === Parent; // true
p.constructor === Object; // false
1
2
3
4
5
6
7

构造函数本身就是一个函数,与普通函数没有任何区别,不过为了规范一般将其首字母大写。构造函数和普通函数的区别在于,使用 new 生成实例的函数就是构造函数,直接调用的就是普通函数。

那是不是意味着普通函数创建的实例没有 constructor 属性呢?不一定。

// 普通函数
function parent2(age) {
  this.age = age;
}
var p2 = parent2(50);
// undefined

// 普通函数
function parent3(age) {
  return {
    age: age,
  };
}
var p3 = parent3(50);
p3.constructor === Object; // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# Symbol 是构造函数吗

Symbol 是基本数据类型,但作为构造函数来说它并不完整,因为它不支持语法 new Symbol(),Chrome 认为其不是构造函数,如果要生成实例直接使用 Symbol() 即可。

虽然是基本数据类型,但 Symbol(123) 实例可以获取 constructor 属性值。

var sym = Symbol(123);
console.log(sym);
// Symbol(123)

console.log(sym.constructor);
// ƒ Symbol() { [native code] }
1
2
3
4
5
6

这里的 constructor 属性来自哪里?其实是 Symbol 原型上的,即 Symbol.prototype.constructor 返回创建实例原型的函数, 默认为 Symbol 函数。

# constructor 值只读吗

引用类型 constructor 属性值是可以修改的,但是对于基本类型来说是只读的,当然 null 和 undefined 没有 constructor 属性。

引用类型情况其值可修改这个很好理解,比如原型链继承方案中,就需要对 constructor 重新赋值进行修正。

function Foo() {
  this.value = 42;
}
Foo.prototype = {
  method: function() {},
};

function Bar() {}

// 设置 Bar 的 prototype 属性为 Foo 的实例对象
Bar.prototype = new Foo();
Bar.prototype.foo = "Hello World";

Bar.prototype.constructor === Object;
// true

// 修正 Bar.prototype.constructor 为 Bar 本身
Bar.prototype.constructor = Bar;

var test = new Bar(); // 创建 Bar 的一个新实例
console.log(test);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

对于基本类型来说是只读的,比如 1、“muyiy”、true、Symbol,当然 null 和 undefined 是没有 constructor 属性的。

function Type() {}
var types = [1, "muyiy", true, Symbol(123)];

for (var i = 0; i < types.length; i++) {
  types[i].constructor = Type;
  types[i] = [
    types[i].constructor,
    types[i] instanceof Type,
    types[i].toString(),
  ];
}

console.log(types.join("\n"));
// function Number() { [native code] }, false, 1
// function String() { [native code] }, false, muyiy
// function Boolean() { [native code] }, false, true
// function Symbol() { [native code] }, false, Symbol(123)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

为什么呢?因为创建他们的是只读的原生构造函数(native constructors),这个例子也说明了依赖一个对象的 constructor 属性并不安全。

# 模拟实现 new

说到这里就要聊聊 new 的实现了,实现代码如下。

function create() {
  // 1、创建一个空的对象
  var obj = new Object(),
    // 2、获得构造函数,同时删除 arguments 中第一个参数
    Con = [].shift.call(arguments);
  // 3、链接到原型,obj 可以访问构造函数原型中的属性
  Object.setPrototypeOf(obj, Con.prototype);
  // 4、绑定 this 实现继承,obj 可以访问到构造函数中的属性
  var ret = Con.apply(obj, arguments);
  // 5、优先返回构造函数返回的对象
  return ret instanceof Object ? ret : obj;
}
1
2
3
4
5
6
7
8
9
10
11
12

# 原型

JavaScript 是一种基于原型的语言 (prototype-based language),这个和 Java 等基于类的语言不一样。

每个对象拥有一个原型对象,对象以其原型为模板,从原型继承方法和属性,这些属性和方法定义在对象的构造器函数的 prototype 属性上,而非对象实例本身。

# __proto__ 注意点

__proto__ 属性在 ES6 时才被标准化,以确保 Web 浏览器的兼容性,但是不推荐使用,除了标准化的原因之外还有性能问题。为了更好的支持,推荐使用 Object.getPrototypeOf()。

通过改变一个对象的 [[Prototype]] 属性来改变和继承属性会对性能造成非常严重的影响,并且性能消耗的时间也不是简单的花费在 obj.proto = ... 语句上, 它还会影响到所有继承自该 [[Prototype]]的对象,如果你关心性能,你就不应该修改一个对象的[[Prototype]]。

如果要读取或修改对象的 [[Prototype]] 属性,建议使用如下方案,但是此时设置对象的 [[Prototype]] 依旧是一个缓慢的操作,如果性能是一个问题,就要避免这种操作。

// 获取
Object.getPrototypeOf();
Reflect.getPrototypeOf();

// 修改
Object.setPrototypeOf();
Reflect.setPrototypeOf();
1
2
3
4
5
6
7

如果要创建一个新对象,同时继承另一个对象的 [[Prototype]] ,推荐使用 Object.create()。

function Parent() {
  age: 50;
}
var p = new Parent();
var child = Object.create(p);
1
2
3
4
5

这里 child 是一个新的空对象,有一个指向对象 p 的指针__proto__

# 原型链

更新时间: 9/30/2021, 11:08:49 AM
js 防抖节流
js this

← js 防抖节流 js this→

最近更新
01
前端权限管理
02-24
02
vue2指令
02-24
03
vue2 hook
02-24
更多文章>
Theme by Vdoing | Copyright © 2019-2022 放肆青春
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式