js高级程序设计学习之高级函数


安全的类型检测

function isArray(value) {
  return Object.prototype.toString.call(value) === '[object Array]';
}

function isFunction(value) {
  return Object.prototype.toString.call(value) === '[object Function]';
}

//检测原生JSON对象
function isRegExp(value) {
  return Object.prototype.toString.call(value) === '[object RegExp]';
}

var isNativeJson =
  window.JSON && Object.prototype.toString.call(JSON) === '[object JSON]';

不过要注意 Object.prototype.toString 本身可能被改写。

作用域安全的构造函数

当使用 new 调用构造函数时,构造函数内用到的 this 对象会指向新创建的对象实例,如:

function Person(name) {
  this.name = name;
}
var person = new Person('oliver');
console.log(person.name);

问题是当没有使用 new 操作符,直接调用构造函数,this 会映射到全局对象 window 上,导致错误对象属性的意外增加:

function Person(name) {
  this.name = name;
}
var person = Person('oliver');
console.log(window.name); //oliver

解决办法是创建一个作用域安全的构造函数:

function Person(name) {
  if (this instanceof Person) {
    //如果this是Person的实例
    this.name = name;
  } else {
    return new Person(name); //否则调用new操作符
  }
}
var person1 = Person('oliver');
console.log(person1.name); //oliver
var person2 = new Person('troy');
console.log(person2.name); //troy
console.log(window.name); //""

但是,如果使用构造函数窃取模式的继承且不实用原型链,那么这个继承很可能被破坏如:

function Person(name) {
  if (this instanceof Person) {
    //如果this是Person的实例
    this.name = name;
  } else {
    return new Person(name); //否则调用new操作符
  }
}
function People(name, age) {
  Person.call(this, name);
  this.age = age;
}
var p = new People('Oliver', 18);
console.log(p.name); //undefined
console.log(p.age); //18

结合使用原型链或者寄生组合则可以解决这个问题:

function Person(name) {
  if (this instanceof Person) {
    //如果this是Person的实例
    this.name = name;
  } else {
    return new Person(name); //否则调用new操作符
  }
}
function People(name, age) {
  Person.call(this, name);
  this.age = age;
}
People.prototype = new Person(); //关键点
var p = new People('Oliver', 18);
console.log(p.name); //Oliver
console.log(p.age); //18

惰性载入函数

惰性函数表示函数执行的分支仅会发生一次。有两种实现惰性载入函数的方式,第一种就是在函数被调用时在处理函数。在第一次调用的过程中,该函数被覆盖为另一个按合适方式执行的函数,这样任何对原函数的调用就不用再经过执行的分支了。

function createXHR() {
  if (typeof XMLHttpRequest !== 'undefined') {
    createXHR = function () {
      //关键点
      return new XMLHttpRequest();
    };
  } else if (typeof ActiveXObject !== 'undefined') {
    createXHR = function () {
      //关键点
      return new ActiveXObject(['MSXML2.XMLHttp']);
    };
  } else {
    createXHR = function () {
      //关键点
      throw new Error('No XHR object available.');
    };
  }
  return createXHR(); //关键点
}

第二种实现惰性载入函数的方式就是在声明函数时就指定适当的函数。这样,第一次调用函数时就不会损失性能了,而在代码首次加载时会损失一些性能。

var createXHR = (function () {
  if (typeof XMLHttpRequest !== 'undefined') {
    return function () {
      //关键点
      return new XMLHttpRequest();
    };
  } else if (typeof ActiveXObject !== 'undefined') {
    return function () {
      //关键点
      return new ActiveXObject(['MSXML2.XMLHttp']);
    };
  } else {
    return function () {
      //关键点
      throw new Error('No XHR object available.');
    };
  }
})();

这个例子中使用的技巧是创建一个匿名、自执行的函数,用以确定应该使用哪一个函数实现。

函数绑定

函数绑定要创建一个函数,可以在特定的 this 环境中以指定参数调用另一个函数。该技巧常常和回调函数与事件处理程序一起使用, 以便在将函数作为变量传递的同时保留代码的执行环境。

var handler = {
  message: 'Event handled',
  handleClick: function (event) {
    console.log(this.message);
  },
};

handler.handleClick();

var a = handler.handleClick;
a();

var btn1 = document.getElementById('my-btn1');
btn1.addEventListener('click', handler.handleClick, false);

var btn2 = document.getElementById('my-btn2');
btn2.addEventListener(
  'click',
  function () {
    handler.handleClick();
  },
  false
);

btn1 点击后显示 undefined,btn2 利用闭包来修正这个问题

由于代码之中存在着 this 变量,而 this 在当前环境下指向确定的对象,但是当更改代码的执行环境时,就会出现问题了。为了解决这个问题, javascript 函数库中实现了一个 bind() 函数来解决这个问题。

一个简单的 bind() 函数接收一个函数和一个环境, 并返回一个在给定环境中调用给定函数的函数, 并且将所有参数原封不动传递过去。 语法如下:

function bind(fn, context) {
  return function () {
    return fn.apply(context, arguments);
  };
}

注意这里使用的 arguments 并不是 bind() 的, 是内部函数的。

var handler = {
  message: 'Event handled',
  handleClick: function (event) {
    alert(this.message);
  },
};
var btn = document.getElementById('my-btn');
EventUtil.addHandler(btn, 'click', bind(handler.handleClick, handler));

ECMAScript5 为所有函数定义了一个原生的 bind() 方法, 进一步简化了操作。

var handler = {
  message: 'Event handled',
  handleClick: function (event) {
    alert(this.message);
  },
};
var btn = document.getElementById('my-btn');
EventUtil.addHandler(btn, 'click', handler.handleClick.bind(handler));

它们主要用于事件处理程序以及 setTimeout() 和 setInterval()。 然而被绑定函数与普通函数相比有更多的开销, 它们需要更多内存, 同时也因为多重函数调用稍微慢一些, 所以最好只在必要时使用。

函数柯里化

它用于创建已经设置好了一个或多个参数的函数。 函数柯里化的基本方法是: 使用一个闭包返回一个函数。 当函数被调用时, 返回的函数还需要设置一些传入的参数。

柯里化函数通常由以下步骤动态的创建: 调用另一个函数并为它传入要柯里化的函数和必要参数。 下面是创建柯里化函数的通用方式:

function curry(fn) {
  var args = Array.prototype.slice.call(arguments, 1);
  return function () {
    var innerArgs = Array.prototype.slice.call(arguments);
    var finalArgs = args.concat(innerArgs);
    return fn.apply(null, finalArgs);
  };
}

函数柯里化还常常作为函数绑定的一部分包含在其中,构造出更为复杂的 bind 函数

function bind(fn, contenxt) {
  var args = Array.prototype.slice.call(arguments, 2);
  return function () {
    var innerArgs = Array.prototype.slice.call(arguments);
    var finalArgs = args.cancat(innerArgs);
    return fn.apply(context, finalArgs);
  };
}

当你想除了 event 对象再额外给事件处理程序传递参数时,这非常有用。

EventUtil.addHandler(
  btn,
  'click',
  bind(handler.handleClick, handler, 'my-btn')
);

ES5 的 bind 方法也实现了函数柯里化,只需要在 this 值后再传另一个参数

EventUtil.addHandler(btn, 'click', handler.handleClick.bind(handler, 'my-btn'));

函数绑定和柯里化都不应滥用,因为每个函数会带来额外的开销


文章作者: Angus
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Angus !
  目录