这段时间听同事面试校招生,发现自己原生js很弱,需要狂补,以下为看《一个合格的中级前端工程师要掌握的javaScript技巧》 有感
背景 1、判断对象的数据类型 1 2 3 const isType = type => target => `[Object ${type} ]` === Object.prototype.toString.call(target); const isArray = isType('Array' ); console.log(isArray([])); // true
以上isType为高阶函数,可翻译如下:
1 2 3 4 5 6 const isType = (type ) => { (target) => { return `[Object ${type} ]` === Object.prototype.toString.call(target); } }
使用 Object.prototype.toString 配合闭包,通过传入不同的判断类型来返回不同的判断函数,一行代码,简洁优雅灵活(注意传入type传入参数时首字母大写)
不推荐将这个函数用来检测可能会产生包装类型的基本数据类型上,因为call会将第一个参数进行装箱操作
2、es5实现数组map方法 对于map方法的运用,我们可以在mdn 中查看到对应的用法,如下
1 2 3 4 5 6 7 8 9 const selfMap = function (fn, context) { let arr = Array.prototype.slice.call(this); let mappedArr = []; for (let i = 0; i< arr.length; i++) { if (!arr.hasOwnProperty(i)) continue ; mappedArr.push(fn.call(context, arr[i], i, this))) } return mappedArr; }
值得一提的是,map的第二个参数为第一个参数回调中的this指向,如果第一个参数为箭头函数,那设置第二个this会因为箭头函数的词法绑定而失败。 另外就是对稀疏数组的处理,通过hasOwnProperty来判断当前下标的元素是否存在与数组中
3.使用reduce实现数组map方法 1 2 3 4 5 6 7 const selfMap2 = function (fn, context) { let arr = Array.prototype.slice.call(this); return arr.reduce((pre,cur,index) => { return [...pre,fn.call(context, cur, index, this)] }, []) }
4.ES5实现数组filter方法 1 2 3 4 5 6 7 8 9 const selfFilter = function (fn, context) { let arr = Array.prototype.slice.call(this); let filteredArr = []; for (let i = 0; i< arr.length; i++) { if (!arr.hasOwnProperty(i)) continue ; fn.call(context, arr[i], i, this) && filteredArr.push(arr[i]); } return filteredArr; }
5.使用reduce实现数组filter方法 1 2 3 4 5 const selfFilter2 = function (fn, context){ return this.reduce((pre, cur, index) => { return fn.call(context, cur, index, this) ? [...pre, cur] : [...pre] }, []) }
6. ES5实现数组的some方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const selfSome = function (fn, context) { let arr = Array.prototype.slice.call(this); if (!arr.length) return false ; let flag = false ; for (let i = 0; i < arr.length; i++) { if (!arr.hasOwnProperty(i)) continue ; let res = fn.call(context, arr[i], i, this); if (res) { flag = true ; break ; } } return flag; }
7.ES5实现数组的reduce方法 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 /** @description 找到第一个empty的元素的下标 @param {Array} arr - 参看说明 @param {Number} {initIndex} - 遍历的起始下标 @return {Number} */ const findRealElementIndex = function (arr, initIndex) { let index; for (let i = initIndex || 0, i < arr.length; i++){ if (!arr.hasOwnProperty(i)) continue ; index = i; return index; } } const selfReduce = function (fn, initValue) { let arr = Array.prototype.slice.call(this); let res; if (initValue === undefined) { res= = arr[findRealElementIndex(arr)]; for (let i = 0; i < arr.length - 1; i++){ if (!arr.hasOwnProperty(i)) continue ; let realElementIndex = findRealElementIndex(arr, i+1); res = fn.call(null, res, arr[realElementIndex], realElementIndex, this); } else { res = initValue; for (let i = 0; i < arr.length; i++) { if (!arr.hasOwnProperty(i)) continue ; res = fn.call(null, res, arr[i], i, this); } } } return res; }
因为可能存在稀疏数组的关系,所以reduce实现略有点复杂,需要保证跳过稀疏数组,遍历正确的元素和下标
8.使用reduce 实现数组的flat方法 1 2 3 4 5 6 7 8 9 10 11 const selfFlat = function (depth = 1) { let arr = Array.prototype.slice.call(this); if (depth === 0) return arr; return arr.reduce((pre, cur) => { if (Array.isArray(cur)) { return [...pre, ...selfFlat.call(cur, depth - 1)] } else { return [...pre, ...cur] } }, []) }
因为slefFlat是依赖this指向的,所以在reduce遍历时需要指定slefFlat的this指向,否则会默认指向window从而发生错误。 远离通过reduce遍历数组,遇到数组的某个元素仍是数组,通过ES6的扩展运算符对其进行降维(ES5可以使用concat方法),而这个数组元素可能内部还嵌套数组,所以需要递归调用slefFlat 同时原生的falt方法支持一个depth参数表示较为的深度,默认为1即给数组降一层维度。
1 2 3 4 5 6 let arr = [1, 2, [3, 4], [5, [6, 7]]];arr.flat(); // 返回为 [1, 2, 3, 4,5, [6, 7]] // 传入Inifity 会将传入的数组变成一个一维数组 arr.falt(Infinity) // [1, 2, 3, 4, 5, 6, 7]
原理是每递归一次将depth参数减1,如果depth参数为0时,直接返回原数组
9.实现ES6的classs语法 1 2 3 4 5 6 7 8 9 10 11 12 function inhertit(subType, superType) { subType.prototype = Object.create(superType.prototype, { contructor: { enumerable: false , configurable: true , writable: true , value: superType.contructor } }) // 继承递归 Object.setPrototypeOf(subType, superType); }
ES6的class内部时基于寄生组合式继承,它是目前最理想的继承方式,通过Object.create方法创造一个空对象,并将这个空对象继承Object.create方法的参数,再让子类(subType)的原型对象等于这个空对象,就可以实现子类实例的原型等于这个空对象,而这个空对象的原型又等于父类原型对象(superType.peototype)的继承关系。
而Object.create支持第二个参数,即给生成的空对象定义属性和属性描述符/访问器描述符,我们可以给这个空对象定义一个contructor 属性更加符合默认的继承行为,同时它是不可枚举的内部属性(enumerable: false)
而ES6的class允许子类继承父类的静态方法和静态属性,而普通的寄生组合式继承只能做到实例与实例之间的继承,对于类与类之间的继承需要额外定义方法,这里使用Object.setPrototypeOf将superType设置为subType的原型,从而能够从父类中继承静态方法和静态属性。