
如何在ES5环境下实现let
对于这个问题,我们可以直接查看babel转换前后的结果,看一下在循环中通过let定义的变量是如何解决变量提升的问题。
  
babel在let定义的变量前加了道下划线,避免在块级作用域外访问到该变量,除了对变量名的转换,我们也可以通过自执行函数来模拟块级作用域。
(function(){` for(var i = 0; i < 5; i ++){console.log(i) // 0 1 2 3 4}})();`console.log(i) // Uncaught ReferenceError: i is not defined
如何在ES5环境下实现const
实现const的关键在于Object.defineProperty()这个API,这个API用于在一个对象上增加或修改属性。
通过配置属性描述符,可以精确地控制属性行为。Object.defineProperty()接收三个参数:
Object.defineProperty(obj, prop, desc)  
  
对于const不可修改的特性,我们通过设置writable属性来实现。
function _const(key, value) {` const desc = {value,writable: false}Object.defineProperty(window, key, desc)}_const('obj', {a: 1}) //定义objobj.b = 2 //可以正常给obj的属性赋值`obj = {} //抛出错误,提示对象read-only
手写call()
call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
语法:function.call(thisArg, arg1, arg2, ...)
call()的原理比较简单,由于函数的this指向它的直接调用者js搜索框代码,我们变更调用者即完成this指向的变更:
//变更函数调用者示例`function foo() {console.log(this.name)}// 测试const obj = {name: '写代码像蔡徐抻'}obj.foo = foo // 变更foo的调用者`obj.foo() // '写代码像蔡徐抻'
基于以上原理, 我们两句代码就能实现call()
Function.prototype.myCall = function(thisArg, ...args) {` thisArg.fn = this // this指向调用call的对象,即我们要改变this指向的函数return thisArg.fn(...args) // 执行函数并return其执行结果}`
但是我们有一些细节需要处理:
Function.prototype.myCall = function(thisArg, ...args) {` const fn = Symbol('fn') // 声明一个独有的Symbol属性, 防止fn覆盖已有属性thisArg = thisArg || window // 若没有传入this, 默认绑定window对象thisArg[fn] = this // this指向调用call的对象,即我们要改变this指向的函数const result = thisArg[fn](...args) // 执行当前函数delete thisArg[fn] // 删除我们声明的fn属性return result // 返回函数执行结果}//测试`foo.myCall(obj) // 输出'写代码像蔡徐抻'
手写apply()
apply() 方法调用一个具有给定this值的函数,以及作为一个数组(或类似数组对象)提供的参数。
语法:func.apply(thisArg, [argsArray])
apply()和call()类似,区别在于call()接收参数列表,而apply()接收一个参数数组,所以我们在call()的实现上简单改一下入参形式即可。
Function.prototype.myApply = function(thisArg, args) {` const fn = Symbol('fn') // 声明一个独有的Symbol属性, 防止fn覆盖已有属性thisArg = thisArg || window // 若没有传入this, 默认绑定window对象thisArg[fn] = this // this指向调用call的对象,即我们要改变this指向的函数const result = thisArg[fn](...args) // 执行当前函数delete thisArg[fn] // 删除我们声明的fn属性return result // 返回函数执行结果}//测试`foo.myApply(obj, []) // 输出'写代码像蔡徐抻'
手写bind()
bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
语法: function.bind(thisArg, arg1, arg2, ...)
从用法上看,似乎给call/apply包一层function就实现了bind():
Function.prototype.myBind = function(thisArg, ...args) {` return () => {this.apply(thisArg, args)}`}
但我们忽略了三点:
bind()除了this还接收其他参数,bind()返回的函数也接收参数,这两部分的参数都要传给返回的函数。
new的优先级:如果bind绑定后的函数被new了,那么此时this指向就发生改变。此时的this就是当前函数的实例。
没有保留原函数在原型链上的属性和方法。
Function.prototype.myBind = function (thisArg, ...args) {` var self = this// new优先级var fbound = function () {self.apply(this instanceof self ? this : thisArg, args.concat(Array.prototype.slice.call(arguments)))}// 继承原型上的属性和方法fbound.prototype = Object.create(self.prototype);return fbound;}//测试const obj = { name: '写代码像蔡徐抻' }function foo() {console.log(this.name)console.log(arguments)}foo.myBind(obj, 'a', 'b', 'c')() //输出写代码像蔡徐抻 ['a', 'b', 'c']`
手写一个防抖函数
防抖和节流的概念都比较简单,所以我们就不在“防抖节流是什么”这个问题上浪费过多篇幅了,简单点一下:
防抖,即短时间内大量触发同一事件,只会执行一次函数,实现原理为设置一个定时器,约定在xx毫秒后再触发事件处理,每次触发事件都会重新设置计时器,直到xx毫秒内无第二次操作。
防抖常用于搜索框/滚动条的监听事件处理,如果不做防抖,每输入一个字/滚动屏幕,都会触发事件处理,造成性能浪费。
function debounce(func, wait) {` let timeout = nullreturn function() {let context = thislet args = argumentsif (timeout) clearTimeout(timeout)timeout = setTimeout(() => {func.apply(context, args)}, wait)}`}
手写一个节流函数
防抖是延迟执行,而节流是间隔执行,函数节流即每隔一段时间就执行一次,实现原理为设置一个定时器,约定xx毫秒后执行事件。
如果时间到了,那么执行函数并重置定时器,和防抖的区别在于js搜索框代码,防抖每次触发事件都重置定时器,而节流在定时器到时间后再清空定时器。
function throttle(func, wait) {` let timeout = nullreturn function() {let context = thislet args = argumentsif (!timeout) {timeout = setTimeout(() => {timeout = nullfunc.apply(context, args)}, wait)}}}`
实现方式2:使用两个时间戳prev旧时间戳和now新时间戳,每次触发事件都判断二者的时间差,如果到达规定时间,执行函数并重置旧时间戳。
function throttle(func, wait) {` var prev = 0;return function() {let now = Date.now();let context = this;let args = arguments;if (now - prev > wait) {func.apply(context, args);prev = now;}}}`
数组扁平化
对于[1, [1,2], [1,2,3]]这样多层嵌套的数组,我们如何将其扁平化为[1, 1, 2, 1, 2, 3]这样的一维数组呢:
(1)ES6的flat()
const arr = [1, [1,2], [1,2,3]]`arr.flat(Infinity)  // [1, 1, 2, 1, 2, 3]`(2)序列化后正则
const arr = [1, [1,2], [1,2,3]]`const str =[${JSON.stringify(arr).replace(/(\[|\])/g, '')}]`JSON.parse(str) // [1, 1, 2, 1, 2, 3]
(3)递归
对于树状结构的数据,最直接的处理方式就是递归
const arr = [1, [1,2], [1,2,3]]`function flat(arr) {let result = []for (const item of arr) {item instanceof Array ? result = result.concat(flat(item)) : result.push(item)}return result}flat(arr) // [1, 1, 2, 1, 2, 3]`
(4)reduce()递归
const arr = [1, [1,2], [1,2,3]]`function flat(arr) {return arr.reduce((prev, cur) => {return prev.concat(cur instanceof Array ? flat(cur) : cur)}, [])}flat(arr) // [1, 1, 2, 1, 2, 3]`
(5)迭代+展开运算符
// 每次while都会合并一层的元素,这里第一次合并结果为[1, 1, 2, 1, 2, 3, [4,4,4]]`// 然后arr.some判定数组中是否存在数组,因为存在[4,4,4],继续进入第二次循环进行合并let arr = [1, [1,2], [1,2,3,[4,4,4]]]while (arr.some(Array.isArray)) {arr = [].concat(...arr);}console.log(arr) // [1, 1, 2, 1, 2, 3, 4, 4, 4]`
- 本章完 -
#heading-48
  
你“在看”我吗?
发表评论