原稿:语雀 · 原生重写 · 原目录:手撕代码 › 原生重写
instanceof
对于A.instanceof(B),它可以判断A的原型链上是否有B的原型
// left的原型链上有right.prototype
function instance_of(left, right) {
// 父类的原型对象
let rightProto = right.prototype;
// 子类的隐式原型
let leftValue = left.__proto__;
while (true) {
if (leftValue === null) return false;
if (leftValue === rightProto) return true;
// 沿原型链上溯
leftValue = leftValue.__proto__;
}
}
// 测试用例
class Animal {
constructor() {
this.name = "animal";
this.sex = "male";
}
sleep() {
console.log("animals can sleep");
}
}
class Dog extends Animal {
constructor() {
super();
this.age = 0;
}
bark = function () {
console.log("dogs can bark");
};
}
new
new用来配合构造函数创建对象,当使用new时会发生: 2. 在内存中创建新对象 4. 新对象内部的
]指向构造函数的原型对象 6. 构造函数内部的this指向新对象 8. 执行构造函数内部代码 10. 如果构造函数返回非空对象,则返回该对象,否则返回第1步创建的对象
function objectFactory(constructor, ...args) {
let obj = new Object();
obj.__proto__ = constructor.prototype;
let ret = constructor.apply(obj, args);
if(ret && (typeof ret == "object" || typeof ret == "function")) return ret;
return obj;
}
// 测试用例
function Animal(name, sex) {
this.name = name;
this.sex = sex;
}
let animal = objectFactory(Animal, "Eve", "female");
console.log(animal);
call()
call和apply类似,由一个函数调用,执行该函数,但是执行上下文中的this指向了我们传入的指定对象,为了实现call(),我们需要 2. 把函数新建为传入对象的一个属性,当传入null时,对象为window 4. 使用对象执行函数 6. 删除刚才添加的属性 8. 返回
不能使用箭头函数=(context)=》,因为执行时没有this和arguments,其this取决于定义位置上下文中this
Function.prototype.myCall = function (context) {
context = context || window;
context.fn = this;
// 这里毕竟还在实现 call,不要使用 [].slice.call(arguments,1)
let args = [...arguments].slice(1);
let result = context.fn(args);
delete context.fn;
return result;
};
// 测试用例
var value = 2;
var obj = {
value: 1,
};
function bar(name, age) {
console.log(this.value);
return {
value: this.value,
name: name,
age: age,
};
}
bar.myCall(null);
console.log(bar.myCall(obj, "kevin", 18));
参考链接:JavaScript深入之call和apply的模拟实现
apply()
比起call还要简单,可以直接展开arguments
,注意无参数情况
Function.prototype.myApply = function (context) {
context = context || window;
context.fn = this;
// 注意无参数情况
let result = (arguments[1]) ? context.fn(...arguments[1]) : context.fn();
delete context.fn;
return result;
};
// 测试用例
var value = 2;
var obj = {
value: 1,
};
function bar(name, age) {
console.log(this.value);
return {
value: this.value,
name: name,
age: age,
};
}
bar.myApply(null);
console.log(bar.myApply(obj, ["kevin", 18]));
bind()
根据MDN:Function.prototype.bind(),bind()中如果传入的context为null时,执行作用域的 this 将被视为新函数的 thisArg,而不是想call()/apply()绑定到全局对象
bind的功能
- 传入指定对象作为函数执行的this,可传入预置参数
- 返回一个函数对象
实现要点
- 当调用bind的不是函数时,需要抛出异常
- 在bind阶段可传入预置参数,执行返回函数时可以继续传参,两部分参数拼接
- 返回函数可作为构造函数使用,此时之前绑定的this失效
- 返回的函数对象可访问绑定函数的原型属性,但返回函数原型属性改动不应该同步到绑定函数
Function.prototype.myBind = function (context) { if (typeof this !== "function") { throw new TypeError( "Function.prototype.bind - what is trying to be bound is not callable" ); } // 要被绑定的函数 let fToBind = this, // 预置参数 args1 = [].slice.call(arguments, 1), // 空函数(保存原型快照) fNOP = function () {}, // 返回函数 fBound = function () { // 调用时传参,和预置参数拼接 let args2 = [...arguments]; // 如果(this instanceof fBound)成立,则为构造函数 return fToBind.apply( this instanceof fBound ? this : context, args1.concat(args2) ); }; // 维护原型关系 fNOP.prototype = this.prototype; fBound.prototype = new fNOP(); return fBound;};// 测试var value = 2;var foo = { value: 1,};
继承
我根据红宝书总结了ES5的6种继承+ES6类继承,面试重点“组合式继承”、“寄生式组合继承”、“类继承”
- 组合式继承
// 父类
function Animal() {
this.superName = "animal";
}
Animal.prototype.getSuperName = function () {
return this.superName;
};
// 子类
function Dog() {
// 借用构造函数
Animal.call(this);
this.subName = "dog";
}
// 原型链继承
Dog.prototype = new Animal();
// 测试
let dog = new Dog();
console.log(dog.getSuperName());
- 寄生式组合继承
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
function inheritPrototype(subType, superType){
let prototype = object(superType.prototype);
prototype.constructor = subType;
subType.prototype = prototype;
}
function Animal() {
this.superName = "animal";
}
Animal.prototype.getSuperName = function () {
return this.superName;
};
function Dog() {
Animal.call(this);
this.subName = "dog";
}
// 寄生式继承
inheritPrototype(Dog, Animal);
let dog = new Dog();
console.log(dog.getSuperName());
- 类继承
class Animal {
constructor() {
this.superName = "animal";
}
getSuperName() {
return this.superName;
}
}
// 别漏掉extends和super()
class Dog extends Animal {
constructor() {
super();
this.subName = "dog";
}
}
let dog = new Dog();
console.log(dog.getSuperName());
Object.create()
该方法实际上就是“原型式继承”中的临时构造函数object()——接受一个对象并返回以此为原型的实例,这种继承模式可以不用创建父类,直接利用已有实例作为模板,缺点是子类实例共享原型(已有实例)属性
function object(o){
function F();
F.prototype = o;
return new F();
}
// 测试用例
// 已有实例
let bus = {
name: "bus",
colors: ["red","blue"]
};
let bus1 = object(bus);
bus1.colors.push("green");
let bus2 = object(bus);
bus2.colors.push("black");
console.log(bus2.colors); //["red", "blue", "green", "black"]
reduce()
reduce()是Array提供的归并方法,方法接收两个参数
- 对每一项都会运行的归并函数和归并初始值
- 对于归并函数,可接受四个参数:上一个归并值、当前项、当前项的索引和数组本身
如果没有提供归并初始值,则第一次执行时默认上一个归并值为数组首元素,当前项为数组第二个元素
方法最后返回一个归并运算的结果