JavaScript 学习笔记(二)

学习 JavaScript专题系列 的笔记

防抖

防抖的原理是,无论你怎么触发事件,但是我一定会在事件触发后的 n 秒后执行,如果你再一个事件触发的 n 秒内又触发了这个事件,那就以新的事件时间为准, n 秒后再执行。始终要等待 n 秒后才会执行事件

function debounce(func,wait,immediate){
    //防抖
    //定义一个定时器。
    var timeout,result;
    var debounced = function() {
        //获取 this
        var context = this;
        //获取参数
        var args = arguments;
        //清空定时器
        if(timeout)clearTimeout(timeout)
        if(immediate){
            //立即触发,但是需要等待 n 秒后才可以重新触发执行
            var callNow = !timeout;
            console.log(callNow)
            timeout = setTimeout(function(){
                timeout = null;
            }, wait)
            if (callNow) result = func.apply(context, args)
        }else{
            //触发后开始定时,
            timeout = setTimeout(function(){
                func.apply(context,args)
            },  wait);
        }
        return result
    }
    debounced.cancel = function(){
        // 当immediate 为 true,上一次执行后立即,取消定时器,下一次可以实现立即触发
        if(timeout) clearTimeout(timeout);
        timeout = null;
    }
    return debounced
}

function a(a){
    console.log(a)
    return a;
}
var aDebounce = debounce(a,1000,true)
console.log(aDebounce('23')) // 23
复制代码

节流

如果持续触发事件,每隔一段时间,只执行一次事件,根据首次是否执行以及结束后是否执行,效果有所不同,leading 代表首次是否执行,trailing 代表是否再执行一次


function throttle(func,wait,options) {
    var context,args,timeout,result;
    var previous = 0;
    options = options || {};
    // leading:false 表示禁用第一次执行
    // trailing: false 表示禁用停止触发的回调
    var later = function(){
        previous = options.leading === false ? 0 : new Date().getTime();
        timeout = null;
        func.apply(context, args);
    }
    var throttled = function(){
        var now = +new Date();
        if (!previous && options.leading === false) previous = now;
        // 下次触发 func 的剩余时间
        var remaining = wait - (now - previous);
        context = this;
        args = arguments;
        // 如果没有剩余的时间了或者你改了系统时间
        if(remaining > wait || remaining <= 0){
            if (timeout) {
                clearTimeout(timeout);
                timeout = null;
            }
            previous = now;
            func.apply(context, args);
        }else if(!timeout && options.trailing !== false){
            timeout = setTimeout(later, remaining);
        }
    }
    throttled.cancel = function() {
        clearTimeout(timeout);
        previous = 0;
        timeout = null;
    }
    return throttled
}
var aThrottle = throttle(a,1000)
console.log(aThrottle('23'))
复制代码

数组去重

双重循环

通过循环嵌套,最外层循环array,里面循环res, 如果 array[i] === res[j] 跳出循环,如果不等于,则此元素是唯一的,那么 j === resLen 是成立的,将 array[i] 添加进 res

var array = [
    1, 1, 
    '1', '1', 
    null, null,
    undefined, undefined,
    new String('1'), new String('1'), 
    /a/, /a/, 
    NaN, NaN
];
function unique(array) {
    var res = [];
    for (var i = 0; i < array.length; i++) {
        for (var j = 0,resLen = res.length; j < resLen; j++) {
            if(array[i] === res[j]){
                break;
            }
        }
        // 如果 array[i] 是唯一的,会提前跳出循环,j 就不会等于 resLen
        if(j === resLen){
            res.push(array[i])
        }
    }
    return res
}
console.log(unique(array))
/*
[1, "1", null, undefined, String, String, /a/, /a/, NaN, NaN]
对象和数组不去重
*/
复制代码

indexOf

indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的位置,如果要检索的字符串值没有出现,则该方法返回 -1

// indexOf
function unique2(array) {
    var res = [];
    for (var i = 0; i < array.length; i++) {
        // 如果 array[i] 是唯一的,会提前跳出循环,j 就不会等于 resLen
        var current = array[i];
        if(res.indexOf(current) === -1){
            res.push(current)
        }
    }
    return res
}
console.log(unique2(array))
/*
[1, "1", null, undefined, String, String, /a/, /a/, NaN, NaN]
对象和数组不去重
*/
复制代码

排序后去重

将要去重的数组使用 sort 方法排序后,相同的值就会被排在一起,然后我们就可以只判断当前元素与上一个元素是否相同,相同就说明重复,不相同就添加进 res

//排序后去重
function unique3(array) {
    var res = [];
    var sortArr = array.concat().sort();
    var seen;
    for (var i = 0; i < sortArr.length; i++) {
        // 如果是第一个元素或者相邻的元素不相同
        if(!i || seen !== sortArr[i]){
            res.push(sortArr[i]);
        }
        seen = sortArr[i]
    }
    return res
}
console.log(unique3(array))
/*
[/a/, /a/, "1", 1, String, 1, String, NaN, NaN, null, undefined]
对象和 NaN 不去重 数字 1 也不去重
*/
复制代码

filter

// filter - indexOf
function unique4(array) {
    return array.filter(function(item,index,array){
        return array.indexOf(item) === index
    })
}
console.log(unique4(array))
/*
[1, "1", null, undefined, String, String, /a/, /a/]
对象不去重 NaN 会被忽略掉
*/
// filter - 排序后去重
function unique5(array) {
    return array.concat().sort().filter(function(item,index,array){
        return !index || item !== array[index - 1]
    })
}
console.log(unique5(array))
/*
[1, "1", null, undefined, String, String, /a/, /a/]
对象和 NaN 不去重 数字 1 也不去重
*/
复制代码

Object键值对

//键值对去重
function unique6(array){
    var obj = {};
    return array.filter(function(item,index,array){
        var key = typeof item + JSON.stringify(item);
        return obj.hasOwnProperty(key) ? false : (obj[key] = true)
    })
}
console.log(unique6(array))
/*
[1, "1", null, undefined, String, /a/, NaN]
全部去重
*/
复制代码

ES6

Set:它类似于数组,但是成员的值都是唯一的,没有重复的值。 Map: 对象保存键值对。任何值(对象或者原始值)都可以作为一个键或一个值。

//ES6
// Set 
var unique7 = (array) => [...new Set(array)]
console.log(unique7(array))
/*
[1, "1", null, undefined, String, String, /a/, /a/, NaN]
对象不去重 NaN 去重
*/
// Map
var unique8 = (array) => {
    const map = new Map();
    return array.filter(item => !map.has(item) && map.set(item, 1))
}
console.log(unique8(array))
/*
[1, "1", null, undefined, String, String, /a/, /a/, NaN]
对象不去重 NaN 去重
*/
复制代码

类型判断

类型判断是通过调用方法来判断 是什么数据类型。

typeof

typeof typeof 是一元操作符,放在其单个操作数的前面,操作数可以是任意类型。返回值为表示操作数类型的一个字符串。

缺点:无法检测出 null 跟 Object 下细分的类型

var stringV = 's-123';
var numberV = 123;
var undefinedV = undefined;
var booleanV = false;
var objectV = {};
var functionV = function(){};
var nullV = null;
var arrayV = [];
var dateV = new Date();
var errorV = new Error();
var reg = /a/g;

var arr = [
    stringV, numberV, undefinedV, booleanV, 
    objectV, functionV, nullV, arrayV, errorV,
    reg, Math, JSON
]

function checkTypeof(arr) {
    for (var i = 0; i < arr.length; i++) {
        console.log(typeof (arr[i]))
    }
}
checkTypeof(arr)
/**
 *  stringV:        string
    numberV:        number
    undefinedV:     undefined
    booleanV:       boolean
    objectV:        object
    functionV:      function
    nullV:          object
    arrayV:         object
    errorV:         object
    reg:            object
    Math:           object
    JSON:           object
*/ 
复制代码

Object.prototype.toString

当 toString 方法被调用的时候,下面的步骤会被执行:

  • 如果 this 值是 undefined,就返回 [object Undefined]
  • 如果 this 的值是 null,就返回 [object Null]
  • 让 O 成为 ToObject(this) 的结果
  • 让 class 成为 O 的内部属性 [[Class]] 的值
  • 最后返回由 "[object " 和 class 和 "]" 三个部分组成的字符串
function checkType(arr) {
    for (var i = 0; i < arr.length; i++) {
        console.log(Object.prototype.toString.call(arr[i]))
    }
}
checkType(arr)
/**
 *  stringV:        [object String]
    numberV:        [object Number]
    undefinedV:     [object Undefined]
    booleanV:       [object Boolean]
    objectV:        [object Object]
    functionV:      [object Function]
    nullV:          [object Null]
    arrayV:         [object Array]
    errorV:         [object Error]
    reg:            [object RegExp]
    Math:           [object Math]
    JSON:           [object JSON]
*/ 
复制代码

type 函数

var class2type = {};

// 生成class2type映射
"Boolean Number String Function Array Date RegExp Object Error Math JSON".split(" ").map(function(item, index) {
    class2type["[object " + item + "]"] = item.toLowerCase();
})

function type(obj) {
    // 兼容 ie6 null 和 undefined 会被 Object.prototype.toString 识别成 [object Object]!
    if (obj == null) {
        return obj + "";
    }
    return typeof obj === "object" || typeof obj === "function" ?
        class2type[Object.prototype.toString.call(obj)] || "object" :
        typeof obj;
}
function checkType(arr) {
    for (var i = 0; i < arr.length; i++) {
        console.log(type(arr[i]))
    }
}
checkType(arr)
/**
 *  stringV:        String
    numberV:        Number
    undefinedV:     Undefined
    booleanV:       Boolean
    objectV:        Object
    functionV:      Function
    nullV:          Null
    arrayV:         Array
    errorV:         Error
    reg:            RegExp
    Math:           Math
    JSON:           JSON
*/ 
复制代码

plainObject

plainObject: "纯粹的对象",就是该对象是通过 "{}" 或 "new Object" 创建的,该对象含有零个或者多个键值对。

除了 {} 和 new Object 创建的之外,jQuery 认为一个没有原型的对象也是一个纯粹的对象。

// 上节中写 type 函数时,用来存放 toString 映射结果的对象
var class2type = {};

// 相当于 Object.prototype.toString
var toString = class2type.toString;

// 相当于 Object.prototype.hasOwnProperty
var hasOwn = class2type.hasOwnProperty;

function isPlainObject(obj) {
    var proto, Ctor;

    // 排除掉明显不是obj的以及一些宿主对象如Window
    if (!obj || toString.call(obj) !== "[object Object]") {
        return false;
    }

    /**
     * getPrototypeOf es5 方法,获取 obj 的原型
     * 以 new Object 创建的对象为例的话
     * obj.__proto__ === Object.prototype
     */
    proto = Object.getPrototypeOf(obj);

    // 没有原型的对象是纯粹的,Object.create(null) 就在这里返回 true
    if (!proto) {
        return true;
    }

    /**
     * 以下判断通过 new Object 方式创建的对象
     * 判断 proto 是否有 constructor 属性,如果有就让 Ctor 的值为 proto.constructor
     * 如果是 Object 函数创建的对象,Ctor 在这里就等于 Object 构造函数
     */
    Ctor = hasOwn.call(proto, "constructor") && proto.constructor;

    // 在这里判断 Ctor 构造函数是不是 Object 构造函数,用于区分自定义构造函数和 Object 构造函数
    return typeof Ctor === "function" && hasOwn.toString.call(Ctor) === hasOwn.toString.call(Object);
}
复制代码

EmptyObject

jQuery提供了 isEmptyObject 方法来判断是否是空对象

function isEmptyObject( obj ) {
        var name;
        for ( name in obj ) {
            return false;
        }
        return true;
}
复制代码

Window对象

function isWindow( obj ) {
    return obj != null && obj === obj.window;
}
复制代码

isArrayLike

function isArrayLike(obj) {
    // obj 必须有 length属性
    var length = !!obj && "length" in obj && obj.length;
    var typeRes = type(obj);
    // 排除掉函数和 Window 对象
    if (typeRes === "function" || isWindow(obj)) {
        return false;
    }
    return typeRes === "array" || length === 0 ||
        typeof length === "number" && length > 0 && (length - 1) in obj;
}
复制代码

isElement

isElement = function(obj) {
    return !!(obj && obj.nodeType === 1);
};
复制代码

数组的浅拷贝和深拷贝

数组的浅拷贝可以通过 调用 slice concat 方法返回一个新数组的特性来拷贝。

数组如果是基本类型,会直接拷贝一份,互相不影响,但是如果数组中有对象或者数组,就会拷贝对象和数组的引用,这种拷贝方法叫做浅拷贝。

深拷贝则是完全的拷贝一个对象,即使嵌套对象,两者也互相分离,互不影响

JSON.parse(JSON.stringify(arr))

通过此方法,适用于数组和对象,但是没办法拷贝函数,函数的话,会变成 null

var arr = ['old', 1, true, ['old1', 'old2'], {old: 1}]
var new_arr = JSON.parse( JSON.stringify(arr) );
console.log(new_arr); //['old', 1, true, ['old1', 'old2'], {old: 1}]
var arr = [function(){
    console.log(a)
}, {
    b: function(){
        console.log(b)
    }
}]
var new_arr = JSON.parse(JSON.stringify(arr));
console.log(new_arr);
//[null, {}]
复制代码

浅拷贝的实现

var shallowCopy = function(obj){
    //只拷贝对象
    if(typeof obj !== 'object')return 
    // 根据 instanceof 判断是数组还是对象
    var newObj = obj instanceof Array ? [] : {};
    for(var key in obj){
        //遍历,如果存在属性,拷贝
        if(obj.hasOwnProperty(key)){
            newObj[key] = obj[key]
        }
    }
    return newObj
}
复制代码

深拷贝的实现


var deepCopy = function(obj){
    //只拷贝对象
    if(typeof obj !== 'object')return 
    // 根据 instanceof 判断是数组还是对象
    var newObj = obj instanceof Array ? [] : {};
    for(var key in obj){
        //遍历,如果存在属性,拷贝
        if(obj.hasOwnProperty(key)){
            //如果值为对象,再执行一波深拷贝函数,由于是递归,有可能会栈溢出
            newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key]
        }
    }
    return newObj
}
复制代码

jQuery extend

extend(): 合并两个或者更多的对象的内容到第一个对象中

// isPlainObject 函数来自于  [JavaScript专题之类型判断(下) ](https://github.com/mqyqingfeng/Blog/issues/30)
    var class2type = {};
    var toString = class2type.toString;
    var hasOwn = class2type.hasOwnProperty;

    function isPlainObject(obj) {
        var proto, Ctor;
        if (!obj || toString.call(obj) !== "[object Object]") {
            return false;
        }
        proto = Object.getPrototypeOf(obj);
        if (!proto) {
            return true;
        }
        Ctor = hasOwn.call(proto, "constructor") && proto.constructor;
        return typeof Ctor === "function" && hasOwn.toString.call(Ctor) === hasOwn.toString.call(Object);
    }


    function extend() {
        // 默认不进行深拷贝
        var deep = false;
        var name, options, src, copy, clone, copyIsArray;
        var length = arguments.length;
        // 记录要复制的对象的下标
        var i = 1;
        // 第一个参数不传布尔值的情况下,target 默认是第一个参数
        var target = arguments[0] || {};
        // 如果第一个参数是布尔值,第二个参数是 target
        if (typeof target == 'boolean') {
            deep = target;
            target = arguments[i] || {};
            i++;
        }
        // 如果target不是对象,我们是无法进行复制的,所以设为 {}
        if (typeof target !== "object" && !isFunction(target)) {
            target = {};
        }

        // 循环遍历要复制的对象们
        for (; i < length; i++) {
            // 获取当前对象
            options = arguments[i];
            // 要求不能为空 避免 extend(a,,b) 这种情况
            if (options != null) {
                for (name in options) {
                    // 目标属性值
                    src = target[name];
                    // 要复制的对象的属性值
                    copy = options[name];
                    // 解决循环引用
                    if (target === copy) {
                        continue;
                    }
                    // 要递归的对象必须是 plainObject 或者数组
                    if (deep && copy && (isPlainObject(copy) ||
                            (copyIsArray = Array.isArray(copy)))) {
                        // 要复制的对象属性值类型需要与目标属性值相同
                        if (copyIsArray) {
                            copyIsArray = false;
                            clone = src && Array.isArray(src) ? src : [];
                        } else {
                            clone = src && isPlainObject(src) ? src : {};
                        }
                        target[name] = extend(deep, clone, copy);
                    } else if (copy !== undefined) {
                        target[name] = copy;
                    }
                }
            }
        }
        return target;
    };
复制代码

取出数组最大值最小值

Math.max

Math.max 函数返回一组数中的最大值 Math.max(a,b,c,..,e);

  • 如果有任一参数不能被转换为数值,则结果为 NaN。
  • max 是 Math 的静态方法,所以应该像这样使用:Math.max(),而不是作为 Math 实例的方法 (简单的来说,就是不使用 new )
  • 如果没有参数,则结果为 -Infinity (注意是负无穷大)
reduce
var arr = [6,2,6,2,10,1,13,22]
var max = arr.reduce(function(prev,next){
    return Math.max(prev,next)
})
console.log(max);
复制代码
排序
var max = arr.sort(function(a,b){return a - b;})[arr.length-1]
var min = arr.sort(function(a,b){return a - b;})[0]
复制代码
eval
var max = eval("Math.max(" + arr + ")")
var min = eval("Math.min(" + arr + ")")
复制代码
apply
var max = Math.max.apply(null, arr)
var min = Math.min.apply(null, arr)
复制代码
ES6 ...
var max = Math.max(...arr)
var min = Math.min(...arr)
复制代码

数组扁平化

数组的扁平化,就是将一个嵌套多层的数组 array (嵌套可以是任何层数)转换为只有一层的数组。

递归实现

var arr = [1,[1,2,[3,[4,[5,[6,[7]]]]]]]
function flatten(arr){
    var result = [];
    for (var i = 0; i < arr.length; i++) {
        if(Array.isArray(arr[i])){
            result = result.concat(flatten(arr[i]))
        }else{
            result.push(arr[i])
        }
    }
    return result
}
console.log(flatten(arr)) //[ 1, 1, 2, 3, 4, 5, 6, 7 ]
复制代码

reduce

function flatten(arr){
    return arr.reduce(function(prev,next){
        return prev.concat(Array.isArray(next)? flatten(next) : next)
    },[])
}
复制代码

ES6 ...

function flatten(arr){
    while (arr.some(item=>Array.isArray(item))) {
        arr = [].concat(...arr)
    }
    return arr
}
console.log(flatten(arr))
复制代码

findIndex

ES6 对数组新增了 findIndex 方法,它会返回数组中满足提供的函数的第一个元素的索引,否则返回 -1。

function isBigEnough(element) {
 return element >= 15;
}
[12, 5, 8, 130, 44].findIndex(isBigEnough);  // 3
复制代码

findIndex

function findIndex(array,predicate,context) {
    for (var i = 0; i < array.length; i++) {
        if(predicate.call(context,array[i],i,array))return i;
    }
    return -1
}
复制代码

findIndex

function findLastIndex(array,predicate,context) {
    for (var i = array.length - 1; i >= 0; i--) {
        if(predicate.call(context,array[i],i,array))return i;
    }
    return -1
}
复制代码

createIndexFinder


function createIndexFinder(dir){
    return function (array,predicate,context) {
        var length = array.length;
        var index = dir > 0 ? 0 : length - 1
        for (; index >= 0 && index < length; index += dir) {
            if(predicate.call(context,array[index],index,array))return index;
        }
        return -1
    }
}
var findIndex = createIndexFinder(1);
var findLastIndex = createIndexFinder(-1);
复制代码

sortedIndex

sortedIndex:在一个排好序的数组中找到 value 对应的位置,保证插入数组后,依然保持有序的状态。


function cb(func,context){
    if(context === void 0)return function(){return arguments[0]};
    return function(){
        return func.apply(context,arguments)
    }
}

function sortedIndex(array,obj,iteratee,context){
    iteratee = cb(iteratee, context)
    var low = 0, high = array.length;
    while (low < high) {
        var mid = Math.floor((low + high) / 2);
        if(iteratee(array[mid]) < iteratee(obj))low = mid + 1;
        else high = mid
    }
    return high
}

console.log(sortedIndex([1,5,10,15,20], 12)) //3
var stooges = [{name: 'stooge1', age: 10}, {name: 'stooge2', age: 30}];

var result = sortedIndex(stooges, {name: 'stooge3', age: 5}, function(stooge){
    return stooge.age
});
console.log(result) //0
复制代码

indexOf 和 lastIndexOf


function createIndexOfFinder(dir,predicate,sortedIndex){
    return function(array,item,idx){
        var length = array.length;
        var i = 0;
        // 第三个参数不传开始搜索的下标值,而是一个布尔值 true,就认为数组是一个排好序的数组
        if (typeof idx == "number") {
            if (dir > 0) {
                i = idx >= 0 ? idx : Math.max(length + idx, 0);
            }else {
                length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1;
            }
        }else if (sortedIndex && idx && length) {
            // 如果该插入的位置的值正好等于元素的值,说明是第一个符合要求的值
            idx = sortedIndex(array, item);
            return array[idx] === item ? idx : -1;
        }
        // 判断元素是否是 NaN
        if (item !== item) {
            // 在截取好的数组中查找第一个满足isNaN函数的元素的下标
            idx = predicate(array.slice(i, length), isNaN)
            return idx >= 0 ? idx + i: -1;
        }
        for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) {
            if (array[idx] === item) return idx;
        }
        return -1
    }
}

var indexOf = createIndexOfFinder(1, findIndex,sortedIndex);
var lastIndexOf = createIndexOfFinder(-1, findLastIndex);
复制代码

jQuery 遍历方法 each

each 方法,作为一个通用遍历方法,可用于遍历对象和数组。回调函数拥有两个参数:第一个为对象的成员或数组的索引,第二个为对应变量或内容。


function each(obj,callback){
    var length, i = 0;
    if(isArrayLike(obj)){
        //判断是否类数组对象
        length = obj.length
        for (; i < length; i++) {
            //把this 指向当前遍历的对象
            if (callback.call(i, obj[i], obj[i]) === false) {
                break;
            }
        }
    }else{
        for(i in obj){
            if (callback.call(i, obj[i], obj[i]) === false) {
                break;
            }
        }
    }
}
复制代码

判断两个对象相等

避免忘记... 判断两个对象相等


var toString = Object.prototype.toString;

function isFunction(obj) {
    return type(obj) === "function";
}

function eq(a, b, aStack, bStack){
    // === 结果为 true 的区别出 -0 和 +0
    if(a === b) return a !== 0 || 1 / a === 1 / b;
    //typeof null 的结果为 object ,提前做判断让其滚蛋
    if(a == null || b == null) return false;
    // 判断 NaN
    if(a !== a)return b !== b;

    // 判断 a 类型 如果是基本类型,在这里直接返回false
    var type = typeof a;
    if(type !== 'function' && type !== 'object' && typeof b !== 'object')return false

    //更复杂的对象 使用 deepEq 深度比较
    return deepEq(a,b, aStack, bStack);
}

function deepEq(a,b, aStack, bStack) {
    var className = toString.call(a);
    if(className !== toString.call(b))return false;
    switch (className) {
        case '[object RegExp]':
        case '[object String]':
            return '' + a === '' + b;
        case '[object Number]':
            if (+a !== +a) return +b !== +b;
            return +a === 0 ? 1 / +a === 1 / b : +a === +b;
        case '[object Date]':
        case '[object Boolean]':
            return +a === +b;
        default:
    }
    var areArrays = className === '[object Array]';
    if(!areArrays){
        //过滤两个函数的情况
        if(typeof a != 'object' || typeof b != 'object')return false
        var aCtor = a.constructor , bCtor = b.constructor;
        // aCtor 和 bCtor 必须都存在
        //并且都不是 Object 构造函数的情况下,
        //aCtor 不等于 bCtor, 那这两个对象就真的不相等啦
        if (aCtor !== bCtor && 
            !(isFunction(aCtor) && aCtor instanceof aCtor && isFunction(bCtor)
             && bCtor instanceof bCtor) && ('constructor' in a && 'constructor' in b)) {
            return false;
        }
    }
    aStack = aStack || [];
    bStack = bStack || [];
    
    var length = aStack.length;
    // 检查是否有循环引用的部分
    while (length--) {
        if (aStack[length] === a) {
            return bStack[length] === b;
        }
    }

    aStack.push(a);
    bStack.push(b);

    if(areArrays){
        //数组情况
        length = a.length;
        if (length !== b.length) return false;

        while (length--) {
            if (!eq(a[length], b[length],aStack, bStack)) return false;
         }
    }else{
        //对象情况
        var keys = Object.keys(a), key;
        length = keys.length;

        if (Object.keys(b).length !== length) return false;

        while (length--) {
            key = keys[length];
            if (!(b.hasOwnProperty(key) && eq(a[key], b[key],aStack, bStack))) return false;
        }
    }

    aStack.pop();
    bStack.pop();
    return true;
}

console.log(eq(0, 0)) // true
console.log(eq(0, -0)) // false

console.log(eq(NaN, NaN)); // true
console.log(eq(Number(NaN), Number(NaN))); // true

console.log(eq('Curly', new String('Curly'))); // true

console.log(eq([1], [1])); // true
console.log(eq({ value: 1 }, { value: 1 })); // true

var a, b;

a = { foo: { b: { foo: { c: { foo: null } } } } };
b = { foo: { b: { foo: { c: { foo: null } } } } };
a.foo.b.foo.c.foo = a;
b.foo.b.foo.c.foo = b;

console.log(eq(a, b)) // true
复制代码

柯里化

在数学和计算机科学中,柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。

function add(a, b) {
    return a + b;
}

// 执行 add 函数,一次传入两个参数即可
add(1, 2) // 3

// 假设有一个 curry 函数可以做到柯里化
var addCurry = curry(add);
addCurry(1)(2) // 3
复制代码

柯里化到底有什么用?

// 示意而已
function ajax(type, url, data) {
    var xhr = new XMLHttpRequest();
    xhr.open(type, url, true);
    xhr.send(data);
}

// 虽然 ajax 这个函数非常通用,但在重复调用的时候参数冗余
ajax('POST', 'www.test.com', "name=kevin")
ajax('POST', 'www.test2.com', "name=kevin")
ajax('POST', 'www.test3.com', "name=kevin")

// 利用 curry
var ajaxCurry = curry(ajax);

// 以 POST 类型请求数据
var post = ajaxCurry('POST');
post('www.test.com', "name=kevin");

// 以 POST 类型请求来自于 www.test.com 的数据
var postFromTest = post('www.test.com');
postFromTest("name=kevin");
复制代码

curry 的这种用途可以理解为:参数复用。本质上是降低通用性,提高适用性。

var person = [{name: 'kevin'}, {name: 'daisy'}]
//如果我们要获取所有的 name 值,我们可以这样做:
var name = person.map(function (item) {
    return item.name;
})
//不过如果我们有 curry 函数:
var prop = curry(function (key, obj) {
    return obj[key]
});
var name = person.map(prop('name'))
复制代码

Curry 函数的实现

  • 实现一个 sub_curry ,用函数包裹原函数,然后给原函数传入之前的参数。
  • 实现一个 curry , 用来传入
function sub_curry(fn){
    // 存储先前传入的参数
    var args = [].splice.call(arguments, 1);
    return function(){
        return fn.apply(this, args.concat([].slice.call(arguments)))
    }
}

function curry(fn,length){
    length = length || fn.length; //纪录参数个数
    var slice = Array.prototype.slice;

    return function() {
        if (arguments.length < length) {
            //如果参数不够,用sub_curry包装,存储当前传入的参数
            var combined = [fn].concat(slice.call(arguments));
            return curry(sub_curry.apply(this, combined), length - arguments.length);
        } else {
            //当参数足够,执行函数
            console.log(arguments);
            return fn.apply(this, arguments);
        }
    };
}
//用闭包把参数保存起来,当参数的数量足够执行函数了,就开始执行函数
var fn = curry(function(a, b, c) {
    return [a, b, c];
});

console.log(fn("a", "b", "c") )// ["a", "b", "c"]
console.log(fn("a", "b")("c")) // ["a", "b", "c"]
console.log(fn("a")("b")("c")) // ["a", "b", "c"]
console.log(fn("a")("b", "c")) // ["a", "b", "c"]
复制代码

偏函数

在计算机科学中,局部应用是指固定一个函数的一些参数,然后产生另一个更小元的函数。

function add(a, b) {
    return a + b;
}
// 执行 add 函数,一次传入两个参数即可
add(1, 2) // 3
// 假设有一个 partial 函数可以做到局部应用
var addOne = partial(add, 1);
addOne(2) // 3
复制代码

柯里化是将一个多参数函数转换成多个单参数函数,也就是将一个 n 元函数转换成 n 个一元函数。

偏函数则是固定一个函数的一个或者多个参数,也就是将一个 n 元函数转换成一个 n - x 元函数。

var _ = {}
function partial(fn) {
    //取出第一次设定的参数
    var args = [].splice.call(arguments,1)
    return function(){
        var position = 0,len = args.length;
        // 查找空位符位置,替换空位符
        for(var i = 0 ; i < len; i++){
            args[i] = args[i] === _ ? arguments[position++] : args[i]
        }
        while(position < arguments.length) args.push(arguments[position++]);
        return fn.apply(this,args);
    }
}
复制代码

函数组合

利用 compose 将多个函数组合成一个函数,让代码从右向左运行,而不是由内而外运行,可读性大大提升。这便是函数组合。


function compose(){
    var args = arguments;
    var start = args.length - 1;
    return function(){
        var i = start;
        var result = args[start].apply(this,arguments);
        while (i--) {
            result = args[i].call(this, result);
        }
        return result
    }
}
// 需求:输入 'kevin',返回 'HELLO, KEVIN'。

// pointfree 先定义基本运算,这些可以封装起来复用
var toUpperCase = function(x) { return x.toUpperCase(); };
var hello = function(x) { return 'HELLO, ' + x; };

var greet = compose(hello, toUpperCase);
console.log(greet('kevin'));

// pointfree
// 先定义基本运算
var split = curry(function(separator, str) { return str.split(separator) })
var head = function(str) { return str.slice(0, 1) }
var toUpperCase = function(str) { return str.toUpperCase() }
var join = curry(function(separator, arr) { return arr.join(separator) })
var map = curry(function(fn, arr) { return arr.map(fn) })

var initials = compose(join('.'), map(compose(toUpperCase, head)), split(' '));

console.log(initials("kevin daisy kelly"));
复制代码

转载于:https://juejin.im/post/5bcbe37be51d450e5f3dd6ac

你可能感兴趣的:(JavaScript 学习笔记(二))