资讯专栏INFORMATION COLUMN

JavaScript高级程序设计-摘要笔记-7

macg0406 / 495人阅读

摘要:作用域安全的构造函数原因如下如果忘记使用操作符,那么,构造函数里的将指向全局,这样会在对象上添加额外的属性,可能覆盖其他有用的属性导致错误。

高级用法 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对象上添加额外的属性,可能覆盖其他有用的属性导致错误。
安全的做法:

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)
  }
}

这样处理后,如果仅采用构造函数模式来继承,可能会出问题
如:

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

所以采用作用域安全的构造函数,要求采用如下措施

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:

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:

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环境中以指定参数调用另一个函数。
如:

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高级程序设计(第三版)》这本书,我就选择性的看了一部分章节,所以,目前的摘要笔记总结已全部结束。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/81201.html

相关文章

  • JavaScript高级程序设计摘要笔记-1

    摘要:说明此摘要笔记系列是我最近看高级程序设计第版随手所记。摘要笔记本身没有系统性,没有全面性可言,写在这里供有一定基础的前端开发者参考交流。对每一项运行给定函数,返回该函数会返回的项组成的数组。是的反操作是的反操作第一部分结束。 说明: 此摘要笔记系列是我最近看《JavaScript高级程序设计(第3版)》随手所记。 里面分条列举了一些我认为重要的、需要记下的、对我有帮助的点,是按照我看...

    chavesgu 评论0 收藏0
  • JavaScript高级程序设计摘要笔记-4

    摘要:思路是,使用原型链对原型属性和方法进行继承,借用构造函数实现对实例属性的继承。注意使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率,这一点与构造函数模式类似。无论什么情况下都会调用两次超类型的构造函数。 说明: 此摘要笔记系列是我最近看《JavaScript高级程序设计(第3版)》随手所记。里面分条列举了一些我认为重要的、需要记下的、对我有帮助的点,是按照我看的顺序来的...

    zr_hebo 评论0 收藏0
  • JavaScript高级程序设计摘要笔记-3

    摘要:如果重设构造函数的原型对象,那么,会切断新的原型对象和任何之前已经存在的构造函数实例之间的联系,它们引用的仍然是最初的原型。说明返回的对象与构造函数或者与构造函数的原型属性没有关系。 说明: 此摘要笔记系列是我最近看《JavaScript高级程序设计(第3版)》随手所记。里面分条列举了一些我认为重要的、需要记下的、对我有帮助的点,是按照我看的顺序来的。摘要笔记本身没有系统性,没有全面性...

    AndroidTraveler 评论0 收藏0
  • JavaScript高级程序设计摘要笔记-2

    摘要:说明此摘要笔记系列是我最近看高级程序设计第版随手所记。其中,描述符对象的属性必须是设置其中一个或多个值,可以修改对应的特性值。如支持的浏览器,可以取得指定属性的描述符。 说明: 此摘要笔记系列是我最近看《JavaScript高级程序设计(第3版)》随手所记。里面分条列举了一些我认为重要的、需要记下的、对我有帮助的点,是按照我看的顺序来的。摘要笔记本身没有系统性,没有全面性可言,写在这里...

    roland_reed 评论0 收藏0
  • JavaScript高级程序设计摘要笔记-5

    摘要:函数表达式和闭包函数声明的一个重要特征是函数声明提升如递归递归函数是在一个函数通过名字调用自身的情况下构成的。注意中已经是块级作用域了,所以这些东西感觉实际用途没有那么大,但是对理解闭包对作用域链中的属性的引用,这一点还是有作用的。 函数表达式和闭包 1. 函数声明的一个重要特征是函数声明提升 如: sayHi() function sayHi () { console.log(h...

    JerryWangSAP 评论0 收藏0

发表评论

0条评论

macg0406

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<