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 中可以区分原生对象和非原生对象很重要。这样才能确切知道某个对象到底有哪些功能。浏览器
缘由以下:安全
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'
如:函数
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') } }())
函数绑定指的是建立一个新函数,能够在特定的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+设计
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'方法
函数柯里化 (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() 不该滥用,由于每一个函数都会带来额外的开销。
默认状况下,全部对象都是能够扩展的。
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
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
最严格的防篡改级别是冻结。冻结的对象既不可扩展,又是密封的,并且对象数据属性 [[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 库最怕有人意外(或有意)的修改核心对象。
对于 setTimeout() 实际执行时间大于等于设定时间。 对于 setInterval() 两次执行的实际间隔时间小于等于设定时间。
对于这样的循环
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 以上来处理,就应该看看可否分割开来处理了。
浏览器中某些计算和处理要比其余昂贵不少,好比,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作间隔,能够根据需求来更改。
定义:
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高级程序设计(第三版)》这本书,我就选择性的看了一部分章节,因此,目前的摘要笔记总结已所有结束。