JavaScript高級程序設計-摘要筆記-7

高級用法

1. 安全的類型檢測

function isArray (value) {
  return Object.prototype.toString.call(value) === '[Object Array]'
}
function isFunction (value) {
  return Object.prototype.toString.call(value) === '[Object Function]'
}
function isRegExp (value) {
  return Object.prototype.toString.call(value) === '[Object RegExp]'
}

這種方法能夠檢測一個對象是不是某種類型的原生對象。前提是 Object.prototype.toString() 方法未被修改
在 Web 中可以區分原生對象和非原生對象很重要。這樣才能確切知道某個對象到底有哪些功能。瀏覽器

2. 做用域安全的構造函數

緣由以下:安全

function Person (name, age, job) {
  this.name = name
  this.age = age
  this.job = job
}
var Person = new Person('wfc', 25, 'frontend')

若是忘記使用 new 操做符,那麼,構造函數裏的this將指向全局window,這樣會在window對象上添加額外的屬性,可能覆蓋其餘有用的屬性致使錯誤。
安全的作法:app

function Person (name, age, job) {
  if (this instanceof Person) {
    this.name = name
    this.age = age
    this.job = job
  } else {
    return new arguments.callee(name, age, job)
  }
}

這樣處理後,若是僅採用構造函數模式來繼承,可能會出問題
如:frontend

function Person (name, age, job) {
  this.name = name
  this.age = age
  this.job = job
}
function Student (name, age, job, sex) {
  Person.call(this, name, age, job)
  this.sex = sex
}
var ming = new Student('ming', 12, 'student', 'male')
console.log(ming.name) // undefined

因此採用做用域安全的構造函數,要求採用以下措施dom

function Person (name, age, job) {
  this.name = name
  this.age = age
  this.job = job
}
function Student (name, age, job, sex) {
  Person.call(this, name, age, job)
  this.sex = sex
}
Student.prototype = new Person()
var gang = new Student('gang', 13, 'student', 'male')
console.log(ming.name) // 'gang'

3. 惰性載入函數

如:函數

function createXHR () {
  if (typeof XMLHttpRequest != undefined) { // 這裏不用 == 來判斷,由於不一樣瀏覽器下結果不同,safari 獲得的是 'object',其餘瀏覽器是'function'
    return new XMLHttpRequest()
  } else if (typeof ActiveXObject != undefined) {
    if (typeof arguments.callee.activeXString != 'string') {
      var versions = [
        'MSXML2.XMLHttp.6.0', 'MSXML2.XMLHttp.3.0', 'MSXML2.XMLHttp'
      ]
      for (var i = 0, len = versions.length; i < len; i++) {
        try {
          new ActiveXObject(versions[i]);
          arguments.callee.activeXString = versions[i];
          break;
        } catch (ex) {}
      }
    } else {
      return new ActiveXObject(arguments.callee.activeXString)
    }
  } else {
    throw new Error('no XHR object available')
  }
}

注:IE7+ 就原生支持 XMLHttpRequest 對象。
每次調用 createXHR() 的時候,它都要對瀏覽器所支持的能力進行檢查,但其實只須要首次載入時檢查一次就能夠肯定該瀏覽器支持哪一個 XHR 對象。
惰性載入表示函數執行的分支僅會發生一次。
方法1:this

function createXHR () {
  if (typeof XMLHttpRequest != undefined) {
    createXHR = function () {
      return new XMLHttpRequest()
    }
  } else if (typeof ActiveXObject != undefined) {
    var versions = [
      'MSXML2.XMLHttp.6.0', 'MSXML2.XMLHttp.3.0', 'MSXML2.XMLHttp'
    ]
    for (var i = 0, len = versions.length; i < len; i++) {
      try {
        new ActiveXObject(versions[i]);
        createXHR = function () {
          return new ActiveXObject(versions[i]);
        }
        break;
      } catch (ex) {}
    }
  } else {
    createXHR = function () {
      throw new Error('No XHR object available')
    }
  }
  return createXHR()
}

方法2:es5

var createXHR = (function () {
  if (typeof XMLHttpRequest != undefined) { // 這裏不用 == 來判斷,由於不一樣瀏覽器下結果不同,safari 獲得的是 'object',其餘瀏覽器是'function'
    return function () {
      return new XMLHttpRequest()
    }
  } else if (typeof ActiveXObject != undefined) {
    var versions = [
      'MSXML2.XMLHttp.6.0', 'MSXML2.XMLHttp.3.0', 'MSXML2.XMLHttp'
    ]
    for (var i = 0, len = versions.length; i < len; i++) {
      try {
        new ActiveXObject(versions[i]);
        return function () {
          return new ActiveXObject(versions[i]);
        }
        break;
      } catch (ex) {}
    }
  } else {
    throw new Error('no XHR object available')
  }
}())

4. 函數綁定

函數綁定指的是建立一個新函數,能夠在特定的this環境中以指定參數調用另外一個函數。
如:prototype

function bind (fn, context) {
  return function () {
    return fn.apply(context, arguments)
  }
}
this.name = 'wfc'
var obj = {
  name: 'abc',
  sayName: function () {
    console.log(this.name)
  }
}
obj.sayName() // 'abc'
var newSayName = bind(obj.sayName, this)
newSayName() // 'wfc'

es5 爲全部函數定義了一個原生的 bind() 方法。
被綁定函數與普通函數相比有更多的分銷,它們須要更多的內存,同時也由於多重函數調用稍微慢一點,因此最好只在必要時使用。
// 支持的瀏覽器有:IE 9+、Chrome、Firefox 4+、Safari 5.1+、Opera 11.6+設計

5. 原生bind() 方法

bind()方法會建立一個新函數。當這個新函數被調用時,bind()的第一個參數將做爲它運行時的 this, 以後的一序列參數將會在傳遞的實參前傳入做爲它的參數。
語法: fun.bind(thisArg[, arg1[, arg2[, ...]]])
如:

this.x = 9;
var module = {
  x: 81,
  getX: function() { return this.x; }
};

module.getX(); // 返回 81

var retrieveX = module.getX;
retrieveX(); // 返回 9, 在這種狀況下,"this"指向全局做用域

// 建立一個新函數,將"this"綁定到module對象
// 新手可能會被全局的x變量和module裏的屬性x所迷惑
var boundGetX = retrieveX.bind(module);
boundGetX(); // 返回 81

偏函數(Partial Functions)
bind()的另外一個最簡單的用法是使一個函數擁有預設的初始參數。
如:

function list() {
  return Array.prototype.slice.call(arguments);
}

var list1 = list(1, 2, 3); // [1, 2, 3]

// Create a function with a preset leading argument
var leadingThirtysevenList = list.bind(undefined, 37);

var list2 = leadingThirtysevenList(); // [37]
var list3 = leadingThirtysevenList(1, 2, 3); // [37, 1, 2, 3]

在默認狀況下,使用 window.setTimeout() 時,this 關鍵字會指向 window (或全局)對象。
當使用類的方法時,須要 this 引用類的實例,你可能須要顯式地把 this 綁定到回調函數以便繼續使用實例。

function LateBloomer() {
  this.petalCount = Math.ceil(Math.random() * 12) + 1;
}

// Declare bloom after a delay of 1 second
LateBloomer.prototype.bloom = function() {
  window.setTimeout(this.declare.bind(this), 1000);
};

LateBloomer.prototype.declare = function() {
  console.log('I am a beautiful flower with ' +
    this.petalCount + ' petals!');
};

var flower = new LateBloomer();
flower.bloom();  // 一秒鐘後, 調用'declare'方法

6. 函數柯里化 (function currying)

函數柯里化 (function currying),用於建立已經設置好了一個或者多個參數的函數。
如:

function curry(fn) {
  var args = Array.prototype.slice.call(arguments, 1)
  return function () {
    var newArgs = args.concat(Array.prototype.slice.call(arguments))
    fn.apply(undefined, newArgs)
  }
}

或者

function curry(fn, context) {
  var args = Array.prototype.slice.call(arguments, 2)
  return function () {
    var newArgs = args.concat(Array.prototype.slice.call(arguments))
    fn.apply(context, newArgs)
  }
}

es5 的 bind() 方法能夠實現函數柯里化。具體見上一條。
須要注意的是 bind() curry() 不該濫用,由於每一個函數都會帶來額外的開銷。

7. 不可擴展對象。

默認狀況下,全部對象都是能夠擴展的。
Object.preventExtensions(obj) 能夠阻止給對象添加新的屬性和方法。可是已有的屬性和方法不受任何影響,能夠被修改和刪除。
Object.isExtensible() 能夠肯定對象是否能夠擴展。
如:

var obj = {
  name: 'wfc'
}
Object.isExtensible(obj) // true
Object.preventExtensions(obj)
obj.age = 18
obj.age // undefined
Object.isExtensible(obj) // false
obj.name = 'ming'
obj.name // 'ming'
delete obj.name // true
obj.name // undefined

8. 密封的對象。

es5 爲對象定義的第二個保護級別是密封對象。
密封對象不可擴展,並且全部成員的 [[Configurable]] 特性被設置成false,這意味着不能刪除對象,可是屬性值是能夠修改的。
密封對象的方法是 Object.seal() 檢測對象是否被密封的方法是 Object.isSealed() 全部密封對象用 Object.isExtensible() 檢測都是false。
如:

var person = {
  name: 'gang'
}
Object.isSealed(person) // false
Object.seal(person)
Object.isSealed(person) // true
Object.isExtensible(person) // false
person.name = 'wang'
person.name // 'wang'
delete person.name // false
person.name // 'wang'

可是能夠賦值成 null 或者 undefined
如:

person.name = null
person.name // null
person.name = undefined
person.name // undefined

9. 凍結的對象

最嚴格的防篡改級別是凍結。凍結的對象既不可擴展,又是密封的,並且對象數據屬性 [[Writable]] 特性會被設置成false。
凍結對象的方法是 Object.freeze()
檢測對象是否凍結的方法是 Object.isFrozen()
如:

var person = {
  name: 'wang'
}
Object.freeze(person)
Object.isFrozen(person) // true
delete person.name // false
person.name // 'wang'
person.name = 'gang'
person.name // 'wang'

對 JS 庫的做者而言,凍結對象是頗有用的。由於 JS 庫最怕有人意外(或有意)的修改核心對象。

10. 定時器

對於 setTimeout() 實際執行時間大於等於設定時間。
對於 setInterval() 兩次執行的實際間隔時間小於等於設定時間。

11. 函數循環分割

對於這樣的循環

for (var i = 0, len = arr.length; i < len; i++) {
  process(arr[i])
}

若是完成 process() 的時間較長,那麼能夠作以下分割。

function chunk (arr, process, context) {
  setTimeout(function () {
    var item = arr.shift()
    process.call(context, item)

    if (arr.length > 0) {
      setTimeout(arguments.callee, 100)
    }
  }, 100)
}

一旦某個函數須要花 50ms 以上來處理,就應該看看可否分割開來處理了。

12. 函數節流。

瀏覽器中某些計算和處理要比其餘昂貴不少,好比,DOM 操做比非 DOM 交互須要更多的內存和CPU時間。
函數節流背後的基本思想是:某些代碼不能夠在沒有間斷的狀況下連續重複執行。
好比連續重複執行的 resize 事件。
如下是該模式的基本形式:

var processor = {
  timeoutId: null,
  performProcessor: function () {
    handleProcess()
  },
  process: function () {
    clearTimeout(this.timeoutId)

    var that = this
    this.timeoutId = setTimeout(function () {
      that.performProcessor()
    }, 100)
  }
}

或者使用函數

function throttle (method, context) {
  clearTimeout(method.tid)
  method.tid = setTimeout(function () {
    method.call(context)
  }, 100)
}

只要代碼是週期性執行的,都應該使用節流,可是你不能控制請求執行的速率。這裏 throttle() 函數用 100 ms作間隔,能夠根據需求來更改。

13. 自定義事件

定義:

function EventTarget () {
  this.handlers = {}
}

EventTarget.prototype = {
  constructor: EventTarget,
  addHandler: function (type, handler) {
    if (typeof this.handlers[type] === 'undefined') {
      this.handlers[type] = []
    }
    this.handlers[type].push(handler)
  },
  fire: function (event) {
    if (!event.target) {
      event.target = this
    }
    if (this.handlers[event.type] instanceof Array) {
      var handlers = this.handlers[event.type]
      for (var i = 0, len = handlers.length; i < len; i++) {
        handlers[i](event)
      }
    }
  },
  removeHandler: function (type, handler) {
    if (this.handlers[type] instanceof Array) {
      var handlers = this.handlers[type]
      for (var i = 0, len = handlers.length; i < len; i++) {
        if (handlers[i] === handler) {
          handlers.splice(i, 1)
          break
        }
      }
    }
  }
}

使用:

function handleMessage (event) {
  console.log(event.message)
}
var target = new EventTarget()
target.addHandler('message', handleMessage)
target.fire({
  type: 'message',
  message: 'Hello'
}) // 'hello'
target.removeHandler('message', handleMessage)
target.fire({
  type: 'message',
  message: 'Hello'
}) // 'undefined'

另一種用法:

function Person (name, age) {
  EventTarget.call(this)
  this.name = name
  this.age = age
}
Person.prototype = Object.create(EventTarget.prototype)

Person.prototype.say = function (message) {
  this.fire({
    type: 'message',
    message: message
  })
}
var li = new Person('li', 18)
li.addHandler('message', handleMessage)
li.say('hahaha') // 'hahaha'

當代碼中存在多個部分在特定時刻相互交互的狀況下,自定義事件就很是有用了。
使用自定義事件有助於結藕相關對象,保持功能的隔絕。

關於《JavaScript高級程序設計(第三版)》這本書,我就選擇性的看了一部分章節,因此,目前的摘要筆記總結已所有結束。
相關文章
相關標籤/搜索