JavaScript设计模式与开发实践(一)

JavaScript设计模式与开发实践

this、call和apply

tips

  1. 如果构造器(构造函数)显式地返回了一个object类型的对象,那么此次运算结果最终会返回这个对象,如果构造器不显式地返回任何数据,或者是返回一个非对象类型的数据,就不会造成上述
    问题
var MyClass = function(){
    
    
  this.name = 'sven';
  return {
    
     // 显式地返回一个对象
    name: 'anne'
  }
};
var obj = new MyClass();
alert ( obj.name ); // 输出:anne


var MyClass = function(){
    
    
  this.name = 'sven'
  return 'anne'; // 返回 string 类型
};
var obj = new MyClass();
alert ( obj.name ); // 输出:sven
  1. 当使用 call 或者 apply 的时候,如果我们传入的第一个参数为 null ,函数体内的 this 会指向默认的宿主对象,在浏览器中则是 window,但如果是在严格模式下,函数体内的 this 还是为 null
var func = function( a, b, c ){
    
    
  alert ( this === window ); // 输出 true
};
func.apply( null, [ 1, 2, 3 ] );

var func = function( a, b, c ){
    
    
  "use strict";
  alert ( this === null ); // 输出 true
}
func.apply( null, [ 1, 2, 3 ] );

// 有时候我们使用 call 或者 apply 的目的不在于指定 this 指向,而是另有用途,比如借用其他对象的方法。那么我们可以传入 null 来代替某个具体的对象
Math.max.apply( null, [ 1, 2, 5, 3, 4 ] ) // 输出:5
  1. bind的实现
Function.prototype.bind = function(context){
    
    
  var self = this  // 指向调用bind的函数
  return function(){
    
     // bind是返回函数,没有直接运行
    return self.apply(context,arguments)  // context为传入的改变this的对象
  }
}


// 升级版
Function.prototype.bind = function(){
    
    
  var self = this, // 保存原函数
  context = [].shift.call( arguments ), // 需要绑定的 this 上下文
  args = [].slice.call( arguments ); // 剩余的参数转成数组
  return function(){
    
     // 返回一个新的函数
    return self.apply( context, [].concat.call( args, [].slice.call( arguments ) ) );
    // 执行新的函数的时候,会把之前传入的 context 当作新函数体内的 this
    // 并且组合两次分别传入的参数,作为新函数的参数
  }
};
  1. 类数组转数组
[].slice.call(arguments)
// 解析:调用[]原型上的slice方法,改变slice方法中this的指向为arguments并执行slice方法

// 同理可以推断,可以把“任意”对象(对象本身要可以存取属性;对象的 length 属性可读写)传入 Array.prototype.push 
var a = {
    
    };
Array.prototype.push.call( a, 'first' );  // a = {0:'first',length:1}
alert ( a.length ); // 输出:1
alert ( a[ 0 ] ); // first
  1. 鸭子类型
    在程序设计中,鸭子类型是动态类型的一种风格。在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由"当前方法"方法 (计算机科学)")和属性的集合"决定。这个概念的名字来源于由James Whitcomb Riley提出的鸭子测试,“鸭子测试”可以这样表述:“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。” 在鸭子类型中,关注点在于对象的行为,能作什么;而不是关注对象所属的类型

高阶函数

高阶函数是指至少满足下列条件之一的函数。

  • 函数可以作为参数被传递;
  • 函数可以作为返回值输出。
  1. 函数作为参数传递
    把函数当作参数传递,这代表我们可以抽离出一部分容易变化的业务逻辑,把这部分业务逻
    辑放在函数参数中,这样一来可以分离业务代码中变化与不变的部分。其中一个重要应用场景就
    是常见的回调函数。
var getUserInfo = function( userId, callback ){
    
    
  $.ajax( 'http://xxx.com/getUserInfo?' + userId, function( data ){
    
    
    if ( typeof callback === 'function' ){
    
    
      callback( data );
    }
  });
}
  1. 函数作为返回值输出
var isType = function(type){
    
    
  return function(obj){
    
    
    return Object.prototype.toString.call(obj) === '[object '+ type +']'
  }
}
var isString = isType( 'String' );
var isArray = isType( 'Array' );
var isNumber = isType( 'Number' );
console.log( isArray( [ 1, 2, 3 ] ) ); // 输出:true

// 还可以用循环语句,来批量注册这些 isType 函数:
var Type = {
    
    };
for ( var i = 0, type; type = [ 'String', 'Array', 'Number' ][ i++ ]; ){
    
     // 装X时刻
  (function( type ){
    
      // 注意这里需要写成闭包,否则(函数体中的)type会取不到(undefined),因为函数没有立即执行     原理相同于点击事件
    Type[ 'is' + type ] = function( obj ){
    
    
      return Object.prototype.toString.call( obj ) === '[object '+ type +']';
    }
  })( type )
};
Type.isArray( [] ); // 输出:true
Type.isString( "str" ); // 输出:true
  1. 高阶函数实现AOP(面向切面编程)
    在 JavaScript中实现 AOP,都是指把一个函数“动态织入”到另外一个函数之中
Function.prototype.before = function (beforefn) {
    
    
  var __self = this; // 保存原函数的引用
  return function () {
    
    
    // 返回包含了原函数和新函数的"代理"函数
    beforefn.apply(this, arguments); // 执行新函数,修正 this  指向window
    return __self.apply(this, arguments); // 执行原函数
  };
};
Function.prototype.after = function (afterfn) {
    
    
  var __self = this; // 指向func.before(function(){console.log(1)})
  return function () {
    
    
    var ret = __self.apply(this, arguments);
    afterfn.apply(this, arguments);
    return ret; // 返回原函数的返回值
  };
};

var func = function () {
    
    
  console.log(2);
};
func = func
  .before(function () {
    
    
    console.log(1);
  }).after(function(){
    
    
    console.log(3)
  })
func();
  1. 高阶函数-函数柯里化
    currying 又称部分求值。是固定部分参数,返回一个接受剩余参数的函数,也称为部分计算函数,目的是为了缩小适用范围,创建一个针对性更强的函数。核心思想是把多参数传入的函数拆成单参数(或部分)函数,内部再返回调用下一个单参数(或部分)函数,依次处理剩余的参数。(例如计算每月花费的总额,每天的消费当作参数传入但不计算,等到最后一天要计算总额的时候再执行计算)
var currying = function(fn) {
    
    
    var args = [];
    return function() {
    
    
        if (arguments.length === 0) {
    
    
            return fn.apply(this, args);
        } else {
    
    
            [].push.apply(args, arguments);
            return arguments.callee;
        }
    }
};
var cost = (function() {
    
    
    var money = 0;
    return function() {
    
    
        for (var i = 0, l = arguments.length; i < l; i++) {
    
    
            money += arguments[i];
        }
        return money;
    }
})();
var cost = currying(cost); // 转化成 currying 函数
cost(100); // 未真正求值
cost(200); // 未真正求值
cost(300); // 未真正求值
alert(cost()); // 求值并输出:600
  1. 高阶函数-uncurrying(反柯里化)
  • 反柯里化,从字面讲,意义和用法跟函数柯里化相比正好相反,扩大适用范围,创建一个应用范围更广的函数。使本来只有特定对象才适用的方法,扩展到更多的对象。
  • tip: 怎么理解Array.prototype.push.call( arguments, 4 )的运行?
    答:让arguments去借用Array.prototype上的push方法并调用它,传入参数为4(可以理解为arguments.push(4))
// 方法一
Function.prototype.uncurrying = function(){
    
    
  var self = this;  // 指向push
  return function(){
    
    
    var obj = Array.prototype.shift.call( arguments )  // 这里的arguments(即1,2,3,4)是 下面push中传入的arguments(即1,2,3)和4 使用shift方法,obj会得到[1,2,3],此时函数里的arguments剩下[4]
    return self.apply( obj, arguments )  // 相当于 self.apply([1,2,3],4)
  }
}
var push = Array.prototype.push.uncurrying();
(function(){
    
    
  push( arguments, 4 );
  console.log( arguments ); // 输出:[1, 2, 3, 4]
})( 1, 2, 3 );

// 方法二
Function.prototype.uncurrying = function(){
    
    
  var self = this;  // 指向push方法
  return function(){
    
    
    return Function.prototype.call.apply(self,arguments)  //首先执行apply方法,让self去执行call函数并传入arguments(此时arguments已经是扁平化处理过的了,此例为[1,2,3],4),self调用call方法,即self.call([1,2,3],4) 
  }
}
var push = Array.prototype.push.uncurrying();
push([1,2,3],4)  // [1,2,3,4]
  1. 高阶函数-分时函数(timeChunk)
    分时函数的原理是让创建节点的工作分批进行,比如把1s创建1000个节点,改为每200ms创建8个节点(其实就是一种异步函数的思想),避免在短时间调用大量数据的时候产生的卡顿感
//timeChunk 函数接受 3个参数,第 1个参数是创建节点时需要用到的数据,第 2个参数是封装了创建节点逻辑的函数,第 3个参数表示每一批创建的节点数量
var timeChunk = function(ary, fn, count) {
    
    
    var obj,
        t;
    var len = ary.length;
    var start = function() {
    
    
        for (var i = 0; i < Math.min(count || 1, ary.length); i++) {
    
    
            var obj = ary.shift();
            fn(obj);
        }
    };
    return function() {
    
    
        t = setInterval(function() {
    
    
            if (ary.length === 0) {
    
     // 如果全部节点都已经被创建好
                return clearInterval(t);
            }
            start();
        }, 200); // 分批执行的时间间隔,也可以用参数的形式传入
    };
};

var ary = [];
for ( var i = 1; i <= 1000; i++ ){
    
    
  ary.push( i );   // 假设 ary 装载了 1000 个好友的数据
};
var renderFriendList = timeChunk( ary, function( n ){
    
    
var div = document.createElement( 'div' );
  div.innerHTML = n;
  document.body.appendChild( div );
}, 8 );
renderFriendList();
  1. 高阶函数-惰性加载函数
    tip:第一次运行该函数后,改变该函数的主体(才能确定该函数的形式)
// 事件绑定兼容
var addEvent = function(elem, type, handler) {
    
    
    if (window.addEventListener) {
    
    
        addEvent = function(elem, type, handler) {
    
    
            elem.addEventListener(type, handler, false);
        }
    } else if (window.attachEvent) {
    
    
        addEvent = function(elem, type, handler) {
    
    
            elem.attachEvent('on' + type, handler);
        }
    }
    addEvent(elem, type, handler);
};
var div = document.getElementById('div1');
addEvent(div, 'click', function() {
    
    
    alert(1);
}); 
addEvent(div, 'click', function() {
    
    
    alert(2);
});  // 运行第二次不再需要判断if语句
  1. 高阶函数-防抖节流
    函数节流是指一定时间内js方法只跑一次。比如人的眨眼睛,就是一定时间内眨一次。这是函数节流最形象的解释。
    函数防抖是指频繁触发的情况下,只有足够的空闲时间,才执行代码一次。比如生活中的坐公交,就是一定时间内,如果有人陆续刷卡上车,司机就不会开车。只有别人没刷卡了,司机才开车。

猜你喜欢

转载自blog.csdn.net/qq_36303110/article/details/114067711