手写一个instanceof
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
| function myInstanceof (obj, target) { let res = false; let tempObj = obj; while(tempObj.__proto__) { if(tempObj.constructor === target) { res = true; break; } tempObj = tempObj.__proto__; } return res; }
class Animal {}
class Dog extends Animal {}
const husky = new Dog();
console.log(husky instanceof Animal);
console.log(myInstanceof(husky, Animal))
console.log(Animal instanceof Animal);
console.log(myInstanceof( Animal, Animal ))
|
__proto__可以用Object.getPrototypeof()代替。因为前者不是标准方法,是浏览器自己实现的。
手写斐波那契数列
很经典的问题,斐波那契数列的意思是:第一位为1、第二位为1、第n位为前两项之和。
递归
所以最符合思路的就是递归。
1 2 3 4 5 6
| function fibo(n) { if (n === 1) return 1; if (n === 2) return 1; return fibo(n - 2) + fibo(n - 1); }
|
然后就会扯到、递归的危害和优化,危害当然是堆栈过多。而且有个规律是,尾递归是肯定可以优化的。以上代码就是个经典的尾递归。就是递归方法的调用在方法的最后。
函数栈的目的是啥?是保持入口环境。那么在什么情况下可以把这个入口环境给优化掉?答案不言而喻,入口环境没意义的情况下为啥要保持入口环境?尾递归,就恰好是这种情况。
尾递归保存的状态再递归执行完成后立马被返回,没有意义。
循环
一般思路下,尾递归可以用循环的方式替代
而这就没必要用什么固定思路去转换了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| function fibo(n) { if (n === 1) return 1; if (n === 2) return 1;
let sum = 0; let n1 = 1; let n2 = 1;
for (let i = 2; i < n; i++) {
sum = n1 + n2;
n1 = n2; n2 = sum;
}
return sum;
}
|
思路是。一个for循环,sum值是前两项的和,则需要两个变量存储前两项的和。n1、n2
每次i递增。则把n1丢弃、n1 = n2、n2 = sum即可。
手写bind
只考虑普通函数的情况时
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| Function.prototype.myBind = function(context, ...args) { const fn = this; return function(...innerArgs) { return fn.call(context, ...args, ...innerArgs) }; }
function print(...inputs) { console.log(...inputs) }
const printAbc = print.myBind(null, 'abc') printAbc('def');
|
普通函数时很简单
- 返回一个新函数
- 新函数的执行上下文固定是bind方法的第一个参数
- bind方法除了第一个参数,作为新函数的默认参数
以上简简单单几行,利用call指定执行上下文即可
手写call、apply
有几个小点是要注意的:
- 没有传递context时,context默认时window
- 在context环境下调用function
- 调用完记得删除context环境下的function
- 进阶一点的时,要防止ctx.fn不会覆盖ctx本身的内容,一个简单的方法是,用Symbol来命名fn。
1 2 3 4 5 6 7 8 9
| Function.prototype.myCall = function(context, ...args) { const ctx = context || window; ctx.fn = this; const result = ctx.fn(...args); delete ctx.fn; return result; }
|
apply同理
1 2 3 4 5 6 7 8 9
| Function.prototype.myapply = function(context, args = []) { const ctx = context || window; ctx.fn = this; const result = ctx.fn(...args); delete ctx.fn; return result; }
|
用new操作符
先复习new操作符干了啥
- 新建一个空白对象
- 将这个空对象的原型指向构造函数的prototype属性
- 将步骤1新创建的对象作为this的上下文
- 执行构造函数内部的代码
- 如果该函数返回值是引用类型数据,则返回该值,否则返回this
new操作也修改了this的指向。实际上,最终的this指向会已new操作符为主。
new操作符还会把构造函数的prototype赋值给新对象。所以需要以下改造
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
| Function.prototype.myBind = function(context, ...args) { const fn = this; const newFunc = function(...innerArgs) { if (this instanceof newFunc) { return fn.call(this, ...args, ...innerArgs) } return fn.call(context, ...args, ...innerArgs) }
newFunc.prototype = this.prototype; return newFunc }
function Person(name) { this.name = name; }
const BindedPerson = Person.myBind({name: 'name'});
const bindedMing = new BindedPerson();
console.log(bindedMing)
|
手写call
本至上,是修改了指定方法的执行上下文,然后执行,然后返回其结果。唯一要注意的是,call的第一个参数未falsy,也就是没有指定上下文,则这个上下文是window
1 2 3 4 5 6 7 8 9 10 11
| Function.prototype.myCall = function(context, ...args) { const fn = this; const ctx = context || window;
context.fn = fn; const result = context.fn(...args); delete context.fn;
return result;
}
|
可能存在一个问题是,context下本来就有fn方法,这么实现会导致原方法被覆盖。可以用Symble来实现唯一的key
1 2 3 4 5 6 7 8 9 10 11 12 13
| Function.prototype.myCall = function(context, ...args) { const fn = this; const ctx = context || window;
const fnName = Symble('tempFn') context[fnName] = fn; const result = context[fnName](...args); delete context[fnName];
return result;
}
|