Javascript 那些套路

清单

模块化

自己想要一个库 , 想让这个库可以通过 import 或者 require 导入, 也兼容浏览器直接导入时. ( 以 Tool 为例)

; (function (global, factory) {
  var isSupportModule = typeof exports !== 'undefined' && typeof module !== 'undefined';
  var isSupportAmd = typeof define === 'function' && define.amd;

  isSupportModule ? module.exports = factory() :
    isSupportAmd ? define(factory) : global.Tool = factory()

})(this, function () {

  var _privateVariable = 'hello world';

  return function Tool(){
    console.log(_privateVariable)
  }

});

高阶函数之偏函数

函数执行返回新的函数, 也就是通过传参, 定制一个新的函数.

比如要写一个判断 object 对象的具体类型的方法, 一般情况会这样写:

function isType(obj,type){
    var objType = Object.prototype.toString.call(obj)
                                  .slice(8,-1).toLowerCase();
    return objType === type;
}

// 如果要抽离出来成 分别是 isArray isFunction 等则可以写成
var isArray = function(obj){
    return isType(obj,'array');
}
var isFunction = function(obj){
    return isType(obj,'function');
}

用高阶函数可以写成

function isType(type){
    type = type.toLowerCase();
    return function(obj){
        return Object.prototype.toString.call(obj).toLowerCase() === '[object '+type+']'
    }
}
var isArray = isType('array')
var isFunction = isType('Function')

以上两种方式, 普通的方式是在一个函数里调用另外一个函数, 而高阶函数就是一个函数执行就直接返回一个可用的函数. 前者利用的每次调用 isArray 时传递的 type 类型, 而高阶函数 , 则是通过 闭包 保有了 isType 时传入的 type 参数值.

高阶函数之柯里化

实质上是, 部分求值, 求的值存在闭包作用域 , 然后返回一个函数, 作为下一次求值的方法体;
一般来讲, 要连续调用几次, 函数的嵌套层次就对应有几层.

function add(a){
    return function inner(b){
        return a+b;
    }
}
var sum = add(1)(2); // 3

这里 add(1) 时 , 返回的是 inner 函数, inner 执行时的 a 则是执行 add 时传入的参数.
如图:
这里写图片描述

利用 js 自身的一些语言特性, 也是可以实现无限连续调用的.
比如实现连续累加调用:

实现原理即是, 函数的每次执行始终返回函数, 但是又需要得出计算的值, 因为 js 获取变量的值的时候是通过 toStringvalueOf 两种方式获取, 而这两种方式用户又是可以覆写的. 也就是虽然是函数 , 在获取函数的字符串形式的时候, 会通过函数的 toString 执行返回, 而要获取值比如数字则是 valueOf 返回.

按步骤一步一步拆解如下:
1.先实现一个函数永远返回一个函数

function f(){
    console.log('f 执行');
    return f;
}
f()()()(); // 可以无限执行下去

2.传参, 且对参数进行累加.

function f(sum,a){
    sum = sum || 0
    a = a || 0
    sum += a 
    var _f = f.bind(null,sum);
    return _f;
}
f(1)(2)(3)(4);

3.对方法的 toStringvalueOf 覆写

var fn = function(){}
fn.toString = function(){
    return '123'
}
fn.valueOf = function(){
    return 123
}
console.log(+fn); // 123
console.log(''+fn); // '123'

4.前几步结合, 并且用 ES6 默认参数简写, 则可以是:

function f(sum = 0, a = 0) {
   sum += a;
   var _f = f.bind(null, sum);
   _f.toString = () => `${sum}`
   _f.valueOf = () => sum
   return _f;
}
console.log(+f(1)(2)(3)(4)); // 10 , 注意前面的加号, 表示强转数字

5.番外, 再来看 bind方法的使用

var fn = function(a=0,b=0){ return a+b };
var fnBind = fn.bind(null,2);
fnBind(3); // 5

bind 阶段, 可以传一个参数值 2 , 也就是部分传参, 然后在 bind 之后的方法 fnBind 传入参数值 3 并执行, 这两个值共同组成了最后的返回结果.

7.不用 bind 的实现

function f(sum = 0, a = 0) {
   sum += a;
   var _f = x => f(sum,x);
   _f.toString = () => `${sum}`
   _f.valueOf = () => sum
   return _f;
}
console.log(+f(1)(2)(3)(4)); // 10 , 注意前面的加号, 表示强转数字

x=>f(sum,x)f.bind(null,sum) 一样, 它们都是返回一个函数 , 并不执行

立即自执行函数做兼容

也就是在做针对不同浏览器做兼容的时候, 并不一定得每次调用都去调用浏览器判断, 再执行相关操作, 而是通过自执行返回一个针对该浏览器特定的使用方式. ( 比如事件监听 )

var bindEvent = (function () {
  if (window.attachEvent) {
    // useCapture 表示在什么阶段相应, 为 true 则是捕获阶段, 为 false 则是冒泡阶段
    return function (el, event, cb, useCapture) {
      el.attachEvent('onclick', cb, useCapture)
    }
  }
  else {
    return function (el, event, cb, useCapture) {
      el.addEventListener('click', cb, useCapture);
    }
  }
})();

var btn = document.getElementById('btn');
bindEvent(btn, 'click', function (e) {
  console.log(1);
}, false);

数组需要边循环边操作

比如,给定一个数组:
var arr = [2, 1, 23, 2, 42];
需要实现,在原数组上进行操作,并删除所有选项为2的元素。

按常规,假如从左到右遍历:

var arr = [2, 1, 23, 2, 42];
function filterArr(arr,num){
    for (var i = 0; i < arr.length; i++) {
        if (arr[i] === num) {
          arr.splice(i, 1);
        }
    }
}
filterArr(arr,2); 
console.log(arr); // [1,23,42]

以上,看上去是没什么问题,正常输出了,但是换一个传入的数组情况就不一样了。

var arr = [2,2, 1, 23, 2,2, 42];
function filterArr(arr,num){
    for (var i = 0; i < arr.length; i++) {
        if (arr[i] === num) {
          arr.splice(i, 1);
        }
    }
}
filterArr(arr,2); 
console.log(arr); // [2, 1, 23, 2, 42]

以上,可以看出,只要连续的两个元素都是相同要被筛选的值 2 时,就筛选不干净了。
因为索引是递增,但是数组长度一直在变化;
比如 i = 1 时,理应指向的是数组的第二个元素2,但是,因为第一个元素是2已经被剔除,此时的数组是 [2,1,23,2,2,42] ; 所以它指向的是1,就直接略过了 数组的第二项。

那么怎么做到,边删减,但又能保证完整遍历完呢?
很简单,就是从右到左遍历。

var arr = [2,2, 1, 23, 2,2, 42];
function filterArr(arr,num){
    for (var i = arr.length; i >-1; i--) {
        if (arr[i] === num) {
          arr.splice(i, 1);
        }
    }
}
filterArr(arr,2); 
console.log(arr); // [1, 23, 42]

以上,结果显示正确。因为是从最后一个开始数,删掉的元素,改变数组长度,也不会对前面的元素有影响。可以保证每一项都是可以被遍历到的。

总结,在一筹莫展时,或者好像把问题弄得越来越复杂时,应该主动跳出来,尝试着从不同角度,甚至是反方向尝试一下。
可能会有惊喜。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值