手写代码系列

手写代码系列

三月 10, 2021

<! – more –>

手写一个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
function myInstanceof (obj, target) {
let res = false;
let tempObj = obj;
while(tempObj.__proto__) {
if(tempObj.constructor === target) {
res = true;
}
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位为前两项之和。

递归

所以最符合思路的就是递归。

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;
// bind返回的方法
return function(...innerArgs) {
return fn.call(context, ...args, ...innerArgs)
};
}

function print(...inputs) {
console.log(...inputs)
}

// const printAbc = print.bind(null, 'abc')
const printAbc = print.myBind(null, 'abc')
printAbc('def'); // 'abc' 'def'

普通函数时很简单

  • 返回一个新函数
  • 新函数的执行上下文固定是bind方法的第一个参数
  • bind方法除了第一个参数,作为新函数的默认参数

以上简简单单几行,利用call指定执行上下文即可

用new操作符

先复习new操作符干了啥

  1. 新建一个空白对象
  2. 将这个空对象的原型指向构造函数的prototype属性
  3. 将步骤1新创建的对象作为this的上下文
  4. 执行构造函数内部的代码
  5. 如果该函数没有返回对象,则返回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;

// 返回的方法。可能被直接调用或者被new 调用
const newFunc = function(...innerArgs) {
// 以下判断是否是由new操作符调用的该方法。
// 如果是普通调用,this应该为window,或其他执行上下文。
// 如果是new 则this则是new过程新建的对象,也就是实例。
// 所以 this instanceof newFunc会成立
if (this instanceof newFunc) {
return fn.call(this, ...args, ...innerArgs)
}
return fn.call(context, ...args, ...innerArgs)
}

// 应该修改原型链,不改,则新方法的constructor指向“newFunc”,
// 应该把其指向this.prototype,也就是Persion
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) // {name: 'ming'}