3. JS设计模式与开发实践摘录(一)

1.数据的封装
  • 在许多语言的对象系统中,封装数据是由语法解析来实现的,这些语言也许提供了private、public、protected 等关键字来提供不同的访问权限。但JavaScript 并没有提供对这些关键字的支持,我们只能依赖变量的作用域来实现封装特性,
    而且只能模拟出public 和private 这两种封装性。
// 除了ES6提供的 let 之外,一般我们通过函数来创建作用域
var myObject = (function() {
   // 私有变量
   var _name = 'sven';
  // 公开的public方法
   return {
      getName: function () {
          return _name;
      }
   }
})()
console.log( myObject.getName() ); // 输出:sven
console.log( myObject.__name ) // 输出:undefined
2.使用克隆的原型模式
  • 不同于传统的类的方式,先创建一个类实例,然后想这个实例中添加属性和方法;
  • 原型模式的实现关键,是语言本身是否提供了clone 方法。ECMAScript 5 提供了Object.create方法,可以用来克隆对象。代码如下:
var Plane = function () {
    this.blood = 100;
    this.attackLevel = 1;
    this.defenseLevel = 1;
};
var plane = new Plane();
plane.blood = 500;
plane.attackLevel = 10;
plane.defenseLevel = 7;

var clonePlane = Object.create( plane );
console.log( clonePlane ); // 输出:Object {blood: 500, attackLevel: 10, defenseLevel: 7}
// 在不支持Object.create 方法的浏览器中,则可以使用以下代码:
Object.create = Object.create || function( obj ){
   var F = function(){};
   F.prototype = obj;
   return new F();
}

//克隆是创建对象的手段

3.JS中的原型继承

 所有的数据都是对象。
 要得到一个对象,不是通过实例化类,而是找到一个对象作为原型并克隆它。
 对象会记住它的原型。
 如果对象无法响应某个请求,它会把这个请求委托给它自己的原型。

// ES6继承
class Animal {
  constructor(name) {
    this.name = name;
   }

  getName() {
    return this.name;
   }
}
class Dog extends Animal {
  constructor(name) {
    super(name);
  }
  speak() {
    return "woof";
  }
}
var dog = new Dog("Scamp");
console.log(dog.getName() + ' says ' + dog.speak());
4.this的指向问题

//除去不常用的with 和eval 的情况,具体到实际应用中,this 的指向大致可以分为以下4 种。
 作为对象的方法调用。
 作为普通函数调用。
 构造器调用。
 Function.prototype.call 或Function.prototype.apply 调用。

5.call apply的用途
  • apply 接受两个参数,第一个参数指定了函数体内this 对象的指向,第二个参数为一个带下标的集合,这个集合可以为数组,也可以为类数组,apply 方法把这个集合中的元素作为参数传递给被调用的函数;
  • 当使用call 或者apply 的时候,如果我们传入的第一个参数为null,函数体内的this 会指
    向默认的宿主对象,在浏览器中则是window:
//1. 改变this 指向
var obj1 = {
  name: 'sven'
};
var obj2 = {
  name: 'anne'
};
window.name = 'window';
var getName = function(){
   alert ( this.name );
};
getName(); // 输出: window
getName.call( obj1 ); // 输出: sven
getName.call( obj2 ); // 输出: anne

// 假如该事件函数中有一个内部函数func,在事件内部调用func 函数时,func 函数体内的this就指向了window,而不是我们预期的div,见如下代码:
document.getElementById( 'div1' ).onclick = function(){
  alert( this.id ); // 输出:div1
  var func = function(){
    alert ( this.id ); // 输出:undefined
  }
  func();
};
这时候我们用call 来修正func 函数内的this,使其依然指向div:
document.getElementById( 'div1' ).onclick = function(){
  var func = function(){
     alert ( this.id ); // 输出:div1
  }
  func.call( this );
};
//2. Function.prototype.bind
// 大部分高级浏览器都实现了内置的Function.prototype.bind,用来指定函数内部的this 指向,即使没有原生的Function.prototype.bind 实现,我们来模拟一个也不是难事,代码如下:
Function.prototype.bind = function( context ){
  var self = this; // 保存原函数
  return function(){ // 返回一个新的函数
    return self.apply( context, arguments ); // 执行新的函数的时候,会把之前传入的context当作新函数体内的this
  }
};
var obj = {
  name: 'sven'
};
var func = function(){
  alert ( this.name ); // 输出:sven
}.bind( obj);
func();
6.单例设计
var getSingle = function ( fn ) {
  var ret;
  return function () {
    return ret || ( ret = fn.apply( this, arguments ) );
  };
};

var getScript = getSingle(function(){
  return document.createElement( 'script' );
});
var script1 = getScript();
var script2 = getScript();
alert ( script1 === script2 ); // 输出:true
7.JS实现AOP(面向切面编程)
  • 主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些
    跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。把这些功能抽离出来之后,再通过“动态织入”的方式掺入业务逻辑模块中。这样做的好处首先是可以保持业务逻辑模块的纯净和高内聚性,其次是可以很方便地复用日志统计等功能模块。
Function.prototype.before = function( beforefn ){
  var __self = this; // 保存原函数的引用
  return function(){ // 返回包含了原函数和新函数的"代理"函数
    // 执行新函数,且保证this 不被劫持,新函数接受的参数也会被原封不动地传入原函数新函数在原函数之前执行   
    beforefn.apply( this, arguments ); 
 //  执行原函数并返回原函数的执行结果, 并且保证this 不被劫持
   return __self.apply( this, arguments );// __self === func
  }
};
Function.prototype.after = function( afterfn ){
  var __self = this;
  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 和打印数字3 的两个函数通过AOP 的方式动态植入func 函数。通过执行上面的代码,我们看到控制台顺利地返回了执行结果1、2、3。

// 实际的例子,点击登录之后统计点击事件的点击次数


  


// 实际的例子,给每个ajax 请求都加上Token 参数:

Function.prototype.before = function( beforefn ){
  var __self = this;
  return function(){
    beforefn.apply( this, arguments ); // (1)
    return __self.apply( this, arguments ); // (2)
  }
}
// 为了解决这个问题,先把ajax 函数还原成一个干净的函数:
var ajax= function( type, url, param ){
  console.log(param); // 发送ajax 请求的代码略
};
// 然后把Token 参数通过Function.prototyte.before 装饰到ajax 函数的参数param 对象中:
var getToken = function(){
  return 'Token';
}
ajax = ajax.before(function( type, url, param ){
  param.Token = getToken();
});
ajax( 'get', 'http:// xxx.com/userinfo', { name: 'sven' } );
// 从ajax 函数打印的log 可以看到,Token 参数已经被附加到了ajax 请求的参数中:
{name: "sven", Token: "Token"}

// 实例:插件式的表单验证

Function.prototype.before = function( beforefn ){
  var __self = this;
  return function(){
    if ( beforefn.apply( this, arguments ) === false ){
    // beforefn 返回false 的情况直接return,不再执行后面的原函数
      return;
    }
    return __self.apply( this, arguments );
   }
}
var validata = function(){
if ( username.value === '' ){
  alert ( '用户名不能为空' );
  return false;
}
if ( password.value === '' ){
  alert ( '密码不能为空' );
  return false;
  }
}
var formSubmit = function(){
var param = {
  username: username.value,
  password: password.value
}
ajax( 'http:// xxx.com/login', param );
}
formSubmit = formSubmit.before( validata );
submitBtn.onclick = function(){
  formSubmit();
}

// 因为函数通过Function.prototype.before 或者Function.prototype.after 被装饰之后,返回的实际上是一个新的函数,如果在原函数上保存了一些属性,那么这些属性会丢失。
8.JS 实现柯里化
  • 一个对象也未必只能使用它自身的方法,那么有什么办法可以让对象去借用一个原本不属于它的方法呢?
var obj1 = {
  name: 'sven'
};
var obj2 = {
  getName: function(){
    return this.name;
  }
};
console.log( obj2.getName.call( obj1 ) ); // 输出:sven

//
// 我们常常让类数组对象去借用Array.prototype 的方法,这是call 和apply 最常见的应用场
景之一:
(function(){
   Array.prototype.push.call( arguments, 4 ); // arguments 借用Array.prototype.push 方法
console.log( arguments ); // 输出:[1, 2, 3, 4]
})( 1, 2, 3 );
9.函数节流
  • 在一些不是用户自动出发的函数调用,在这些场景下,函数有可能被非常频繁地调用,而造成大的性能问题。
     window.onresize 事件。
    mousemove 事件
    上传进度
  • 实现函数节流的原理:设置时间段来忽略一些请求
  • 代码实现
var throttle = function ( fn, interval ) {
  var __self = fn, // 保存需要被延迟执行的函数引用
  timer, // 定时器
  firstTime = true; // 是否是第一次调用
  return function () {
    var args = arguments,
    __me = this;
    if ( firstTime ) { // 如果是第一次调用,不需延迟执行
       __self.apply(__me, args);
      return firstTime = false;
    }
    if ( timer ) { // 如果定时器还在,说明前一次延迟执行还没有完成
      return false;
    }
   timer = setTimeout(function () { // 延迟一段时间执行
     clearTimeout(timer);
     timer = null;
      __self.apply(__me, args);
    }, interval || 500 );
  };
};
window.onresize = throttle(function(){
  console.log( 1 );
}, 500 );

你可能感兴趣的:(3. JS设计模式与开发实践摘录(一))