JavaScript手写代码

1. 实现一个new操作符

  • 过程

    1. 创建一个空对象,并且 this 变量引用该对象,同时还继承了该函数的原型。

    2. 属性和方法被加入到 this 引用的对象中。

    3. 新创建的对象由 this 所引用,并且最后隐式的返回 this 。

  • new 操作符做了这些事:

    • 它创建了一个全新的对象

    • 它会被执行[[Prototype]](也就是__proto__)链接

    • 它使this指向新创建的对象

    • 通过 new 创建的每个对象将最终被[[Prototype]]链接到这个函数的 prototype 对象上

    • 如果函数没有返回对象类型 Object(包含 Function、Array、Date、RegExp、Error),那么 new 表达式中的函数调用将返回该对象引用

function New(func) {
  // 创建一个全新的对象
  var res = {};
  if (func.prototype !== null) {
    // 通过new创建的每个对象将链接到这个函数的prototype对象上
    res.__proto__ = func.prototype;
  }
  // 使this指向新创建的对象
  var ret = func.apply(res, Array.prototype.slice.call(arguments, 1));
  if ((typeof ret === "object" || typeof ret === "function") && ret !== null) {
    // 返回该对象引用
    return ret;
  }
  return res;
}

var obj = New(A, 1, 2);
// equals to
var obj = new A(1, 2);

2. 实现一个JSON.stringify

JSON.stringify(value[, replacer [, space]]);

  • Boolean|Number|String 类型会自动转换对应的原始值

  • undefined、任意函数以及 Symbol,会被忽略(出现在非数组对象的属性值中时),或者被转换成 null(出现在数组中时)

  • 不可枚举的属性会被忽略

  • 如果一个对象的属性值通过某种间接的方式指回该对象本身,及循环引用,属性也会被忽略

3. 实现一个JSON.parse

JSON.parse(text,reviver)

用来解析 JSON 字符串,构造由字符串描述的 JavaScript 值或对象。提供可选的 reviver 函数用以在返回之前对得到的对象执行变换(操作)

3.1 第一种:直接调用 eval

避免在不必要的情况下使用evaleval()是一个危险的函数,他执行的代码拥有着执行者的权利

3.2 第二种:Function

核心:Functioneval有相同的字符串参数特性。

在转换 JSON 的实际应用中,只需要这么做

evalFunction都有着动态编译 js 代码的作用,但是在实际的编程中并不推荐使用

4. 实现一个callapply

call语法:

fun.call(thisArg,arg1,arg2,...),调用一个函数,其具有一个指定的this值和分别的提供的参数(参数列表)。

apply语法:

fun.apply(thisArg,[argsArray],调用一个函数,以及作为一个数组(或类似数组的对象)提供平的参数。

4.1 Function.call按套路实现

call核心:

  • 将函数设为对象的属性

  • 执行并删除这个函数

  • 指定this到函数并传入给定参数执行函数

  • 如果不传入参数,默认指向为 window

4.1.1 简单版

4.1.2 完善版

4.2 Function.apply的模拟实现

apply()的实现和call()类似,只是参数形式不同。

5. 实现一个Function.bind()

bind()方法:

会创建一个新函数。当这个新函数被调用时,bind()的第一个参数将作为他运行时的this,之后的一序列参数将会在传递的实参前传入作为他的参数。

此外,bind实现需要考虑实例化后对原型链的影响。

6. 实现一个继承

寄生组合式继承:

核心实现是:用一个F空的构造函数去取代执行了Parent这个构造函数

7. 实现一个 JS 函数柯里化

什么是柯里化?

在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。

7.1 通用版

7.2 ES6写法

8. 手写一个Promise

Promise规范:

  • 三种状态pending|resolved|rejected

  • 当处于pending状态的时候,可以转移到resolved或者rejected状态

  • 当处于resolved状态或者rejected状态的时候,就不可变

8.1 Promise流程图分析

Promise

Promise用法:

8.2 手写

8.3 大厂专供版

9 防抖 (Debounce) 和 节流 (Throttle)

9.1 防抖 (Debounce) 实现

典型例子: 限制 鼠标连击 触发。

一个比较好的解释是:

当一个事件发生后,事件处理器要等一定阈值的时间,如果这段时间过去后,再也没有事情发生,就处理最后一次发生的事情。假设还差0.01秒就到达指定之间,这时又来了一个事件,那么之前的等待作废,需要重新再等待指定时间

举一个小例子:假定在做公交车时,司机需等待最后一个人进入后再关门,每次新进一个人,司机就会把计时器清零并重新开始计时,重新等待 1 分钟再关门,如果后续 1 分钟内都没有乘客上车,司机会认为乘客都上来了,将关门发车。 此时「上车的乘客」就是我们频繁操作事件而不断涌入的回调任务;「1 分钟」就是计时器,它是司机决定「关门」的依据,如果有新的「乘客」上车,将清零并重新计时;「关门」就是最后需要执行的函数。

原理及实现

实现原理就是利用定时器,函数第一次执行时设定一个定时器,之后调用时发现已经设定过定时器就清空之前的定时器,并重新设定一个新的定时器,如果存在没有被清空的定时器,当定时器计时结束后触发函数执行。

9.2 节流 (Throttle) 实现

可以理解为事件在一个管道中传输,加上这个节流阀以后,事件的流速就会减慢。实际上这个函数的作用就是如此,它可以将一个函数的调用频率限制在一定阈值内,例如 1s,那么 1s 内这个函数一定不会被调用两次。

举一个小例子,不知道大家小时候有没有养过小金鱼啥的,养金鱼肯定少不了接水,刚开始接水时管道中水流很大,水到半满时开始拧紧水龙头,减少水流的速度变成 3 秒一滴,通过滴水给小金鱼增加氧气。 此时「管道中的水」就是我们频繁操作事件而不断涌入的回调任务,它需要接受「水龙头」安排;「水龙头」就是节流阀,控制水的流速,过滤无效的回调任务;「滴水」就是每隔一段时间执行一次函数,「3 秒」就是间隔时间,它是「水龙头」决定「滴水」的依据。

原理及实现

实现方案有两种

  • 第一种是用时间戳来判断是否已到执行时间,记录上次执行的时间戳,然后每次触发事件执行回调,回调中判断当前时间戳距离上次执行时间戳的间隔时候已经到达时间差,如果则执行,并更新上次执行的时间戳,如此循环。

  • 第二种就是使用定时器,比如当scroll时间刚触发时,打印一个hello world,然后设置一个1000ms的定时器,此后每次触发scroll事件触发回调,如果已经存在定时器,则回调不执行方法,直到定时器触发,handler被清除,然后重新设置定时器。

10. 手写一个深拷贝

有一个最著名的乞丐版实现,在《你不知道的 JavaScript(上)》里也有提及

10.1 乞丐版

10.2 网传圣杯模式

简约版实现

11. 实现一个instanceOf

12. 数组去重

13. 将浮点数点左边的数每三位添加一个逗号

如何判断一个对象是否属于某个类?

最后更新于