目录
2. call, apply / bind 的实现原理是什么?
一、new
的实现原理
- 创建一个空对象
- 这个新对象被执行 [[原型]] 连接
- 构造函数中的this指向这个空对象,执行构造函数方法,属性和方法被添加到this引用的对象中
- 如果构造函数中没有返回其它对象,那么返回this,即创建的这个的新对象,否则,返回构造函数中返回的对象。
function _new () {
let target = {}; // 创建的新对象
// 第一个参数是构造函数
let [constructor, ...args] = [...arguments];
// 执行[[原型]]连接;target 是 constructor 的实例
target.__proto__ = constructor.prototype;
// 构造函数中的this指向空对象,执行构造函数,将属性和方法添加到创建的空对象上
let result = constructor.apply(target, args);
if (result && (typeof result == 'object' || typeof result == 'function')) {
// 如果构造函数执行的结果返回的是一个对象,那么返回这个对象
return result;
}
// 如果构造函数返回的不是一个对象,返回创建的新对象
return target;
}
二、call, apply,bind 的实现原理
call 和 apply 的功能相同,都是改变 this
的指向,并立即执行函数。区别在于传参方式不同。
-
func.call(thisArg, arg1, arg2, ...)
:第一个参数是this
指向的对象,其它参数依次传入。 -
func.apply(thisArg, [argsArray])
:第一个参数是this
指向的对象,第二个参数是数组或类数组。
一起思考一下,如何模拟实现 call
?
首先,我们知道,函数都可以调用 call
,说明 call
是函数原型上的方法,所有的实例都可以调用。即: Function.prototype.call
。
- 在
call
方法中获取调用call()
函数 - 如果第一个参数没有传入,那么默认指向
window / global
(非严格模式) - 传入
call
的第一个参数是 this 指向的对象,根据隐式绑定的规则,我们知道obj.foo()
,foo()
中的this
指向obj
;因此我们可以这样调用函数thisArgs.func(...args)
- 返回执行结果
// 模拟实现call
Function.prototype.call = function() {
let [thisArg, ...args] = [...arguments];
if (!thisArg) {
//context为null或者是undefined
thisArg = typeof window === 'undefined' ? global : window;
}
//this的指向的是当前函数 func (func.call)
thisArg.func = this;
//执行函数
let result = thisArg.func(...args);
delete thisArg.func; //thisArg上并没有 func 属性,因此需要移除
return result;
}
apply 的实现思路和 call
一致,仅参数处理略有差别。如下:
// 模拟实现apply
Function.prototype.apply = function(thisArg, rest) {
let result; //函数返回结果
if (!thisArg) {
//context为null或者是undefined
thisArg = typeof window === 'undefined' ? global : window;
}
//this的指向的是当前函数 func (func.call)
thisArg.func = this;
if(!rest) {
//第二个参数为 null / undefined
result = thisArg.func();
}else {
result = thisArg.func(...rest);
}
delete thisArg.func; //thisArg上并没有 func 属性,因此需要移除
return result;
}
bind类似call,但返回的是函数
// 模拟实现bind
Function.prototype.mybind = function (context) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
let _this = this
let arg = [...arguments].slice(1)
return function F() {
// 处理函数使用new的情况
if (this instanceof F) {
return new _this(...arg, ...arguments)
} else {
return _this.apply(context, arg.concat(...arguments))
}
}
}
三、柯里化函数实现
在开始之前,我们首先需要搞清楚函数柯里化的概念。
函数柯里化是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
const curry = (fn, ...args) =>
args.length < fn.length
//参数长度不足时,重新柯里化该函数,等待接受新参数
? (...arguments) => curry(fn, ...args, ...arguments)
//参数长度满足时,执行函数
: fn(...args);
function sumFn(a, b, c) {
return a + b + c;
}
var sum = curry(sumFn);
console.log(sum(2)(3)(5));//10
console.log(sum(2, 3, 5));//10
console.log(sum(2)(3, 5));//10
console.log(sum(2, 3)(5));//10
函数柯里化的主要作用:
- 参数复用
- 提前返回 – 返回接受余下的参数且返回结果的新函数
- 延迟执行 – 返回新函数,等待执行
四、Promise 原理
1. 实现 Promise.resolve
实现 resolve 静态方法有三个要点:
- 传参为一个 Promise, 则直接返回它
- 传参为一个 thenable 对象,返回的 Promise 会跟随这个对象,
采用它的最终状态
作为自己的状态
- 其他情况,直接返回以该值为成功状态的promise对象
具体实现如下:
Promise.resolve = (param) => {
if(param instanceof Promise) return param;
return new Promise((resolve, reject) => {
if(param && param.then && typeof param.then === 'function') {
// param 状态变为成功会调用resolve,将新 Promise 的状态变为成功,反之亦然
param.then(resolve, reject);
}else {
resolve(param);
}
})
}
2. 实现 Promise.reject
Promise.reject 中传入的参数会作为一个 reason 原封不动地往下传, 实现如下:
Promise.reject = function (reason) {
return new Promise((resolve, reject) => {
reject(reason);
});
}
3. 实现 Promise.prototype.finally
无论当前 Promise 是成功还是失败,调用finally
之后都会执行 finally 中传入的函数,并且将值原封不动的往下传。
Promise.prototype.finally = function(callback) {
this.then(value => {
return Promise.resolve(callback()).then(() => {
return value;
})
}, error => {
return Promise.resolve(callback()).then(() => {
throw error;
})
})
}
4. 实现 Promise.all
在实现 Promise.all 方法之前,我们首先要知道 Promise.all 的功能和特点。
Promise.all 功能
Promise.all(iterable) 返回一个新的 Promise 实例。此实例在 iterable 参数内所有的 promise 都 fulfilled 或者参数中不包含 promise 时,状态变成 fulfilled;如果参数中 promise 有一个失败rejected,此实例回调失败,失败原因的是第一个失败 promise 的返回结果。
let p = Promise.all([p1, p2, p3]);
p的状态由 p1,p2,p3决定,分成以下;两种情况:
(1)只有p1、p2、p3的状态都变成 fulfilled,p的状态才会变成 fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
(2)只要p1、p2、p3之中有一个被 rejected,p的状态就变成 rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
实现方式1:
Promise.all = function (promises) {
//promises 是可迭代对象,省略参数合法性检查
return new Promise((resolve, reject) => {
//Array.from 将可迭代对象转换成数组
promises = Array.from(promises);
if (promises.length === 0) {
resolve([]);
} else {
let result = [];
let index = 0;
for (let i = 0; i < promises.length; i++ ) {
//考虑到 i 可能是 thenable 对象也可能是普通值
Promise.resolve(promises[i]).then(data => {
result[i] = data;
if (++index === promises.length) {
//所有的 promises 状态都是 fulfilled,promise.all返回的实例才变成 fulfilled 态
resolve(result);
}
}, err => {
reject(err);
return;
});
}
}
});
}
实现方式2:
Promise.all = function(promises) {
return new Promise((resolve, reject) => {
let result = [];
let index = 0;
let len = promises.length;
if(len === 0) {
resolve(result);
return;
}
for(let i = 0; i < len; i++) {
// 为什么不直接 promise[i].then, 因为promise[i]可能不是一个promise
Promise.resolve(promise[i]).then(data => {
result[i] = data;
index++;
if(index === len) resolve(result);
}).catch(err => {
reject(err);
})
}
})
}
5. 实现 Promise.race
race 的实现相比之下就简单一些,只要有一个 promise 执行完,直接 resolve 并停止执行。
Promise.race = function(promises) {
return new Promise((resolve, reject) => {
let len = promises.length;
if(len === 0) return;
for(let i = 0; i < len; i++) {
Promise.resolve(promise[i]).then(data => {
resolve(data);
return;
}).catch(err => {
reject(err);
return;
})
}
})
}
五、instanceof 的实现原理
// 思路:右边变量的原型存在于左边变量的原型链上
function instanceOf(left, right) {
let leftValue = left.__proto__
let rightValue = right.prototype
while (true) {
if (leftValue === null) {
return false
}
if (leftValue === rightValue) {
return true
}
leftValue = leftValue.__proto__
}
}
六、Object.create() 的实现原理
// 思路:将传入的对象作为原型
function create(obj) {
function F() {}
F.prototype = obj
return new F()
}
七、实现一个基本的Event Bus
// 组件通信,一个触发与监听的过程
class EventEmitter {
constructor () {
// 存储事件
this.events = this.events || new Map()
}
// 监听事件
addListener (type, fn) {
if (!this.events.get(type)) {
this.events.set(type, fn)
}
}
// 触发事件
emit (type) {
let handle = this.events.get(type)
handle.apply(this, [...arguments].slice(1))
}
}
// 测试
let emitter = new EventEmitter()
// 监听事件
emitter.addListener('ages', age => {
console.log(age)
})
// 触发事件
emitter.emit('ages', 18) // 18
八、实现一个简易的模板引擎
function render(template, data) {
const reg = /\{\{(\w+)\}\}/; // 模板字符串正则
while (reg.test(template)) { // 判断模板里是否有模板字符串
const name = reg.exec(template)[1]; // 查找当前模板里第一个模板字符串的字段
template = template.replace(reg, data[name]); // 将第一个模板字符串渲染
}
return template; // 如果模板没有模板字符串直接返回
}