面向对象Object Oriented

收益

结构化

什么是

把相关的数据组合在一个结构中,从而方便访问

案例
1 2 3
Player player = ...;player.atk = 10;player.def = 10;
JavaScript 中表达

使用 Object 就可以轻松做到

1 2 3 4 5 6
const player = { atk: 0, def: 0};player.atk = 10;player.def = 10;

封装

什么是

将部分资源进行隐藏

案例
1 2 3!
Player player = ...;player.setAtk(10);player.atk = 10;
JavaScript 中表达

可以使用 Closure 进行模拟

1 2 3 4 5 6 7 8 9 10 11 12
function Player () { let _atk = 0; function setAtk(atk) { _atk = atk; } return { setAtk }}const player = Player()player.setAtk(10);player.atk = 10;

基于对象

什么是

方法操作可以基于对象的属性

案例
1 2 3 4 5 6
public class Player { private int atk; public int getAtk() { return this.atk; }}
JavaScript 中表达

可以使用上面的 closure 进行模拟

模型

什么是

构建好一套资源后,可以根据这个模板生成具体实例

案例
1 2 3 4 5 6
public class Player { private int atk; public int getAtk() { return this.atk; }}
JavaScript 中表达

可以使用上面的 closure 进行模拟

this

this 变量

干什么

函数调用时,如果使用 对象引导,则会传输为 this 变量

案例
1 2 3 4 5 6 7
const player = { name: "赵哲", print: function() { console.log(this.name); }}player.print();

函数 挂接/脱离 对象 对 this 的影响

案例 1
1 2 3 4 5 6 7 8
function print() { console.log(this.name);}const player = { name: "赵哲", print}player.print();
案例 2
1 2 3 4 5 6 7 8
function print() { console.log(this.name);}const player = { name: "赵哲", print}print();
案例 3
1 2 3 4 5 6 7 8 9
function print() { console.log(this.name);}const player = { name: "赵哲", print}const print2 = player.print;print2();

call 和 bind

使用 call 动态调用

调用无参函数
1 2 3 4 5
function print() { console.log("dynamic called");}print();print.call();
调用带参函数
1 2 3 4 5
function add(v1, v2) { return v1 + v2;}const result = add.call(null, 3, 4);console.log(result);
调用时动态绑定 this
1 2 3 4 5 6 7
function print() { console.log(this.name);}const player = { name: "赵哲"}print.call(player);

使用 bind 绑定 this

案例 1
1 2 3 4 5 6 7
function print() { console.log(this.name);}const player = { name: "赵哲"}print();
案例 2
1 2 3 4 5 6 7 8
function print() { console.log(this.name);}const player = { name: "赵哲"}const boundPrint = print.bind(player);boundPrint();

原型链机制Prototype Chain

背景

模板效果

类 与 对象

父类 与 子类

Object.create()

1 2 3 4 5 6
const obj1 = { a: 1};const obj2 = Object.create(obj1);console.log(obj2);console.log(obj2.a);
原型链

使用 Object.create() 可以根据现有对象作为模板,复制出一个新的对象

旧对象 叫做 新对象 的 原型 Prototype

obj1 -> a: 1
|
obj2

__proto__

查找原型

使用 Object.create() 创建新对象时,新对象会保留一个指向旧对象的引用

使用 obj.__proto__ 可以查找 obj 对象的原型是谁

1 2 3 4 5
const obj1 = { a: 1};const obj2 = Object.create(obj1);console.log(obj2.__proto__ === obj1);
指定原型
1 2 3 4 5 6 7
const obj1 = { a: 1};const obj2 = { __proto__: obj1};console.log(obj2.a);

属性访问实质

规则

每次访问时,系统会查找在原型链上最近的属性,并返回

如果新对象没有属性,系统会通过 __proto__ 在原型上寻找属性

案例
1 2 3 4 5 6
const obj1 = { a: 1};const obj2 = Object.create(obj1);const obj3 = Object.create(obj2);console.log(obj3.a);
原型链
obj1 -> a: 1
|
obj2
|
obj3

属性赋值与删除

属性赋值实质

规则

只会对当前操作对象 添加 / 设置 属性值

不会影响到原型对象

案例
1 2 3 4 5 6 7 8 9
const obj1 = { a: 1};const obj2 = Object.create(obj1);obj2.b = 2;console.log(obj1.a);console.log(obj1.b);console.log(obj2.a);console.log(obj2.b);
原型链
obj1 -> a: 1
|
obj2 -> b: 2

属性覆盖

规则

在新对象上 对原型对象同名属性 进行赋值,并不会修改 原型属性

而是在新对象上设置一个跟父级属性同名的属性,从而形成属性覆盖

访问时,采用就近原则

案例
1 2 3 4 5 6 7 8
const obj1 = { a: 1};const obj2 = Object.create(obj1);obj2.a = 2;console.log(obj1.a);console.log(obj2.a);console.log(obj2.__proto__.a);
原型链
obj1 -> a: 1
|
obj2 -> a: 2
注意

因为 js 中函数可以存在变量里,所以属性也可能是函数

可以使用这个机制达到 方法覆盖的效果

删除属性

规则

可以使用 delete 关键字,对属性进行删除

删除时,不会影响原型对象

案例
1 2 3 4 5 6 7 8
const obj1 = { a: 1};const obj2 = Object.create(obj1);obj2.a = 2;delete obj2.a;delete obj2.a;console.log(obj2.a);
原型链
after line 5
obj1 -> a: 1
|
obj2 -> a: 2

after line 6
obj1 -> a: 1
|
obj2 

after line 7
obj1 -> a: 1
|
obj2 

属性归属区分

hasOwnProperty()

查询当前对象自身是否拥有属性

1 2 3 4 5 6 7
const obj1 = { a: 1};const obj2 = Object.create(obj1);obj2.b = 2;console.log(obj2.hasOwnProperty("a"));console.log(obj2.hasOwnProperty("a"));

Object.keys()

调出来所有当前对象自身拥有的属性名

1 2 3 4 5 6
const obj1 = { a: 1};const obj2 = Object.create(obj1);obj2.b = 2;console.log(Object.keys(obj2));

in 语句

查询当前对象及原型链上是否拥有属性

1 2 3 4 5 6 7
const obj1 = { a: 1};const obj2 = Object.create(obj1);obj2.b = 2;console.log("a" in obj2);console.log("b" in obj2);

for in 语句

循环遍历当前对象 和原型链上 所有的属性。

重名属性,只会出现一次

1 2 3 4 5 6 7 8 9 10
const obj1 = { a: 1};const obj2 = Object.create(obj1);obj2.a = 2;obj2.b = 2; for (const key in obj2) { console.log(key + " -> " + obj2[key]);}

旧版面向对象

使用原型链模拟

思路

使用 原型对象 表达 类,放方法

使用 子对象 表达 对象,放属性

代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
const Point = { trace: function() { console.log(`(${this.x}, ${this.y})`); }} const p1 = Object.create(Point);p1.x = 1;p1.y = 1;const p2 = Object.create(Point);p2.x = 3;p2.y = 4; p1.trace();p2.trace();
原型链
Point -> trace: func 
|
+   p1 -> x: 1, y: 1
+   p2 -> x: 3, y: 4

使用 new 与 prototype

三元素

构造方法、类、对象

代码
1 2 3 4 5 6 7 8 9 10 11 12 13
function Point(x, y) { this.x = x; this.y = y;} Point.prototype.trace = function() { console.log(`(${this.x}, ${this.y})`);} const p1 = new Point(1, 1);const p2 = new Point(3, 4);p1.trace();p2.trace();
new
1
const p1 = new Point(3, 4);

等效于

1 2 3
const obj = Object.create(Point.prototype);obj.Point(3, 4);const p1 = obj
三元素关系

ES 2015 面向对象

基础结构

新语法可以使用 class 去描述一个类

属性写在 构造方法里

方法 不用 function 关键字

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
class Point { constructor(x, y) { this.x = x; this.y = y; } trace() { console.log(`(${this.x}, ${this.y})`); }} const p1 = new Point(1, 1);const p2 = new Point(3, 4);p1.trace();p2.trace();

与旧版的共同性

虽然使用了新的语法,但实际内部的机制 跟 旧版 一样

依然存在 三元素

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
class Point { constructor(x, y) { this.x = x; this.y = y; } trace() { console.log(`(${this.x}, ${this.y})`); }} const p1 = new Point(1, 1);const p2 = new Point(3, 4);console.log(p1.__proto__.hasOwnProperty("trace"));console.log(Point.prototype.hasOwnProperty("trace"));
原型链
Point.prototype -> trace();
|
+   p1 -> x: 1, y: 1
+   p2 -> x: 3, y: 4

原型链在 js 中 表达 类 和 对象

继承

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
class Point { constructor(x, y) { this.x = x; this.y = y; } trace() { console.log(`(${this.x}, ${this.y})`); }} class Point3D extends Point { constructor(x, y, z) { super(x, y); this.z = z; } trace() { console.log(`(${this.x}, ${this.y}, ${this.z})`); }} const p1 = new Point3D(1, 2, 3);p1.trace();
原型链
Point.prototype -> trace();
|
Point3D.prototype -> trace();
|
p1 -> x: 1, y: 2, z: 3

原型链在 js 中 表达 父类 和 子类

接口 与 抽象类

接口

不支持接口,因为接口 是 调用保障,类型检查使用,而 js 没有编译时类型检查机制

抽象类

不支持抽象类

静态资源

也支持 static 关键字声明 静态属性 和 静态方法

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
class Point { constructor(x, y) { this.x = x; this.y = y; } trace() { console.log(`(${this.x}, ${this.y})`); } static zero = new Point(0, 0); static ofSize(size) { return new Point(size, size); }} Point.zero.trace();Point.ofSize(3).trace();

class 与 module

使用 default export 类

folder
Point.js
main.js

Point.js

1 2 3 4 5 6 7 8 9 10
class Point { constructor(x, y) { this.x = x; this.y = y; } trace() { console.log(`(${this.x}, ${this.y})`); }}export default Point;

main.js

1 2 3 4
import Point from "./Point" const p1 = new Point(3, 4);p1.trace();

使用 module 作为 namespace

folder
Graph.js
main.js

Graph.js

1 2 3 4 5 6 7 8 9
export class Point { constructor(x, y) { this.x = x; this.y = y; } trace() { console.log(`(${this.x}, ${this.y})`); }}

main.js

1 2 3 4
import { Point } from "./Graph" const p1 = new Point(3, 4);p1.trace();

class 与 module

如果一个类 全是静态属性和资源,我们可以不使用 面向对象 的 class

转而使用 基础的 module 和 function

folder
Dice.js
main.js

Dice.js

1 2 3 4 5 6
export function roll(amount) { return Math.floor(Math.random() * amount);}export function roll6() { return roll(6);}

main.js

1 2 3
import Dice from "./Dice" console.log(Dice.roll6());

ZZAX 微信公众

文档一更新,立刻告诉你