写这些主要是为了回顾与沉淀,再次编写的过程当中也会提出本身的一些理解,有不恰当的地方但愿指出。
在这里推荐一个线上 IDE, 我没事敲一些代码 demo 就用它了 StackBlitz,仍是比较好使的,可是推荐在设置中改成保存的时候再更新,否则编写的时候更新太频繁会常常出问题。
编程

实现 new
先用文字描述一下 new 的实现过程json
- 定义一个 json 对象
- 对象 继承 构造函数的原型链
- 将构造函数的 this 指向这个 json 对象
- 根据构造函数的返回值类型返回结果,
function myNew(fn) { let obj = {} obj.__proto__ = Object.create(fn.prototype) let args = Array.prototype.slice.call(arguments, 1) // 获取除去fn以外的参数 let result = fn.call(obj, ...args) return typeof result === 'object' ? result : obj; } function foo() { this.name = 'ciel' this.arg = arguments[0] } foo.prototype.callName = function() { console.log(this.name) } // 测试 let test = myNew(foo, 'hhh', '123', 'saf') test.callName() console.log(test.arg) 复制代码
这里解释一下 return typeof result === 'object' ? result : obj;
这句代码:
在JavaScript构造函数中:若是return值类型,那么对构造函数没有影响,实例化对象返回空对象;若是return引用类型(数组,函数,对象),那么实例化对象就会返回该引用类型; 能够测试如下两个构造函数在 new 以后返回的值就能够理解这句话的意思了
数组
function foo() { this.name = 'ciel' return function() { } } new foo() // fn(){} function bar() { this.name = 'ciel' return 1 } new bar() // {name: ciel} 复制代码
实现 call
先看看伪代码是如何使用 myCall 的 fn.myCall(obj, args)
分析下代码应该怎么实现bash
- myCall 应该挂在 Function.prototype 上
- fn 的 this 指向 为 obj
- myCall 的 args 透传给 fn
Function.prototype.myCall = function(target, ...args) { // this 指向调用 myCall函数的对象 if (typeof this !== "function") { throw new TypeError("not a function") } target = target || window target.fn = this // 隐式绑定,改变构造函数的调用者间接改变 this 指向 let result = target.fn(...args) return result }; // 测试 var obj = { name: 123 } function foo(...args) { console.log(this.name, args) } var s = foo.myCall(obj, '111', '222') 复制代码
实现 apply
回忆一下 apply 与 call 的区别: apply 参数要为数组。 其余和 call 实现同样app
Function.prototype.myApply = function(target) { if (typeof this !== "function") { throw new TypeError("not a function"); } if (!Array.isArray(arguments[1])) { throw new Error('arg not a array') } target = target || window target.fn = this let args = arguments[1] let result = target.fn(args) return result }; var obj = { name: 123 }; function foo(...args) { console.log(this.name, args); } foo.prototype.name = 123; var s1 = [1, 2, 3, 4, 5]; var s = foo.myApply(obj,s1); 复制代码
实现 bind
- 与 call 与 apply 的区别: fn.bind(obj) 不会当即执行 fn 函数,而 call, apply 会当即执行
- bind 返回的新函数能够普通调用也能够构造函数方式调用,当为构造函数时,this 是指向实例的
- bind() 方法的参数具备一个特性,就是函数柯里化,简单来讲就是保留一个参数的位置,再第二次传参的时候自动把参数存入到这个位置中
Function.prototype.mybind = function(thisArg) { if (typeof this !== 'function') { throw TypeError("Bind must be called on a function"); } // 拿到参数,为了传给调用者 const args = Array.prototype.slice.call(arguments, 1), self = this, // 构建一个干净的函数,用于保存原函数的原型 nop = function() {}, // 绑定的函数 bound = function() { // this instanceof nop, 判断是否使用 new 来调用 bound // 若是是 new 来调用的话,this的指向就是其实例, // 若是不是 new 调用的话,就改变 this 指向到指定的对象 o return self.apply( this instanceof nop ? this : thisArg, args.concat(Array.prototype.slice.call(arguments)) ); }; // 箭头函数没有 prototype,箭头函数this永远指向它所在的做用域 if (this.prototype) { nop.prototype = this.prototype; } // 修改绑定函数的原型指向 bound.prototype = new nop(); return bound; } // 测试 let obj = { name: "ciel" } function test(x,y,z) { console.log(this.name) // ciel console.log(x+y+z) // 6 } var Bound = test.mybind(obj, 1, 2) Bound(3) // 6 复制代码
实现 reduce
arr.reduce((res,cur, index, arr) => res+cur, 0)
函数
- 参数: 一个回调函数,一个初始化参数 (非必须)
- 回调函数参数有 4 个值(res: 表明累加值,cur: 目前值,index: 第几个,arr 调用 reduce 的数组)
- 总体返回 res 累加值
Array.prototype.myReduce = function(cb, initValue) { if (!Array.isArray(this)) { throw new TypeError("not a array") } // 数组为空,而且有初始值,报错 if (this.length === 0 && arguments.length < 2) { throw new TypeError('Reduce of empty array with no initial value') } let arr = this let res = null // 判断有没有初始值 if (arguments.length > 1) { res = initValue } else { res = arr.splice(0,1)[0] //没有就取第一个值 } arr.forEach((item, index) => { res = cb(res, item, index, arr) // cb 每次执行完都会返回一个新的 res值,覆盖以前的 res }) return res }; // 测试结果 let arr = [1,2,3,4] let result = arr.myReduce((res, cur) => { return res + cur }) console.log(result) // 10 复制代码
tip: 平时在工做中 处理数据的时候常常会用到 reduce, 实现一个数据处理本来屡次遍历,由 reduce 实现可能就只须要遍历一次测试
实现 Currying
什么是柯里化? 将复杂问题分解为多个可编程的小问题,实现多参函数提供了一个递归降解的实现思路——把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,而且返回接受余下的参数并且返回结果的新函数 结合一个例子,实现以下效果this
sum(1,2) // 3
sum(1,2)(3) // 6
sum(4,5)(10) // 19
复制代码
实现代码spa
function sum() { let allArgs = Array.prototype.slice.call(arguments); let add = function(){ allArgs.push(...arguments) // 每次调用 sum 函数都收集参数 return add } // 重写 toString 方法,函数执行的时候会自动调用toString()方法,计算返回全部参数结果 add.toString = function () { return allArgs.reduce((a, b) => a+b) } return add } 复制代码
测试结果prototype

实现防抖
防抖:触发高频事件后 n 秒内函数只会执行一次,若是 n 秒内高频事件再次被触发,则从新计算时间(取最后一次) 思路:每次触发前都取消以前的延时调用方法
function debounce(fn, delay) { let timer = null return function() { let self = this // 这获取 this 是由于 debounce() 返回的是内部函数,在这才能捕获到 this。 let args = Array.prototype.slice.call(arguments) if (timer) clearTimeout(timer) // 取消以前的 timer setTimeout(function () { fn.call(self, ...args) // 防止 this 指向改变,确保上下文为当前的this,传递参数 }, delay) } } function testFn() { console.log('被点击了', this) } // 测试 document.addEventListener('click', debounce(testFn, 1000)) 复制代码
实现节流
节流:高频事件触发,但在 n 秒内只会执行一次,因此节流会稀释函数的执行频率 思路:每次触发事件时都判断当前是否有等待执行的延时函数,须要一个标记
function throtting(fn, delay) { let timer = null let isCancel = false return function() { if (isCancel) return isCancel = true clearTimeout(timer) let self = this; let args = Array.prototype.slice.call(arguments) if (timer) clearTimeout(timer) setTimeout(function () { fn.call(self, ...args) isCancel = false }, delay) } } function testFn() { console.log('输入了', this) } document.addEventListener('input', throtting(testFn, 1000)) 复制代码
在必定时间内只执行一次,判断当前是否有等待执行的延时函数,有就返回