手写JavaScript

本文最后更新于 2025年7月30日 下午

创建对象

工厂模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function CreatePerson1(name, age, job) {
let o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function () {
console.log(this.name);
};
return o;
}

let person1 = CreatePerson1("Nich", 25, "Teacher");
let person2 = CreatePerson1("Greg", 29, "Doctor");
person1.sayName(); // "Nich"
person2.sayName(); // "Greg"

构造函数模式

1
2
3
4
5
6
7
8
9
10
11
12
13
function CreatePerson2(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function () {
console.log(this.name);
};
}

let person1 = new CreatePerson2("Nich", 25, "Teacher");
let person2 = new CreatePerson2("Greg", 29, "Doctor");
person1.sayName(); // "Nich"
person2.sayName(); // "Greg"

原型模式

1
2
3
4
5
6
7
8
9
10
11
12
function CreatePerson3() {}
CreatePerson3.prototype.name = "Nich";
CreatePerson3.prototype.age = 25;
CreatePerson3.prototype.job = "Teacher";
CreatePerson3.prototype.sayName = function () {
console.log(this.name);
};

let person1 = new CreatePerson3();
let person2 = new CreatePerson3();
person1.sayName(); // "Nich"
person2.sayName(); // "Nich"

总结

方式 分析
工厂模式 可解决创建多个类似对象的问题,但没解决对象标识的问题(即新创建的对象是什么类型)
构造函数模式 1.定义自定义构造函数可以确保其实例被标识为特定类型,相比工厂模式这是一个很大的好处。2.用构造函数定义的属性和方法会在每个实例上都创建一遍,创建时可以向构造函数传递参数,所有类型的数据都不会共享。3.正因为如此,主要问题在于创建实例时会重复创建相同的方法。
原型模式 1.同一个构造函数创建的所有实例共享同一个原型对象上的属性和方法,在构造函数原型对象上定义的方法不用在每一个实例上重复创建。2.正因为如此,原型模式弱化了向构造函数传递参数的能力,会导致所有实例默认都取得相同的属性值。3.其主要问题在于创建实例时原型对象上的引用值属性会在所有实例间共享混用。

继承

原型链继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function SuperType1() {
this.name = "Nich";
this.colors = ["red", "blue", "green"];
}
SuperType1.prototype.sayName = function () {
return this.name;
};
function SubType1() {
this.age = 22;
}
SubType1.prototype = new SuperType1();
SubType1.prototype.sayAge = function () {
return this.age;
};

let instance11 = new SubType1();
let instance12 = new SubType1();
instance11.colors.push("black");
console.log(instance11.colors); // ["red, blue, green, black"]
console.log(instance12.colors); // ["red, blue, green, black"]
instance11.sayName(); // 'Nich'
instance11.sayAge(); // 22
instance11.sayName === instance12.sayName; // true
instance11.sayAge === instance12.sayAge; // true
instance11 instanceof SubType1; // true
instance11 instanceof SuperType1; // true
instance11 instanceof Object; // true
instance11 instanceof Function; // false

借用构造函数继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function SuperType2(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
this.sayName = function () {
return this.name;
};
}
function SubType2(name) {
SuperType2.call(this, name);
this.age = 22;
this.sayAge = function () {
return this.age;
};
}

let instance21 = new SubType2("Nich");
let instance22 = new SubType2("Greg");
instance21.colors.push("black");
console.log(instance21.colors); // ["red, blue, green, black"]
console.log(instance22.colors); // ["red, blue, green"]
instance21.sayName(); // 'Nich'
instance21.sayAge(); // 22
instance21.sayName === instance22.sayName; // false
instance21.sayAge === instance22.sayAge; // false

组合继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function SuperType3(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType3.prototype.sayName = function () {
return this.name;
};
function SubType3(name) {
SuperType3.call(this, name);
this.age = 22;
}
SubType3.prototype = new SuperType3();
SubType3.prototype.sayAge = function () {
return this.age;
};

let instance31 = new SubType3("Nich");
let instance32 = new SubType3("Greg");
instance31.colors.push("black");
console.log(instance31.colors); // ["red, blue, green, black"]
console.log(instance32.colors); // ["red, blue, green"]
instance31.sayName(); // 'Nich'
instance31.sayAge(); // 22
instance31.sayName === instance32.sayName; // true
instance31.sayAge === instance32.sayAge; // true

原型式继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
let person = {
name: "Nich",
colors: ["red", "blue", "green"],
sayName() {
return this.name;
},
};

let instance41 = object(person);
let instance42 = object(person);
instance41.colors.push("black");
console.log(instance41.colors); // ["red, blue, green, black"]
console.log(instance42.colors); // ["red, blue, green, black"]
instance41.sayName(); // 'Nich'
instance41.sayName === instance42.sayName; // true

即等于

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let person = {
name: "Nich",
colors: ["red", "blue", "green"],
sayName() {
return this.name;
},
};

let instance43 = Object.create(person);
let instance44 = Object.create(person);
instance43.colors.push("black");
console.log(instance43.colors); // ["red, blue, green, black"]
console.log(instance44.colors); // ["red, blue, green, black"]
instance43.sayName(); // 'Nich'
instance43.sayName === instance44.sayName; // true

寄生式继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function createAnother(original) {
let clone = Object.create(original); // 不一定必须使用 Object.create()
clone.sayHi = function () {
console.log("hi");
};
return clone;
}
let person = {
name: "Nich",
colors: ["red", "blue", "green"],
sayName() {
return this.name;
},
};

let instance51 = createAnother(person);
let instance52 = createAnother(person);
instance51.sayName === instance52.sayName; // true
instance51.sayHi === instance52.sayHi; // false

寄生式组合继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function SuperType6(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType6.prototype.sayName = function () {
return this.name;
};
function SubType6(name) {
SuperType6.call(this, name);
this.age = 22;
}
function inheritPrototype(subType, superType) {
let prototype = Object.create(superType.prototype); // 创建对象
prototype.constructor = subType; // 增强对象
subType.prototype = prototype; // 赋值对象
}
inheritPrototype(SubType6, SuperType6);
// 运行完 inheritPrototype(SubType6, SuperType6) 再添加需要的方法,否则无效
SubType6.prototype.sayAge = function () {
return this.age;
};

let instance61 = new SubType6("Nich");
let instance62 = new SubType6("Greg");
instance61.colors.push("black");
console.log(instance61.colors); // ["red, blue, green, black"]
console.log(instance62.colors); // ["red, blue, green"]
instance61.sayName(); // 'Nich'
instance61.sayAge(); // 22
instance61.sayName === instance62.sayName; // true
instance61.sayAge === instance62.sayAge; // true

总结

方式 分析
原型链继承 有着原型模式一样的优缺点。是 ECMAScript 的主要继承方式。构造函数的原型对象是另一个构造函数的实例,以此类推,就在实例和原型之间构造了一条原型链。
借用构造函数继承 有着构造函数模式一样的优缺点。创建子类构造函数实例时,都会在子类构造函数上运行父类构造函数中的所有初始化代码,结果是每一个子类构造函数的实例都有父类构造函数的属性,亮点是可以在子类构造函数中借用多个父类构造函数。
组合继承 使用最多的继承模式。综合了原型链继承和借用构造函数继承,将两者的优点集中起来:使用原型链继承原型对象上的方法,使用借用构造函数继承属性。既可以把方法定义在原型对象上以实现重用,又可以让每个实例都有自己的属性。缺点是父类构造函数会被调用两次。
原型式继承 有着原型模式一样的优缺点。对原型模式的封装,Object.create() 方法将该概念规范化。
寄生式继承 有着构造函数模式一样的优缺点。以某种方式增强对象,然后返回这个对象。
寄生式组合继承 引用类型继承的最佳模式。组合继承的改进版:不通过调用父类构造函数给子类构造函数原型对象赋值,而是取得父类构造函数原型对象的一个副本,赋值给子类构造函数原型对象,同时指定该副本的 constructor 属性为子类构造函数。这样父类构造函数不会被调用两次。

函数方法

参考:https://juejin.cn/post/6946022649768181774

1
2
3
Function.prototype.call(thisArg, arg1, arg2, ...)
Function.prototype.apply(thisArg, [argsArray]])
Function.prototype.bind(thisArg[, arg1[, arg2[, ...]]])

call

使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Function.prototype.call2 = function (context) {
let context = context || window;
context.fn = this;

const args = [];
for (let i = 1, len = arguments.length; i < len; i++) {
args.push("arguments[" + i + "]");
}

let result = eval("context.fn(" + args + ")");

delete context.fn;
return result;
};

apply

使用一个指定的 this 值和单独给出的一个数组对象(或类数组对象)来调用一个函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Function.prototype.apply2 = function (context, arr) {
let context = context || window;
context.fn = this;

let result;
if (!arr) result = context.fn();
else {
const args = [];
for (let i = 0, len = arr.length; i < len; i++) {
args.push("arr[" + i + "]");
}
result = eval("context.fn(" + args + ")");
}

delete context.fn;
return result;
};

bind

使用一个指定的 this 值和单独给出的一个数组对象(或类数组对象)来调用一个函数。

创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Function.prototype.bind2 = function (context) {
let self = this;
let args = Array.prototype.slice.call(arguments, 1);

let fNOP = function () {};
let fBound = function () {
let bindArgs = Array.prototype.slice.call(arguments);
return self.apply(
this instanceof fNOP ? this : context,
args.concat(bindArgs)
);
};

fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
};

操作符

参考:https://juejin.cn/post/6844903613584654344

typeof

JS 在底层存储变量的时候,会在变量的机器码的低位 1-3 位存储其类型信息:

  • 000:对象
  • 010:浮点数
  • 100:字符串
  • 110:布尔值
  • 1:整数
  • null:所有机器码均为 0
  • undefined:用 −2^30 整数来表示

因此,由于 null 的所有机器码均为 0,因此直接被当做了对象来看待。

instanceof

1
2
3
4
5
6
7
8
function instanceOf(left, right) {
let proto = Object.getPrototypeOf(left); // 也可以:let proto = left.__proto__
while (true) {
if (proto === null) return false;
if (proto === right.prototype) return true;
proto = Object.getPrototypeOf(proto); // 也可以:proto = proto.__proto__
}
}

手写JavaScript
https://xuekeven.github.io/2021/08/20/手写JavaScript/
作者
Keven
发布于
2021年8月20日
更新于
2025年7月30日
许可协议