原型相关
new
new 做了四件事:① 创建空对象;② 把对象原型指向构造函数的 prototype;③ 以该对象为 this 执行构造函数;④ 若构造函数返回的是对象则返回它,否则返回新建对象。
function myNew(Fn, ...args) {
// 创建对象并把原型链接到 Fn.prototype(等价于 obj.__proto__ = Fn.prototype)
const obj = Object.create(Fn.prototype);
// 以新对象为上下文执行构造函数
const result = Fn.apply(obj, args);
// 构造函数显式返回对象时以其为准,否则返回新建对象
return result !== null && (typeof result === "object" || typeof result === "function")
? result
: obj;
}
// myNew(Demo, 1, 2, 3) 等价于 new Demo(1, 2, 3)
instanceof
function myinstanceof(left, right) {
let leftProto = left.__proto__;
let rightProto = right.prototype;
while (leftProto) {
if (leftProto === rightProto) {
return true;
}
leftProto = leftProto.__proto__;
}
return false;
}
console.log(myinstanceof([], Array));
call/apply
要点:① 把函数挂到 context 上并以 context.fn() 形式调用,从而改变 this;② 用 Symbol 作为临时键,避免覆盖 context 已有属性;③ 处理 context 为 null/undefined/原始值的情况。
let obj = { name: "jacksonzhou" };
function Fn(id) {
console.log(id + " " + this.name);
}
// call
Function.prototype.myCall = function (context, ...args) {
// null/undefined 时指向全局对象,原始值需装箱
context = context == null ? globalThis : Object(context);
const key = Symbol("fn");
context[key] = this; // this 即被调用的函数
const result = context[key](...args);
delete context[key];
return result;
};
Fn.myCall(obj, 1);
// apply(与 call 的区别仅是第二个参数为数组)
Function.prototype.myApply = function (context, args = []) {
context = context == null ? globalThis : Object(context);
const key = Symbol("fn");
context[key] = this;
const result = context[key](...args);
delete context[key];
return result;
};
Fn.myApply(obj, [1]);
bind
要点:① 支持「分两次传参」(柯里化);② 当返回的绑定函数被 new 调用时,this 应指向新实例而非绑定的 context,且要维持原型链。
Function.prototype.myBind = function (context, ...preArgs) {
const self = this; // 保存原函数
const bound = function (...args) {
// 作为构造函数(new bound())调用时,this 指向新实例
const isNew = this instanceof bound;
return self.apply(isNew ? this : context, [...preArgs, ...args]);
};
// 维持原型链,使 new bound() 的实例能访问原函数原型上的方法
if (self.prototype) {
bound.prototype = Object.create(self.prototype);
}
return bound;
};
class
其实 es6 的 class 就是一个语法糖,是基于 js 构造函数和原型去实现的
function createClass(Constructor, protoProps, staticProps) {
if (protoProps) {
Object.defineProperties(Constructor.prototype, protoProps);
}
if (staticProps) {
Object.defineProperties(Constructor, staticProps);
}
return Constructor;
}
function Animal(name) {
// 实例属性赋值
this.name = name;
}
// 在构造函数的原型对象上挂载实例方法
createClass(
Animal,
{
getName: {
value: function getName() {
console.log("调用实例方法,获取实例属性", this.name);
return this.name;
},
},
},
{
getParentName: {
// 在构造函数上挂载静态属性和方法
value: function getParentName() {
console.log("调用静态方法,获取静态属性", this.parentName);
return this.parentName;
},
},
parentName: {
value: "Animal",
},
}
);
let animal1 = new Animal("animal1");
// 调用实例方法
animal1.getName();
// 调用静态方法
Animal.getParentName();
继承(extends)
extends 本质上是基于寄生式组合继承来实现的
- new 一个实例时,调用父类构造函数,继承实例属性
- 通过原型链继承父类实例方法
- 设置子类的 proto = 父类,让子类可以调用父类静态属性和方法
// 已知 Animal 这个父类
function _inherits(child, parent) {
let proto = Object.create(parent.prototype);
proto.constructor = child;
child.prototype = proto;
}
function Cat(name) {
// 调用父类的构造函数,继承实例属性
Animal.call(this, name);
}
// 继承父类实例方法
_inherits(Cat, Animal);
// 设置子类的 __proto__ = 父类,让子类可以调用父类静态属性和方法
Object.setPrototypeOf(Cat, Animal);
let cat = new Cat("jack");
cat.getName();
console.log(Cat.parentName);