一. 写在前面
- 本次内容全部是js的,没有ts内容,学习这些内容是为了之后更好的学习ts,如果不学习这些内容可能会导致之后ts的学习有地方难以理解
二. 面向对象之原型
- 假设我需要用js造一个小兵,以下是基础代码
js
// JavaScript 造一个小兵
const 近战兵 = {
兵种:"近战",
血量:1488,
物理攻击力:60,
护甲:180,
金钱:42,
补刀奖励:16,
出生:function (){/*出生动画*/},
死亡:function (){/*死亡动画*/},
攻击:function (){/*攻击特效*/ },
行走:function (){/*行走动画*/ },
}
兵营(近战兵)
- 需求: 造100个
- 有两种方式,第一种写100遍,这种方式明显很不合理
js
const 近战兵1={/*略*/}
const 近战兵2={/*略*/}
/* ... */
const 近战兵100={/*略*/}
兵营(近战兵1, 近战兵2, ···, 近战兵100)
- 第二种方式,使用循环,但是这种方式还有问题,太浪费内存了
js
const list = []
for (let i=0; i<100; i++) {
list.push({
id: i,
兵种: '近战',
血量: 1488,
物理攻击力: 60,
护甲: 180,
金钱: 42,
补刀奖励: 16,
出生: function(){/*出生动画*/},
死亡: function(){/*死亡动画*/},
攻击: function(){/*攻击特效*/},
行走: function(){/行走动画*/},
})
}
兵营(...list)
- 我们通过分析,可以发现,金钱,补刀奖励,以及四个函数是一样的,共用的,我们期望实现以下效果
js
{
id: 1,
血量: 1488,
物理攻击力: 60,
护甲: 180,
more: 近战兵公有属性
}
{
id: 2,
血量: 1488,
物理攻击力: 60,
护甲: 180,
more: 近战兵公有属性
}
近战兵公有属性 = {
兵种: '近战',
金钱: 42,
补刀奖励: 16,
出生: function(){/*出生动画*/},
死亡: function(){/*死亡动画*/},
攻击: function(){/*攻击特效*/},
行走: function(){/*行走动画*/},
}
- 我们通过下图来体会一下这样做的好处,从1100个属性降到507个属性,大大减少了内存,我们的核心思想就是把所有的属性分成独有属性和共有属性

- 经过上面这个步骤之后,我们可以得到造100个士兵,同时内存尽量少的方法
js
const 近战兵公有属性 = {
兵种: '近战',
金钱: 42,
补刀奖励: 16,
出生: function(){/*出生动画*/},
死亡: function(){/*死亡动画*/},
攻击: function(){/*攻击特效*/},
行走: function(){/*行走动画*/},
}
for (let i=0; i<100; i++) {
const 近战兵 = {
id: i,
血量: 1488,
物理攻击力: 60,
护甲: 180,
}
近战兵.__proto__ = 近战兵公有属性 // 隐藏属性指向近战兵公有属性
list.push(近战兵)
}
兵营(...list)
三. 再次去优化代码
- 目前代码还存在的问题: 代码过于分散,分成了两部分代码去创建士兵,我们可以这样去优化
js
// soldier.js
export const 创建近战兵 = function(id) {
const 近战兵 = {
id: i,
血量: 1488,
物理攻击力: 60,
护甲: 180,
}
近战兵.__proto__ = 近战兵公有属性
return 近战兵
}
const 近战兵公有属性 = {
兵种: '近战',
金钱: 42,
补刀奖励: 16,
出生: function(){/*出生动画*/},
死亡: function(){/*死亡动画*/},
攻击: function(){/*攻击特效*/},
行走: function(){/*行走动画*/},
}
// main.js
import {创建近战兵} from './soldier'
const list = []
for (let i=0; i<100; i++) {
list.push(创建近战兵(i))
}
兵营(...list)
- 目前还存在的缺点,创建近战兵和近战兵公有属性不够内聚,这两段代码非常互相依赖,但是仅仅靠
近战兵.__proto__ = 近战兵公有属性
去关联- 这里解释一下什么是高内聚低耦合,所谓的高内聚就是该在一起的代码不要分开,低耦合就是能分开的代码尽量不要放在一起
js
// 进一步优化代码,让代码内聚,这样子之后,这两段代码彼此无法分开
export const 创建近战兵 = function(id) {
const 近战兵 = {
id: i,
血量: 1488,
物理攻击力: 60,
护甲: 180,
}
近战兵.__proto__ = 创建近战兵.prototype
return 近战兵
}
const 创建近战兵.prototype = {
constructor: 创建近战兵
兵种: '近战',
金钱: 42,
补刀奖励: 16,
出生: function(){/*出生动画*/},
死亡: function(){/*死亡动画*/},
攻击: function(){/*攻击特效*/},
行走: function(){/*行走动画*/},
}
- 以上这段代码非常经典,于是js之父决定把这段代码推广给每一个写js的程序员
js
// 进一步优化代码,让代码内聚,这样子之后,这两段代码彼此无法分开
export const 创建近战兵 = function(id) {
const 近战兵 = {
id: i,
血量: 1488,
物理攻击力: 60,
护甲: 180,
} // const 近战兵,这句不需要写,肯定会创建一个对象,但是要把独有属性写清楚
近战兵.__proto__ = 创建近战兵.prototype // 这句不需要写,每个对象都肯定要加
return 近战兵 // 不需要写,每一个构造函数肯定要return一个对象
}
const 创建近战兵.prototype = { // 这句固定下来就叫prototype
constructor: 创建近战兵 // 这句不需要写,自动加一个constructor,指向上面函数,这样就做到了高内聚
兵种: '近战',
金钱: 42,
补刀奖励: 16,
出生: function(){/*出生动画*/},
死亡: function(){/*死亡动画*/},
攻击: function(){/*攻击特效*/},
行走: function(){/*行走动画*/},
}
- 其实剩下的代码只剩下独有属性和共有属性
js
// soldier.js
export const 近战兵 = function(id) {
this.id = id
this.血量 = 1488
this.物理攻击力 = 60
this.护甲 = 180
}
近战兵.prototype.兵种 = '近战'
近战兵.prototype.金钱 = 42
近战兵.prototype.补刀奖励 = 16
近战兵.prototype.出生 = function(){/*出生动画*/}
近战兵.prototype.死亡 = function(){/*死亡动画*/}
近战兵.prototype.攻击 = function(){/*攻击特效*/}
近战兵.prototype.行走 = function(){/*行走动画*/}
// main.js
import {近战兵} from './soldier'
const list = []
for (let i=0; i<100; i++) {
list.push(new 近战兵(i)) // 注意这里的new
}
- 这段代码还能优化吗?通过浅拷贝实现优化
js
// soldier.js
export const 近战兵 = function(id) {
copy(this, { id: id, 血量: 1488, 物理攻击力: 60, 护甲: 180})
}
近战兵.prototype = {
constructor: 近战兵, // 这样写会把constructor覆盖,因此这里需要加回来
兵种: '近战'
金钱 : 42
补刀奖励: 16
出生 : function(){/*出生动画*/}
死亡 : function(){/*死亡动画*/}
攻击 : function(){/*攻击特效*/}
行走 : function(){/*行走动画*/}
}
四. new做了哪些事
- 创建一个完全空的对象,放到this上面
this = {}
- 把这个对象的隐藏属性指向公有属性
this.__proto__ = 近战兵.prototypes
- 返回this,
return this
近战兵.protoType
这件事情,包括里面的constructor: 近战兵
都是js做的,并非new去做的- 当在浏览器里面声明一个非箭头函数然后通过console.dir,即可发现自带prototype和constructor

五. 总结: JS如何创建对象
- 以new为语法糖
js
var a = {} // 这只是简写
var a = new Object() // 实际上是这样写
// 同理
var array = []
var array = new Array()
- 用构造函数给对象添加独有尾性
- 用构造函数的prototype容纳共有隐性
- 使用属性查找规则 1. 读取obj的'x'属性时,先看obj的独有属性有没有x 2. 再看obj的共有属性有没有x 3. 再去看obj的共有属性的共有属性有没有x 4. 直到共有属性为
null
,则认为obj.x不存在
六. js如何知道共有属性有哪些
- 以前用的是
obj.__proto__
这种形式 - 现在用
obj.[[prototype]]
七. 一些面试题
- 什么原型?
- 第一题
js
const obj = new 近战兵(i)
// 请问obj的原型是,答案: 1 和 2,他们是相等的
// 1. 近战兵.prototype
// 2. obj.__proto__ 或者 obj.[[prototype]]
- 第二题
js
const fn = function() {}
// 函数比较特殊会有一个prototype属性
// 请问fn的原型是? 2 和 3,这里的关键点是要把fn当作一个obj,1是把fn当作函数去解释,每一个非箭头函数都有一个prototype
// 1. fn.prototype
// 2. fn.__proto__
// 3. Function.prototype
- 第三题
js
// Function的原型是什么?
// Function.prototype不是它的原型,是这个函数构造出来的对象的原型
Function.__proto__ === Function.prototype // true,函数的原型是其共有的属性
// Function的构造函数是什么? 是它本身
Function.prototype.constructor === Function // true
// 1. 浏览器构造了Function
// 2. 浏览器卸了代码 Function.constructor = Function
- 总结: 原型就是指一个对象的共有属性所在地
八. 画出js世界
九. 相关文章
- new相关: zhuanlan.zhihu.com/p/23987456?...
- protoType相关: www.zhihu.com/question/56...