20 trucos de JavaScript que los desarrolladores front-end deben saber

1. Determinar el tipo de datos del objeto

function myType(type) {
  return Object.prototype.toString.call(type).slice(8, -1);
复制代码

Use Object.prototype.toString para devolver diferentes funciones de juicio al pasar diferentes tipos de juicios, una línea de código, conciso, elegante y flexible.

2. Recorra el método del mapa de matriz

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
复制代码

Vale la pena señalar que el segundo parámetro del mapa apunta a esto en la devolución de llamada del primer parámetro. Si el primer argumento es una función de flecha, la segunda configuración de este no tiene efecto debido a la vinculación léxica de las funciones de flecha.

3. Recorra el método de filtro de matriz

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 = myFilter
let arr = [1, 2, 3]
console.log(arr.myFilter(item => item === 2)) // [2]
复制代码

4. Use reduce para implementar el método de filtro de matriz

const myFilter2 = function (fn, context) {
    return this.reduce((total, current, index) => {
        return fn.call(context, current, index, this) ? [...total, current] : [...total]
    }, [])
}
复制代码

5. Algún método para atravesar una matriz

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 conversely
  if (!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));
复制代码

La matriz que ejecuta alguna siempre devuelve falso si es una matriz vacía, y la matriz en el método cada de otra matriz siempre devuelve verdadero si es una matriz vacía.

6. Implementar el método reduce de una matriz a través de un bucle

Array.prototype.myReduce = function (fn, initialValue) {
    let arr = Array.prototype.slice.call(this)
    let startItem
    let startIndex
    if (initialValue === undefined) {
        // Finds the element and subscript of the first non-empty (real) unit
        for (let i = 0; i < arr.length; i++) {
            if (!arr.hasOwnProperty(i)) continue
            startIndex = i
            startItem = 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 cell
    for (let i = ++startIndex || 0; i < arr.length; i++) {
        if (!arr.hasOwnProperty(i)) continue
        startItem = 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)) // 6
console.log(arr.reduce((acc, cur) => acc + cur)) // 6
复制代码

7. Use reduce para implementar el método plano de una matriz

// reduce implements array.prototype.flat, Array flat
const myFlat = function (depth = 1) {
    let arr = Array.prototype.slice.call(this)
    if (depth === 0) return arr
    return arr.reduce((total, current) => {
        if (Array.isArray(current)) {
            // You need to bind this with call, otherwise it points to the window
            return [...total, ...myFlat.call(current, depth-1)]
        } else {
            return [...total, current]
        }
    }, [])
}

Array.prototype.myFlat  = myFlat
let arr = [1, 2, [3, 4, [5, 6,['a','b','c',['d']], 7, 8], 9], 10, 11, 12, [13, 14]]

console.log(arr.myFlat())
复制代码

Debido a que myFlat depende de este punto, es necesario especificar este punto de myFlat cuando se reduce el recorrido; de lo contrario, apunta a la ventana de forma predeterminada y se informará un error.

Cuando los elementos de la matriz siguen siendo matrices, use el operador de extensión ES6 para reducir la dimensión (el método concat se puede usar en ES5). Pero puede haber matrices anidadas dentro de los elementos de la matriz, por lo que se debe llamar a selfFlat de forma recursiva.

8. Implementar la sintaxis de clase 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 constructors
function 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 subType
    subType.prototype = Object.create(superType.prototype, {
        constructor: {
            enumerable: false,
            configurable: true,
            writable: true,
            // Points to subclasses, consistent with the default inheritance behavior
            value: 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 overwritten
Dog.prototype.barking = function () {
    console.log('wang!')
}


let brownTeddy = new Dog('teddy', 'brown')
Dog.staticFunc()
console.log(brownTeddy)
brownTeddy.sleep()
brownTeddy.barking()
复制代码

El método Create crea un Objeto vacío y hereda este Objeto vacío de los parámetros del método Object.create. Luego haga que el prototipo de la subclase (subTipo) sea igual al objeto vacío, puede darse cuenta de que el prototipo de la subclase es igual al objeto vacío y el objeto vacío es igual al prototipo heredado de la clase principal.

Object.create admite un segundo parámetro que define propiedades y descriptores de acceso/propiedad para el objeto vacío resultante. Podemos darle a este objeto vacío una propiedad de constructor que coincida más con el comportamiento de herencia predeterminado. También es una propiedad interna que no se puede enumerar (Enumerable: False).

Las clases de ES6 permiten que las subclases hereden métodos estáticos y propiedades estáticas de las clases principales, mientras que la herencia de composición parasitaria ordinaria solo se puede lograr entre instancias. Para la herencia de clase a clase, es necesario definir métodos adicionales.

Aquí usamos Object.setProtoTypeof para establecer superType como el prototipo de subType, pudiendo así heredar métodos estáticos y propiedades estáticas de la clase principal.

9、Coriolization of functions

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 result
            return fn(...args)
        } else {
            return (...args2) => {
                //Return generator function
                return 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、Function Corrification (placeholder support)

const curry3 = (fn, placeholder = "_") => {
    curry3.placeholder = placeholder
    if (fn.length <= 1) return fn;
    let argsList = []
    const generator = (...args) => {
        let currentPlaceholderIndex = -1 
        args.forEach(arg => {
            let placeholderIndex = argsList.findIndex(item => item === curry3.placeholder)
            if (placeholderIndex < 0) {
                currentPlaceholderIndex = argsList.push(arg) - 1
               
                // (1,'_')('_',2)
            } else if (placeholderIndex !== currentPlaceholderIndex) {
                argsList[placeholderIndex] = arg
            } else { 
                argsList.push(arg)
            }
        })
        let realArgsList = argsList.filter(arg => arg !== curry3.placeholder) 
        if (realArgsList.length >= fn.length) {
            return fn(...argsList)
        } else {
            return generator
        }
    }

    return generator
}

const curriedDisplay3 = curry3(display);
console.log("curriedDisplay3", curriedDisplay3('_', 2)(1, '_', 4)(3, '_',)('_', 5)(6)(7, 8))
复制代码

如果当前轮参数包含占位符,则将其放置在内部保存数组的末尾。当前轮的元素不填充当前轮参数的占位符,而只填充之前传入的占位符。

11、Fibonacci sequence and its optimization

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 1
    return fibonacci(n - 1) + fibonacci(n - 2)
}

speed(fibonacci, 40)


//Memory function
const 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 = 1
    if (n === 1 && n === 2) return res
    n = n - 2
    let cur = 1
    let pre = 1
    while (n) {
        res = cur + pre
        pre = cur
        cur = res
        n--
    }
    return res
}

speed(fibonacci_DP, 40)
复制代码

使用函数内存,我们可以为经常依赖先前结果的计算节省大量时间,例如斐波那契数列。缺点是闭包中的 obj 对象占用了额外的内存。

另外,动态规划的空间复杂度比前者低,也是比较推荐的方案。

12、实现绑定方法

const isComplexDataType = obj => (typeof obj === 'object' || typeof obj === 'function') && obj !== null

// Implement a simple bind
const myBind = function (bindTarget, ...args1) {
    if (typeof this !== 'function') throw new TypeError('Bind must be called on a function')
    const originFunc = this
    const boundFunc = function (...args2) {
        // Calls using the new keyword return a new object
        if (new.target) {
            let res = originFunc.call(this, ...args1, ...args2)
            //If the constructor returns an object, that object is returned
            if (isComplexDataType(res)) return res
            //Otherwise, the newly created object is returned
            return this
        } else {
            return originFunc.call(bindTarget, ...args1, ...args2)
        }
    }
    if (originFunc.prototype) {
        boundFunc.prototype = originFunc.prototype
    }

    const desc = Object.getOwnPropertyDescriptors(originFunc)
    Object.defineProperties(boundFunc, {
        length: desc.length,
        name: Object.assign(desc.name, {
            value: `bound ${desc.name.value}`
        })
    })
    return boundFunc
}
复制代码

实现函数的bind方法的核心使用调用绑定指向this,同时,考虑到其他情况如:
当bind返回的函数作为构造函数被new调用时,绑定值失效,变为new指定的对象。
定义绑定函数的长度和名称属性(不可枚举的属性)。

绑定函数的原型必须指向原函数的原型。

13、实现调用方法

const myCall = function (context, ...args) {
    let func = this
    context || (context = window)
    if (typeof func !== 'function') throw new TypeError('this is not function')
    let caller = Symbol('caller')
    context[caller] = func
    let res = context[caller](...args)
    delete context[caller]
    return res
}
复制代码

原理是将函数作为传入的上下文参数的属性执行。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 function
          next(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)
        }
    };
    
    _debounce.cancel = function () {
        clearTimeout(timer)
        timer = null
    };
    return _debounce
};
复制代码

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 无法同时为 false
    leading: true,
    trailing: false,
    context: null
}) => {
    let previous = new Date(0).getTime()
    let timer;
    const _throttle = function (...args) {
        let now = new Date().getTime();

        if (!options.leading) {
            if (timer) return
            timer = setTimeout(() => {
                timer = null
                func.apply(options.context, args)
            }, time)
        } else if (now - previous > time) {
            func.apply(options.context, args)
            previous = now
        } else if (options.trailing) {
            clearTimeout(timer)
            timer = setTimeout(() => {
                func.apply(options.context, args)
            }, time)
        }
    };
    _throttle.cancel = () => {
        previous = 0;
        clearTimeout(timer);
        timer = null
    };
    return _throttle
};
复制代码

添加尾随选项以指示是否在序列结束时触发附加事件。

17、图片的延迟加载

// getBoundingClientRect lazy Load
let imgList1 = [...document.querySelectorAll(".get_bounding_rect")]
let num = imgList1.length

let lazyLoad1 = (function () {
    let count = 0
    return 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 successfully
                deleteIndexList.push(index)
                count++
                if (count === num) {
                    //Unbind the Scroll event when all images are loaded
                    document.removeEventListener('scroll',lazyLoad1)
                }
            }
        })
        // Delete images that have been loaded
        imgList1 = imgList1.filter((_,index)=>!deleteIndexList.includes(index))

    }
})()

// The throttling function of throttle.js is referenced here
lazyLoad1 = proxy(lazyLoad1, 100)

document.addEventListener('scroll', lazyLoad1)
// Manually load the image once. Otherwise, the image on the first screen cannot be loaded without triggering scrolling
lazyLoad1()



// intersectionObserver lazy Load
let imgList2 = [...document.querySelectorAll(".intersection_observer")]

let lazyLoad2 = function () {
    // instantiation observer
    let observer = new IntersectionObserver(entries => {
        entries.forEach(entry => {
            if (entry.intersectionRatio > 0) {
                entry.target.src = entry.target.dataset.src
                observer.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 = name
    this.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 false
        if (proto === right.prototype) {
            return true
        }
        proto = Object.getPrototypeOf(proto)
    }
}

console.log(myInstanceof({}, Array))
复制代码

总结

以上就是我跟大家分享的20 个出色实用的JavaScript技巧,可帮助您编写更好、更高效的代码。想要了解更多欢迎前往公众号:WEB前端开发社区,每日干货分享

Supongo que te gusta

Origin juejin.im/post/7083016475547533348
Recomendado
Clasificación