手写 JS
instanceof
instanceof 用于检测构造函数的 prototype 是否出现在被检测对象的原型链上。
- 基础数据类型都返回 false
- null 返回 false
- 校验右侧数据类型,如果是基础数据类型则报错:
Uncaught TypeError: Right-hand side of 'instanceof' is not an object
- 右侧如果是
{}
,则报错:Uncaught TypeError: Right-hand side of 'instanceof' is not callable
- 右侧要有 prototype 属性
function myInstanceof(left, right) {
if ((typeof left !== 'object' && typeof left !== 'function') || left === null) return false;
let proto = Object.getPrototypeOf(left);
while (true) {
if (proto === null) return false;
if (proto === right.prototype) return true;
proto = Object.getPrototypeOf(proto);
}
}
console.log(myInstanceof(1, Number)); // false
console.log(myInstanceof(new Boolean(), Boolean)); // true
console.log(myInstanceof(() => {}, Function)); // true
console.log({} instanceof {}); // Uncaught TypeError: Right-hand side of 'instanceof' is not callable
console.log({} instanceof 1); // Uncaught TypeError: Right-hand side of 'instanceof' is not an object
new
function myNew(constructor, ...args) {
if (typeof constructor !== 'function') {
throw new TypeError('Constructor must be a function');
}
const obj = Object.create(constructor.prototype);
const result = constructor.apply(obj, args);
return result !== null && result instanceof Object ? result : obj;
}
call
示例:
let foo = { value: 1 };
function bar() {
console.log(this.value);
}
bar.call(foo);
通过 call 将 this 指向了 foo,可以理解成foo.bar()
,如下:
let foo = {
value: 1,
bar: function () {
console.log(this.value);
}
};
还有其他的情况:接收参数、参数为 null 或 undefined、有返回值
let foo = { value: 1 };
function bar(a, b) {
console.log(a, b);
console.log(this.value);
return { a: 1 };
}
bar.call(foo, 1, 2);
bar.call(null); // this 指向 window
let res = bar.call(foo, 1, 2);
console.log(res); // { a: 1 }
综上,总结出以下步骤:
- 给 foo 增加一个临时的函数 fn,指向 bar:
foo.fn = bar
- 执行 fn:
foo.fn()
- 删除 fn:
delete foo.fn
- 如果传入 call 的参数是
null
或者undefined
,那么 this 就指向window
- 如果 bar 有返回值,需要将结果返回
最终结果:
Function.prototype.myCall = function (context, ...args) {
context = context || window;
context.fn = this;
let result = context.fn(...args);
delete context.fn;
return result;
};
apply
apply 方法和 call 方法类似,只是传入的参数不同,apply 方法传入的是一个数组。
Function.prototype.myApply = function (context, args = []) {
context = context || windown;
context.fn = this;
let result = context.fn(...args);
delete context.fn;
return result;
};
bind
步骤:
- 保存当前函数的 this 指向
- 返回一个新函数
- 组合参数,将新函数调用时的参数和绑定的参数合并
- 处理函数调用时的 new 操作符
- 设置新函数的 prototype 为原函数的 prototype,以便于正确继承
Function.prototype.myBind = function (context, ...args) {
const fn = this;
const boundFn = function (...newArgs) {
return fn.apply(this instanceof boundFn ? this : context, [...args, ...newArgs]);
};
boundFn.prototype = Object.create(fn.prototype);
return boundFn;
};
验证:
const foo = { value: 'world' };
function bar(name, age) {
console.log(name + ', ' + this.value);
this.name = name;
this.age = age;
}
bar.prototype.say = function () {
console.log('say');
};
const fn = bar.myBind(foo, 'hello');
fn();
const ins = new fn(18);
console.log(ins);
console.log(foo);
- 调用 fn() 时:
- 不是通过 new 调用,即
fn.apply(context, [...args, ...newArgs])
,实际上执行bar.apply(foo, ['hello'])
- bar 函数执行,输出
hello, world
,此时 foo 为:
- 不是通过 new 调用,即
{ value: 'world', name: 'hello', age: undefined }
-
使用 new fn(18) 创建一个新实例 ins:
- 这次调用 fn 时,
this instanceof boundFn
为 true,所以 this 指向新的实例对象 ins - 调用
fn.apply(this, [...args, ...newArgs])
实际上执行bar.apply(ins, ['hello', 18])
- bar 函数执行,输出
hello, undefined
(因为 ins 没有 value 属性),并在 ins 上设置 name 和 age 属性 :{ name: 'hello', age: 18 }
- ins 继承了
bar.prototype
,所以 ins 也有 say 方法
- 这次调用 fn 时,
-
boundFn.prototype = Object.create(fn.prototype)
,这里如果直接写成boundFn.prototype = fn.prototype
,则在修改boundFn.prototype
时,也会修改fn.prototype
,因为两个对象引用了同一个原型对象。