フロントエンド開発者が知っておくべき20のJavaScriptトリック

1.オブジェクトのデータ型を決定します

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

Object.prototype.toStringを使用して、さまざまなタイプの判断、1行のコード、簡潔、エレガント、柔軟な判断を渡すことにより、さまざまな判断関数を返します。

2.配列マップメソッドをループします

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

マップの2番目のパラメーターは、最初のパラメーターのコールバックでこれを指していることに注意してください。最初の引数が矢印関数の場合、矢印関数の字句バインディングのため、これの2番目の設定は効果がありません。

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 = myFilter
let 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 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));
复制代码

一部を実行する配列は、空の配列の場合は常にfalseを返し、別の配列のeveryメソッドの配列は、空の配列の場合は常にtrueを返します。

6.ループを介して配列のreduceメソッドを実装します

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. reduceを使用して、配列のフラットメソッドを実装します

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

myFlatはこのポイントに依存しているため、トラバーサルを減らすときにmyFlatのこのポイントを指定する必要があります。指定しない場合、デフォルトでウィンドウを指し、エラーが報告されます。

配列の要素がまだ配列である場合は、ES6スプレッド演算子を使用して次元を減らします(concatメソッドはES5で使用できます)。ただし、配列要素内にネストされた配列が存在する可能性があるため、selfFlatを再帰的に呼び出す必要があります。

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

Createメソッドは空のオブジェクトを作成し、この空のオブジェクトをObject.createメソッドのパラメーターから継承します。次に、サブクラス(subType)のプロトタイプを空のオブジェクトと等しくすると、サブクラスのプロトタイプが空のオブジェクトと等しく、空のオブジェクトが親クラスの継承プロトタイプと等しいことがわかります。

Object.createは、結果の空のオブジェクトのプロパティとプロパティ/アクセサ記述子を定義する2番目のパラメータをサポートします。この空のオブジェクトに、デフォルトの継承動作により厳密に一致するコンストラクタープロパティを与えることができます。これは、列挙できない内部プロパティでもあります(列挙可能:False)。

ES6クラスを使用すると、サブクラスは親クラスから静的メソッドと静的プロパティを継承できますが、通常の寄生合成継承はインスタンス間でのみ実現できます。クラス間継承の場合、追加のメソッドを定義する必要があります。

ここでは、Object.setProtoTypeofを使用してsuperTypeをsubTypeのプロトタイプとして設定し、親クラスから静的メソッドと静的プロパティを継承できるようにします。

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前端开发社区,每日干货分享

おすすめ

転載: juejin.im/post/7083016475547533348