清单
模块化
自己想要一个库 , 想让这个库可以通过 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
获取变量的值的时候是通过toString
和valueOf
两种方式获取, 而这两种方式用户又是可以覆写的. 也就是虽然是函数 , 在获取函数的字符串形式的时候, 会通过函数的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.对方法的 toString
和 valueOf
覆写
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]
以上,结果显示正确。因为是从最后一个开始数,删掉的元素,改变数组长度,也不会对前面的元素有影响。可以保证每一项都是可以被遍历到的。
总结,在一筹莫展时,或者好像把问题弄得越来越复杂时,应该主动跳出来,尝试着从不同角度,甚至是反方向尝试一下。
可能会有惊喜。