經常使用的一些函數墊片,意在加深js基礎概念,同時對js也是個很好的總結。如下案例爲我的實踐,考慮主流程完整,但有些邊界問題未考慮,不推薦在工程項目中使用。正式項目推薦使用lodash。javascript
var foo = { value: 1 }
function bar() { console.log(this.value) }
bar.call(foo) // 期待打印:1
bar.apply(foo) // 期待打印:1
複製代碼
call/apply當即執行函數,同時函數中的this改成指向context。相似等價於如下java
var foo = {
value: 1,
fn: function bar() { console.log(this.value) }
}
複製代碼
Function.prototype.call = function(context, ...args) {
context = context || window
context.fn = this // 這裏的this表明函數
context.fn(...args) // 給context添加屬性fn,因此執行fn方法時,裏面的this表明context
delete context.fn
}
Function.prototype.apply = function(context, ...args) {
context = context || window
context.fn = this
context.fn(args) // apply傳遞數組
delete context.fn
}
複製代碼
var foo = { value: 1 }
function bar() { console.log(this.value) }
let barBind = bar.bind(foo)
barBind() // 期待打印:1
複製代碼
經過apply改變this,而且返回一個函數git
Function.prototype.bind = function (context, ...args) {
var fn = this
return function() {
return fn.apply(context, args)
}
}
複製代碼
let addFun = function(a, b, c) { return a + b + c }
let curryFun = curry(addFun)
curryFun(1)(2)(3) === 6 // true
複製代碼
遞歸,當執行的參數個數等於本來函數的個數,執行函數es6
var curry = function(fn) {
var limit = fn.length // fn函數參數個數
return function judgeCurry (...args) {
if (args.length >= limit) {
return fn.apply(null, args)
} else {
return function(...args2) {
return judgeCurry.apply(null, args.concat(args2))
}
}
}
}
// or es6
var curry = function(fn, ...args) {
if (args.length >= fn.length) {
return fn(...args)
}
return function (...args2) {
return curry(fn, ...args, ...args2)
}
}
複製代碼
let loopItem = (prevFn, nextFn) => (...args) => prevFn(nextFn(...args))
const compose = (...fns) => fns.reduce(loopItem);
const pipe = (...fns) => fns.reduceRight(loopItem)
const example = pipe(
(x, y) => x * y,
x => x + 1
);
console.log(example(3, 4)) // 13
複製代碼
// before:[1, 2, [3, 4, [5, 6]]]
// after flat: [1, 2, 3, 4, [5, 6]]
// 思路:使用reduce或map
function flatSingle(arr) {
return arr.reduce((pre, val) => pre.concat(val), [])
}
// or
let flatSingle = arr => [].concat(...arr)
複製代碼
// before: [1,2,3,[1,2,3,4, [2,3,4]]]
// after flatDeep: [1, 2, 3, 1, 2, 3, 4, 2, 3, 4]
// 思路:深度優先遞歸,使用reduce鏈接起來
// 深度優先算法 - 遞歸
function flatDeep(arr) {
return arr.reduce((pre, val) => pre.concat(Array.isArray(val) ? flatDeep(val) : val), [])
}
// 深度優先算法 - 堆棧
function flatDeep(arr) {
const stack = [...arr]
const res = []
while (stack.length) {
const val = stack.pop() // 從尾部開始
Array.isArray(val) ? stack.push(val) : res.push(val)
}
return res.reverse()
}
// 取巧,利用Array.toString()
function flatDeep(arr) {
return arr.toString().split(',')
}
複製代碼
深度的含義是指每一項展平的次數github
// before: [1,2,3,[1, [2]], [1, [2, [3]]]]
// after: [ 1, 2, 3, 1, 2, 1, 2, [ 3 ] ]
function flatDeep(arr, depth = 1) {
if (depth === 1) return arr.reduce((pre, val) => pre.concat(val), [])
return arr.reduce((pre, val) => pre.concat(Array.isArray(val) ? flatDeep(val, depth - 1) : val), [])
}
複製代碼
數組去除重複算法
// before: [2, 1, 3, 2]
// after: [2, 1, 3]
function removeRepeat(arr) {
return arr.filter((item, index) => arr.indexOf(item) === index)
}
// or es6
let removeRepeat = arr => Array.from(new Set(arr))
let removeRepeat = arr => [...new Set(arr)]
複製代碼
// 淺拷貝
function clone(source) {
var target = {}
for (var i in source) {
source.hasOwnProperty(i) && target[i] = source[i]
}
return target
}
// or es6
const clone = source => Object.assign({}, source)
const clone = source => { ...source }
複製代碼
// 深拷貝
// 思路:遞歸賦值
const deepClone = source => {
if (!source || typeof source !== 'object') {
throw new Error('error arguments', 'shallowClone')
}
// 區分array和object對象
let target = source instanceof Array ? [] : {}
for (let key in source) {
if (source.hasOwnProperty(key)) {
target[key] = typeof source[key] === 'object' ? deepClone(source[key]) : source[key]
}
}
return target
}
// or 取巧方法
// 注意這種取巧方法是有限制的
// 1. 只能解析Number、String、Array等可以被json表示的數據結構
// 2. 不能處理循環引用
const deepClone = source => JSON.parse(JSON.stringify(source))
複製代碼
// 防抖案例:窗口resize中止才執行最終的函數
function debounce(fn, wait, ...args) {
var that = this
fn.tId && clearTimeout(fn.tId)
fn.tId = setTimeout(function() {
fn.apply(that, args)
}, wait)
}
function handle(e) {
console.log('resize end event')
console.log(e) // Event{}
}
// 缺點:handle不能寫成匿名函數,由於把tId存儲在handle函數對象上。因此間接致使傳遞參數e較爲複雜
window.onresize = function(e) { debounce(handle, 1000, e) }
// 改進版
// 思路: 用閉包把tId存儲起來
function debounce(fn, wait) {
var tId
return function() {
var that = this
var args = arguments
tId && clearTimeout(tId)
tId = setTimeout(function() {
fn.apply(that, args)
}, wait)
}
}
function handle(e) {
console.log('resize end event')
console.log(e) // Event{}
}
window.onresize = debounce(handle, 1000)
複製代碼
// 節流案例: 不停scroll時,滾動條每隔100ms固定頻率執行函數
function throttle(fn, wait) {
var cur = new Date()
fn.last = fn.last || 0
if (cur - fn.last > wait) {
fn.call()
fn.last = cur
}
}
function handle() {
console.log('scroll event')
}
window.onscroll = function() { throttle(handle, 100) }
複製代碼