call函数
1 | Function.prototype.selfCall = function(context) { |
上面的方法借用了很多ES6的方法,让我们看一下比较原始的方法
1 | Function.prototype.call2 = function (context) { |
apply函数
1 | Function.prototype.selfApply = function (context) { |
不借用ES6
1 | Function.prototype.apply = function (context, arr) { |
关于 Object(context) 原文是这么说的:
非严格模式下,指定为 null 或 undefined 时会自动指向全局对象,郑航写的是严格模式下的,我写的是非严格模式下的,实际上现在的模拟代码有一点没有覆盖,就是当值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的自动包装对象。
bind函数
- 首先书写了一个简单的bind函数,并不包括 new 操作
1 | Function.prototype.myBind = function (ctx) { |
构造函数效果的模拟实现 // TODO
new 操作符
首先分析一下 new 操作符
1 | function Student(name, age) { |
从结果分析
- 返回了一个对象,其实例属性是通过构造函数(Student)生成的
- 对象的
__proto__
指向Student.prototype - 创建一个空对象obj({})
- 将obj的[[prototype]]属性指向构造函数constrc的原型(即obj.[[prototype]] = constrc.prototype)。
- 将构造函数constrc内部的this绑定到新建的对象obj,执行constrc(也就是跟调用普通函数一样,只是此时函数的this为新创建的对象obj而已,就好像执行obj.constrc()一样);
- 若构造函数没有返回非原始值(即不是引用类型的值),则返回该新建的对象obj(默认会添加return this)。否则,返回引用类型的值。
官方解释
- 创建一个空的简单JavaScript对象(即{});
- 链接该对象(设置该对象的constructor)到另一个对象 ;
- 将步骤1新创建的对象作为this的上下文 ;
- 如果该函数没有返回对象,则返回this。
实现方式如下:
1 | function createClass(_Class, ...rest) { |
利用Object.create稍微简化一下
1 | function newFactorSimple(ctor) { |
其实构造函数内含有return语句时,结果会出现差异
1 | // 例子4 |
前面六种基本类型都会正常返回{name: ‘若川’},后面的Object(包含Functoin, Array, Date, RegExg, Error)都会直接返回这些值
下面是考虑到各种情况后的new实现
1 | /** |
create函数
很多框架源码作者使用它来初始化一个新的对象,难道是最佳实践?
原因有二
- 通过Object.create(null)创建出来的对象,没有任何属性,显示No properties。我们可以将其当成一个干净的 map 来使用,自主定义 toString,hasOwnProperty等方法,并且不必担心将原型链上的同名方法被覆盖。
- {…}创建的对象,使用for in遍历对象的时候,会遍历原型链上的属性,带来性能上的损耗。使用Object.create(null)则不必再对其进行遍历了。
手写Object.create
1 | function ObjCreate(proto, properties) { |
new func()的作用是创建一个新的对象,其中func是一个构造函数,在这个过程中,主要包含了如下步骤:
- 创建空对象obj;
- 将obj的原型设置为构造函数的原型,obj.proto= func.prototype;
- 以obj为上下文执行构造函数,func.call(obj);
- 返回obj对象。
手写promise
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
- 对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
- 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
摘自阮一峰ES6深入理解
引入三个问题
这三个问题可以帮助理解
1、Promise 中为什么要引入微任务?
由于promise采用.then延时绑定回调机制,而new Promise时又需要直接执行promise中的方法,即发生了先执行方法后添加回调的过程,此时需等待then方法绑定两个回调后才能继续执行方法回调,便可将回调添加到当前js调用栈中执行结束后的任务队列中,由于宏任务较多容易堵塞,则采用了微任务
2、Promise 中是如何实现回调函数返回值穿透的?
首先Promise的执行结果保存在promise的data变量中,然后是.then方法返回值为使用resolved或rejected回调方法新建的一个promise对象,即例如成功则返回new Promise(resolved),将前一个promise的data值赋给新建的promise
3、Promise 出错后,是怎么通过“冒泡”传递给最后那个捕获
promise内部有resolved_和rejected_变量保存成功和失败的回调,进入.then(resolved,rejected)时会判断rejected参数是否为函数,若是函数,错误时使用rejected处理错误;若不是,则错误时直接throw错误,一直传递到最后的捕获,若最后没有被捕获,则会报错。可通过监听unhandledrejection事件捕获未处理的promise错误
简单的promise
1 | function MyPromise(executor) { |
进阶的promise
实现then的链式调用
1 | class MyPromise { |
限制promise并发
主要的点
- 在then方法里启动下一次任务
- while为了塞满队列
- task是一个待启动的promise
1 | class MaxDuty{ |
珂里化
第一版 比较简单,也好理解
1 | var curry = function (fn) { |
固定参数
第二版 稍微复杂一些,先看实现的效果会比较好理解一点
1 | var fn = curry(function (a, b, c) { |
首先需要明确1点
- 参数的个数是确定的,这也是递归调用的截止条件
来看实现过程
1 | function curry(fn) { |
不固定参数
这种方法太刻意了,建议闲下来的时候看看
1 | // fn(a,b,c) = fn(a)(b)(c) |
防抖
有一些需要注意的点,可以帮助理解函数,重点看代码中注释的地方
1 | <!DOCTYPE html> |
需要注意的是,setTimeout第一个参数需要是一个函数,而fn.apply(this, arguments)
不能算一个函数
下面写一个比较标准的debounce,可以正确响应参数和this
1 | function debounce(fn, wait) { |
立刻执行
这个时候,代码已经很是完善了,但是为了让这个函数更加完善,我们接下来思考一个新的需求。
这个需求就是:
我不希望非要等到事件停止触发后才执行,我希望立刻执行函数,然后等到停止触发 n 秒后,才可以重新触发执行。
其实这个实现也算简单,主要是理解需求
1 | function immedia(fn, wait) { |
写在一个函数里,也很简单
1 | function debounce(fn, wait, immediate) { |
节流
节流的原理很简单:
如果你持续触发事件,每隔一段时间,只执行一次事件。
关于节流的实现,有两种主流的实现方式,一种是使用时间戳,一种是设置定时器。
时间戳
1 | function throttle(fn, wait) { |
定时器
1 | function throttle(fn, wait) { |
deepclone
先看一个足够用的版本
1 | function deepClone(obj) { |
首先实现一个简单clone,包括对象 数组和基本类型的clone
1 | function deepClone(target) { |
如果出现循环引用,就会栈溢出,如何解决这个问题呢,需要借助map数据结构
解决循环引用问题,我们可以额外开辟一个存储空间,来存储当前对象和拷贝对象的对应关系,当需要拷贝当前对象时,先去存储空间中找,有没有拷贝过这个对象,如果有的话直接返回,如果没有的话继续拷贝,这样就巧妙化解的循环引用的问题。
这个存储空间,需要可以存储key-value
形式的数据,且key
可以是一个引用类型,我们可以选择Map
这种数据结构:
- 检查
map
中有无克隆过的对象 - 有 - 直接返回
- 没有 - 将当前对象作为
key
,克隆对象作为value
进行存储 - 继续克隆
1 | function deepClone(target) { |
控制台结果如下
1 | { |
circular
表示循环引用的意思,把Map换成weakMap,Map有强引用,不利用垃圾收集