常见手写面试题

# 常见手写面试题

# 实现一个new操作符

  • 首先函数接受不定量的参数,第一个参数为构造函数,接下来的参数被构造函数使用
  • 然后内部创建一个空对象 obj
  • 因为 obj 对象需要访问到构造函数原型链上的属性,所以我们通过 setPrototypeOf 将两者联系起来。这段代码等同于 obj.proto = Con.prototype
  • 将 obj 绑定到构造函数上,并且传入剩余的参数
  • 判断构造函数返回值是否为对象,如果为对象就使用构造函数返回的值,否则使用 obj,这样就实现了忽略构造函数返回的原始值
/**
 * 创建一个new操作符
 * @param {*} Con 构造函数
 * @param  {...any} args 往构造函数中传的参数
 */
function createNew(con,...args){
    let obj = {};   //创建一个对象,因为new操作符会返回一个对象
    obj.__proto__ = con.prototype;  //将对象与构造函数原型链接起来
    let res = con.apply(obj,args);  //将构造函数中的this指向这个对象,并传递参数
    if(res instanceof Object){  // 判断构造函数返回值是否为对象
        return res;
    }else{
        return obj;
    }
}

function foo(name, age) {
    this.name = name;
    this.age = age;
    //console.log(this); //此时this已经发生变化了
}
var f = createNew(foo,'Chocolate',18);
console.log(f);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

注意:

# 一、new操作符的几个作用:

  • new操作符返回一个对象,所以我们需要在内部创建一个对象
  • 这个对象,也就是构造函数中的this,可以访问到挂载在this上的任意属性
  • 这个对象可以访问到构造函数原型链上的属性,所以需要将对象与构造函数链接起来
  • 返回原始值需要忽略,返回对象需要正常处理

# 二、new操作符的特点:

  • new通过构造函数Test创建处理的实例可以访问构造函数中的属性也可以访问构造函数原型链上的属性,所以:通过new操作符,实例与构造函数通过原型链连接了起来
  • 构造函数如果返回原始值,那么这个返回值毫无意义
  • 构造函数如果返回对象,那么这个返回值会被正常的使用,导致new操作符没有作用

# 手写 call, apply , bind

# 手写 call

Function.prototype.call = function () {
  if (typeof this !== 'function') {
    throw 'caller must be a function';
  }
  let othis = arguments[0] || window;
  othis._fn = this;
  let arg = [...arguments].slice(1);
  let res = othis._fn(...arg);
  Reflect.deletePropert(othis, '_fn');
  return res;
};
1
2
3
4
5
6
7
8
9
10
11

# 手写 apply

``apply的核心原理:

  • 将函数设为对象的属性
  • 执行和删除这个函数
  • 指定 this 到函数并传入给定参数执行函数
  • 如果不传参数,默认指向 window
Function.prototype.myApply= function(content = window){
    content.fn = this; //此时this指向的是调用myApply的函数bar
    let res;
    if(arguments[1]){
        res = content.fn(...arguments[1]);  //函数bar的this已经发生变化,指向content
    }else{
        res = content.fn();
    }
    delete content.fn;
    return res;
}

var obj = {
    value: 1
};
function foo(name, age) {
    console.log(name)
    console.log(age)
    console.log(this); //此时this已经发生变化了
    console.log(this.value);
}
foo.myApply(obj, ['Chocolate', 18]);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

注意:当apply传入的第一个参数为null时,函数体内的this会指向window。

# 手写 bind

# 实现函数科里化

# 科里化

function currying(fn, arr = []) {
  let len = fn.length;
  return (...args) => {
    let concatArgs = [...arr, ...args];
    if (concatArgs.length < len) {
      return currying(fn, concatArgs);
    } else {
      return fn.call(this, ...concatArgs);
    }
  };
}
let sum = (a, b, c, d) => {
  // console.log(a, b, c, d);
  console.log(a + b + c + d);
};
let newSum = currying(sum);
newSum(1)(2)(3)(4);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 反科里化

Function.prototype.uncurring = function () {
  var self = this;
  return function () {
    var obj = Array.prototype.shift.call(arguments);
    return self.apply(obj, arguments);
  };
};
1
2
3
4
5
6
7

# 实现深拷贝

# 大部分功能都实现版本

  • 实现功能:

    • 正常拷贝基础数据类型,数组和对象
    • 解决环引用问题。
  • 未实现功能

    • 无法拷贝 Symbol,RegExp,Function, Map,Set 数据类型
function deepClone(target, map = new Map()){
  // 基本数据类型直接返回
  if(typeof target !== 'object') {
    return target
  }

  // 判断是数组还是对象
  const temp = Array.isArray(target) ? [] : {}

  // 如果已存在则直接返回
  if(map.get(target)) {
    return map.get(target);
  }
  // 不存在则第一次设置
  map.set(target, temp)

  for(const key in target) {
    // 递归
    temp[key] = deepClone(target, map)
  }
  return temp;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 完全版本

# 我们先把以上的引用类型数据分为两类

  • 可遍历的数据类型
  • 不可遍历的数据类型

# 可遍历数据类型

  • 主要处理以下几种类型
    • Map
    • Set
    • Array
    • Object
// 可遍历的类型
const mapTag = '[object Map]';
const setTag = '[object Set]';
const arrayTag = '[object Array]';
const objectTag = '[object Object]';

// 不可遍历类型
const symbolTag = '[object Symbol]';
const regexpTag = '[object RegExp]';
const funcTag = '[object Function]';

// 将可遍历类型存在一个数组里
const canForArr = ['[object Map]', '[object Set]', '[object Array]', '[object Object]']

// 将不可遍历类型存在一个数组
const noForArr = ['[object Symbol]', '[object RegExp]', '[object Function]']

// 判断类型的函数
function checkType(target) {
    return Object.prototype.toString.call(target)
}

// 判断引用类型的temp
function checkTemp(target) {
  const c = target.constructor
  return new c()
}
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

# 不可遍历引用类型

主要处理以下几种类型

  • Symbol
  • RegExp
  • Function
// 拷贝Function的方法
function cloneFunction(func){
  const bodyReg = /(?<={)(.|\n)+(?=})/m;
  const paramReg = /(?<=\().+(?=\)\s+{)/;
  const funcString = func.toString();
  if (func.prototype) {
    const param = paramReg.exec(funcString);
    const body = bodyReg.exec(funcString);

    if (body) {
      if (param) {
          const paramArr = param[0].split(',');
          return new Function(...paramArr, body[0]);
      } else {
          return new Function(body[0]);
      }
    } else {
        return null;
    }

  } else {
    return eval(funcString);
  }
}

// 拷贝Symbol的方法
function cloneSymbol(target) {
    return Object(Symbol.prototype.valueOf.call(target));
}

// 拷贝RegExp的方法
function cloneReg(target) {
  const reFlags = /\w*$/;
  const result = new target.constructor(target.source, reFlags.exec(target));
  result.lastIndex = target.lastIndex;
  return result;
}

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
31
32
33
34
35
36
37
38

# 最终深拷贝方法


function deepClone(target ,map = new Map()) {

  // 获取类型
  const type = checkType(target)

  // 基本数据类型直接返回
  if(!canForArr.concat(noForArr).includes(type)) {
    return target
  }

  // 判断Function,RegExp,Symbol
  if (type === funcTag) return cloneFunction(target)
  if (type === regexpTag) return cloneReg(target)
  if (type === symbolTag) return cloneSymbol(target)


  // 引用数据类型特殊处理
  const temp = checkTemp(target)

  if(map.get(target)) {
    // 已存在则直接返回
    return map.get(target)
  }

  // 不存在则第一次设置
  map.set(target, temp)

  // 处理Map类型
  if(type === mapTag) {
    target.forEach((item, i) => {
      temp.set(i, deepClone(item, map))
    })

    return temp
  }

  // 处理Set类型
  if(type === setTag) {
    target.forEach(item => {
      temp.add(deepClone(item, map))
    })
    return temp
  }

  // 处理数组和对象
  for(const key in target) {
    // 递归
    temp[key] = deepClone(target[key], map)
  }

  return temp


}
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

# 实现一个对象内型的函数

let isType = (type) => {
  return (obj) => {
    return Object.prototype.toString.call(obj) === `[object ${type}]`
  }
}
let isArray = isType('Array')
let isFun = isType('Function')
console.log(isArray([1,2,3]))
console.log(isFun(function(){}))
1
2
3
4
5
6
7
8
9

# 函数防抖

  • 将多次操作合并为一次操作进行
function debounce(fn, delay = 500) {
  const timeId = null;
  return function () {
    if (timeId) clearTimeout(timeId);

    timeId = setTimeout(() => {
      timeId = null;
      fn.apply(this, arguments);
    }, delay);
  };
}
1
2
3
4
5
6
7
8
9
10
11

# 函数节流

  • 不管事件触发有多频繁,都会保证在规定时间内只执行一次
function throttle(fn, wait) {
  var flag = true;
  return function () {
    if (!flag) return;

    flag = false;
    setTimeout(() => {
      fn.apply(this, arguments);
      flag = true;
    }, wait);
  };
}
1
2
3
4
5
6
7
8
9
10
11
12

# 数组去重

  • 使用 SetArray.from
 const newArr = Array.from(new Set(arr))
1
  • filter
const newArr = arr.filter((item,i) => arr.indexOf(item) === i)
1
  • reduce
const newArr = arr.reduce((last,next,i) => {
  return [].concat(last, arr.indexOf(next) === i ? next : [])
})
1
2
3

# js 中怎么使 if(aᅠ==1 && a== 2 && ᅠa==3) 返回 true?

let a = {
  i: 1,
  toString: function () {
    return a.i++;
  }
};
1
2
3
4
5
6
最后更新时间: 2022/10/23 10:24:55