20 个值得收藏的实用 JavaScript 技巧
1.确定对象的数据类型
function myType(type) {return Object.prototype.toString.call(type).slice(8, -1);使用 Object.prototype.toString,通过传入不同类型的判断返回不同的判断函数,一行代码,简洁优雅灵活;
2.循环遍历数组 map 方法
const myMap = function (fn, context) {let arr = Array.prototype.slice.call(this);let resultArr = Array();for (let i = 0; i < arr.length; i++) {if (!arr.hasOwnProperty(i)) continue;resultArr[i] = fn.call(context, arr[i], i, this);}return resultArr;};
Array.prototype.myMap = myMap;let arr = [1, 2, 3];console.log(arr.myMap((item) => item + 1)); // 2,3,4 值得注意的是,map 第二个参数在第一个参数回调中指向 this。如果第一个参数是箭头函数,则第二个 this 的设置无效。
3.循环遍历数组过滤方法
const myFilter = function (fn, context) {let arr = Array.prototype.slice.call(this)let resultArr = []for (let i = 0; i < arr.length; i++) {if(!arr.hasOwnProperty(i)) continue;fn.call(context, arr[i], i, this) && resultArr.push(arr[i])}return resultArr}Array.prototype.myFilter = myFilterlet arr = [1, 2, 3]console.log(arr.myFilter(item => item === 2)) // [2]4.使用 reduce 实现数组过滤方法
const myFilter2 = function (fn, context) {return this.reduce((total, current, index) => {return fn.call(context, current, index, this) ? [...total, current] : [...total]}, [])}5.遍历数组的一些方法
const mySome = function (fn, context) {let arr = Array.prototype.slice.call(this);// The empty array returns false directly, and the every method of the array returns true converselyif (!arr.length) return 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) return true;}return false;};
Array.prototype.mySome = mySome;
let arr = [1, 2, 3];console.log(arr.mySome((item) => item === 2));执行 some 的数组如果是空数组总是返回 false,而另一个数组的 every 方法中的数组如果是空数组总是返回 true。
6.通过循环实现数组的 reduce 方法
Array.prototype.myReduce = function (fn, initialValue) {let arr = Array.prototype.slice.call(this)let startItemlet startIndexif (initialValue === undefined) {// Finds the element and subscript of the first non-empty (real) unitfor (let i = 0; i < arr.length; i++) {if (!arr.hasOwnProperty(i)) continuestartIndex = istartItem = arr[i]break}} else {startItem = initialValue}// The starting point for traversal is the real element after the real element found in the previous step// Each iteration skips the elements of the empty cellfor (let i = ++startIndex || 0; i < arr.length; i++) {if (!arr.hasOwnProperty(i)) continuestartItem = fn.call(null, startItem, arr[i], i, this)}return startItem}
Array.prototype.myReduce = myReduce
let arr = [1, 2, 3]
console.log(arr.myReduce((acc, cur) => acc + cur)) // 6console.log(arr.reduce((acc, cur) => acc + cur)) // 67.使用 reduce 实现 array 的 flat 方法
// reduce implements array.prototype.flat, Array flatconst myFlat = function (depth = 1) {let arr = Array.prototype.slice.call(this)if (depth === 0) return arrreturn arr.reduce((total, current) => {if (Array.isArray(current)) {// You need to bind this with call, otherwise it points to the windowreturn [...total, ...myFlat.call(current, depth-1)]} else {return [...total, current]}}, [])}
Array.prototype.myFlat = myFlatlet arr = [1, 2, [3, 4, [5, 6,['a','b','c',['d']], 7, 8], 9], 10, 11, 12, [13, 14]]console.log(arr.myFlat()) 因为 myFlat 依赖这个指向,所以需要在 reduce 遍历的时候指定 myFlat 的这个指向;否则默认指向 window,会报错。
当数组的元素还是数组时,使用 ES6 的扩展运算符对其进行降维(ES5 中可以使用 concat 方法)。但是数组元素内部可能有嵌套数组,所以需要递归调用 selfFlat。
同时,原生的 Flat 方法支持一个深度参数来表示降维的深度。默认值为 1,表示数组减少一维。
传递 Infinity 将传递的数组变成一维数组:
8.实现 ES6 类语法
function Animal(name) {this.name = name}
Animal.staticFunc = function () {console.log('staticFunc')}Animal.prototype.sleep = function () {console.log('animal is sleeping')}
//Parasitic combinatorial inheritance + inheritance between constructorsfunction Dog(name, color) {Animal.call(this, name)this.color = color}
function inherit(subType, superType) {//Due to the nature of JavaScript reference types and functions passing by value, you cannot change the reference address of subTypesubType.prototype = Object.create(superType.prototype, {constructor: {enumerable: false,configurable: true,writable: true,// Points to subclasses, consistent with the default inheritance behaviorvalue: subType}})//The child constructor inherits the parent constructor (the child inherits the static methods and static properties of the parent class)Object.setPrototypeOf(subType, superType)}
inherit(Dog, Animal)
//You need to add the prototype method to Dog after inheritance, otherwise it will be overwrittenDog.prototype.barking = function () {console.log('wang!')}
let brownTeddy = new Dog('teddy', 'brown')Dog.staticFunc()console.log(brownTeddy)brownTeddy.sleep()brownTeddy.barking()Create 方法创建一个空 Object,并从 Object.create 方法的参数中继承这个空 Object。然后让子类的原型(subType)等于空对象,就可以实现子类的原型等于空对象,空对象等于父类的继承原型。
Object.create 支持第二个参数,它为生成的空对象定义属性和属性/访问器描述符。我们可以给这个空对象一个更符合默认继承行为的构造函数属性。它也是一个不能枚举的内部属性(Enumerable: False)。
ES6 类允许子类从父类继承静态方法和静态属性,而普通的寄生组合继承只能在实例之间实现。对于类到类的继承,需要定义额外的方法。
这里我们使用 Object.setProtoTypeof 将 superType 设置为 subType 的原型,从而能够从父类继承静态方法和静态属性。
9.函数的焦化
const display = (a, b, c, d, e, f) => [a, b, c, d, e, f];
/**
@description Currization of a function (How many times a currization function needs to be executed according to the number of parameters of the function before currization)
@param {function} fn -The Currified function*/
function curry(fn) {if (fn.length <= 1) return fn;const generator = (...args) => {if (fn.length === args.length) {//Executes fn and returns the execution resultreturn fn(...args)} else {return (...args2) => {//Return generator functionreturn generator(...args, ...args2)}}}return generator}
const curriedDisplay = curry(display);console.log("curriedDisplay", curriedDisplay(1)(2)(3)(4)(5)(6));Currization 是函数式编程中的一项重要技术,该技术将一个接受多个参数的函数转换为一系列接受一个参数的函数。
函数式编程 compose 另一个重要的功能,并且要能够进行函数组合,函数的组合只接受一个参数,所以如果你必须接受多个函数的需求并且需要使用 compose 函数组合,就需要使用 compose 的部分 curry 准备复合函数,让它总是只接受一个参数。
10.函数修正(占位符支持)
const curry3 = (fn, placeholder = "_") => {curry3.placeholder = placeholderif (fn.length <= 1) return fn;let argsList = []const generator = (...args) => {let currentPlaceholderIndex = -1args.forEach(arg => {let placeholderIndex = argsList.findIndex(item => item === curry3.placeholder)if (placeholderIndex < 0) {currentPlaceholderIndex = argsList.push(arg) - 1
}
const curriedDisplay3 = curry3(display);console.log("curriedDisplay3", curriedDisplay3('', 2)(1, '', 4)(3, '',)('', 5)(6)(7, 8))如果当前轮参数包含占位符,则将其放置在内部保存数组的末尾。当前轮的元素不填充当前轮参数的占位符,而只填充之前传入的占位符。
11.斐波那契数列及其优化
const speed = function (fn, num) {console.time('time')let value = fn(num)console.timeEnd('time')console.log(result:${value}
)}
/**
@description Fibonacci numbers
@param {number} n -Number of positions
@return {number} The argument corresponds to a number in a sequence**/let fibonacci = function (n) {if (n < 1) throw new Error('Parameter is wrong')if (n === 1 || n === 2) return 1return fibonacci(n - 1) + fibonacci(n - 2)}
speed(fibonacci, 40)
//Memory functionconst memory = function (fn) {let obj = {}return function (n) {if (obj[n] === undefined) obj[n] = fn(n)return obj[n]}}fibonacci = memory(fibonacci)
speed(fibonacci, 40)
/**
@description Fibonacci dynamic programming version (Optimal)**/function fibonacci_DP(n) {let res = 1if (n === 1 && n === 2) return resn = n - 2let cur = 1let pre = 1while (n) {res = cur + prepre = curcur = resn--}return res}
speed(fibonacci_DP, 40)使用函数内存,可以为经常依赖先前结果的计算节省大量时间,例如斐波那契数列。缺点是闭包中的 obj 对象占用了额外的内存。
另外,动态规划的空间复杂度比前者低,也是比较推荐的方案。
12.实现绑定方法
const isComplexDataType = obj => (typeof obj === 'object' || typeof obj === 'function') && obj !== null
// Implement a simple bindconst myBind = function (bindTarget, ...args1) {if (typeof this !== 'function') throw new TypeError('Bind must be called on a function')const originFunc = thisconst boundFunc = function (...args2) {// Calls using the new keyword return a new objectif (new.target) {let res = originFunc.call(this, ...args1, ...args2)//If the constructor returns an object, that object is returnedif (isComplexDataType(res)) return res//Otherwise, the newly created object is returnedreturn this} else {return originFunc.call(bindTarget, ...args1, ...args2)}}if (originFunc.prototype) {boundFunc.prototype = originFunc.prototype}
}实现函数的 bind 方法的核心使用调用绑定指向 this,同时考虑到其他情况如:
当 bind 返回的函数作为构造函数被 new 调用时,绑定值失效,变为 new 指定的对象。定义绑定函数的 length 和 name 属性(不可枚举的属性)。绑定函数的 prototype 必须指向 prototype 原函数的 。13.实现调用方法
const myCall = function (context, ...args) {let func = thiscontext || (context = window)if (typeof func !== 'function') throw new TypeError('this is not function')let caller = Symbol('caller')context[caller] = funclet res = contextcallerdelete context[caller]return res}原理是将函数作为 context 传入参数的属性执行。ES6 Symbol 类型用于防止属性冲突。
14.简单的 CO 模块
//Self-executing generator functions
const data = "{a:1,b:2}";const data2 = "{c:3,d:4}";const data3 = "{e:5,f:6}";
const api = function (data) {return new Promise((resolve) => {setTimeout(() => {resolve(data);}, 1000);});};
function* func() {let res = yield api(data);console.log(res);let res2 = yield api(data2);console.log(res2);let res3 = yield api(data3);console.log(res3);console.log(res, res2, res3);}
function makePromisify(source) {if (source.then && typeof source.then === "function") return source;return Promise.resolve(source);}
function run(generatorFunc) {let it = generatorFunc();let result = it.next();
return new Promise((resolve, reject) => {const next = function (result) {if (result.done) {return resolve(result.value);}result.value = makePromisify(result.value);result.value.then((res) => {let result = it.next(res);//Recursively execute the next functionnext(result);}).catch((err) => {reject(err);});};next(result);});}
run(func);run 函数接受一个生成器函数,每次 run 函数包裹的生成器函数遇到 yield 关键字时停止,当 yield 后的 promise 成功解析时,自动调用 next 方法执行到下一个 yield 关键字。
最后,每次成功解析一个 promise,都会解析下一个 promise。
当所有的结果都解析成功后,所有解析的结果都会被打印出来,演变成今天最常用的 async/await 语法。
15.功能防抖
/**
@description debounce
@param {Function} func -Functions that need function stabilization
@param {Number} time -Delay time
@param {Options} options -Configuration items
@return {Function} -A function that has been shaken out**/
/**
@typedef {Object} Options -Configuration items
@property {Boolean} leading -Whether an extra trigger is required to start
@property {Boolean} trailing -Whether an additional trigger is required after the end
@property {this} context -this**/
const debounce = (func, time = 20, options = {leading: true,context: null}) => {let timer;const _debounce = function (...args) {if (timer) {clearTimeout(timer)}if (options.leading && !timer) {timer = setTimeout(null, time)func.apply(options.context, args)}else{timer = setTimeout(() => {func.apply(options.context, args)timer = null}, time)}};
};16.函数节流
/**
@description throttle
@param {Function} func -Functions that require function throttling
@param {Number} time -Delay time
@param {Options} options -Configuration items
@return {Function} -经过节流处理的函数**/
/**
@typedef {Object} Options -Configuration items
@property {Boolean} leading -Whether an extra trigger is required to start
@property {Boolean} trailing -Whether an additional trigger is required after the end
@property {this} context -this**/
const throttle = (func, time = 17, options = {// leading 和 trailing 无法同时为 falseleading: true,trailing: false,context: null}) => {let previous = new Date(0).getTime()let timer;const _throttle = function (...args) {let now = new Date().getTime();
};添加尾随选项以指示是否在序列结束时触发附加事件。
17.图片的延迟加载
// getBoundingClientRect lazy Loadlet imgList1 = [...document.querySelectorAll(".get_bounding_rect")]let num = imgList1.length
let lazyLoad1 = (function () {let count = 0return function () {let deleteIndexList = []imgList1.forEach((img,index) => {let rect = img.getBoundingClientRect()if (rect.top < window.innerHeight) {img.src = img.dataset.src// Add the image to the remove list after loading successfullydeleteIndexList.push(index)count++if (count === num) {//Unbind the Scroll event when all images are loadeddocument.removeEventListener('scroll',lazyLoad1)}}})// Delete images that have been loadedimgList1 = imgList1.filter((_,index)=>!deleteIndexList.includes(index))
})()
// The throttling function of throttle.js is referenced herelazyLoad1 = proxy(lazyLoad1, 100)
document.addEventListener('scroll', lazyLoad1)// Manually load the image once. Otherwise, the image on the first screen cannot be loaded without triggering scrollinglazyLoad1()
// intersectionObserver lazy Loadlet imgList2 = [...document.querySelectorAll(".intersection_observer")]
let lazyLoad2 = function () {// instantiation observerlet observer = new IntersectionObserver(entries => {entries.forEach(entry => {if (entry.intersectionRatio > 0) {entry.target.src = entry.target.dataset.srcobserver.unobserve(entry.target)}})})imgList2.forEach(img => {observer.observe(img)})}
lazyLoad2()getBoundClientRect 的实现监听滚动事件(建议为监听事件添加节流)。图片加载完成后,会从 img 标签组成的 DOM 列表中删除。最后,加载监听器事件后,所有图像都需要解除绑定。
IntersectionObserver 是通过实例化一个 intersectionObserver 并使其观察所有 IMG 标签来实现的。
当 img 标签进入查看区域时,实例化时执行回调。同时传入一个回调,保存实例来观察所有元素的某种状态,比如每个元素的边界,当前元素对应的 DOM 节点,当前元素进入查看区域的比例。每当一个元素进入查看区域时,将真实图像分配给当前 IMG 标签,同时不观察它。
18.新关键字
const isComplexDataType = obj => (typeof obj === 'object' || typeof obj === 'function') && obj !== null
const myNew = function (fn, ...rest) {let instance = Object.create(fn.prototype)let res = fn.call(instance, ...rest)return isComplexDataType(res) ? res : instance}
function Person(name, sex) {this.name = namethis.sex = sex}
let newPerson = new Person('tony', 'woman')let myNewPerson = myNew(Person, 'tony1', 'man')
console.log(newPerson)console.log(myNewPerson)19.实现对象分配
"use strict"
const isComplexDataType = obj => (typeof obj === 'object' || typeof obj === 'function') && obj !== null
const myAssign = function (target, ...source) {if (target == null) throw new TypeError('Cannot convert undefined or null to object')return source.reduce((acc, cur) => {isComplexDataType(acc) || (acc = new Object(acc));if (cur == null) return acc;[...Object.keys(cur), ...Object.getOwnPropertySymbols(cur)].forEach(key => {acc[key] = cur[key]})return acc}, target)}
Object.myAssign = myAssign
let target = {a: 1,b: 1}
let obj1 = {a: 2,b: 2,c: undefined}
let obj2 = {a: 3,b: 3,[Symbol("a")]: 3,d: null}
console.log(Object.myAssign(target, obj1, obj2))console.log(Object.myAssign("abd", null, undefined))20.实例化
const myInstanceof = function (left, right) {let proto = Object.getPrototypeOf(left)while (true) {if (proto == null) return falseif (proto === right.prototype) {return true}proto = Object.getPrototypeOf(proto)}}
console.log(myInstanceof({}, Array))结论
到这里,我们我们终于得到它了。20 个出色的技巧,可帮助您编写更好、更高效的代码。阅读前你知道多少?
最后推荐一套好不错的 JavaScript 从入门到精通全套完整版教程,有需要的小伙伴建议收藏学习!
评论