本章内容
- 使用高级函数
- 防止篡改对象
- ...
22.1 高级函数
22.1.1 安全的类型检测
JavaScript内置的类型检测并不是完全可靠,发生错误的情况不再少数,
-
例如typeof操作符噢,由于它有一些无法预知的行为,经常会导致检测数据类型时得不到靠谱的结果。Safari在对正则表达式应用typeof操作符时会返回function,因此很难判断一个值是否是函数
-
instanceof 操作符存在全局作用域的情况下,也是问题多多。
let isArray = value instanceof Array
以上代码要true
,value必须是一个数组,而且还必须与Array构造函数在同一个全局的作用域中(比如value是一个页面中的iframe定义的,就会返回false)
解决以上问题的方案是调用Object的toString方法,都会返回一个[object NativeConstructorName]格式的字符串,每个类在内部都有一个[[class]]属性,这个属性中就指定了上述字符串中构造函数的名。
例如
let isArray = (value) => {
return Object.prototype.toString.call(value) === '[object Array]'
}
let isFunction = (value) => {
return Object.prototype.toString.call(value) === '[object Function]'
}
let isRegExp = (value) => {
return Object.prototype.toString.call(value) === '[object RegExp]'
}
不过要注意IE中以COM对象实现的任何函数,isFunction都会返回false,因为他不是原生的javascript对象。
JavaScript:Object.prototype.toString方法的原理
构造函数其实就是一个用new操作符调用的函数,当使用new调用的时候,构造函数内用到的this会指向新创建的对象实例。
举个例子
function Person (name, age, job) {
this.name = name
this.age = age
this.job = job
}
let p = new Person('qianlongo', 100, 'fe')
当使用new操作符调用时会创建一个新的Person对象,但是问题就是出现在没有使用new的时候,由于this是在运行时绑定的,所以直接调用Person会映射到全局的window对象
作用域安全的构造函数在进行任何更改之前,首先确认this对象是正确类型的实例,如果不是,那么会创建新的实例并返回。
举个例子
function Person (name) {
if (!(this instanceof Person)) {
return new Person(name)
}
this.name = name
}
let p1 = new Person('qianlongo')
let p2 = new Person('xiaohuihui')
以上代码实现了不管是否使用new操作符去调用Person函数都会生成一个新的Person实例
但是以上模式也有缺陷,比如在使用构造函数的窃取模式来实现继承并且不用原型链,那么这个继承可能会遭到破坏。
举个例子
function A (name) {
if (!(this instanceof A)) {
return new A(name)
}
this.name = name
this.showName = function () {
console.log(this.name)
}
}
function B (name, age) {
A.call(this, name)
this.age = age
}
let b1 = new B('qianlongo', 100)
b1.name // undefined
在这段代码中A函数的构造函数是安全的,然后B函数却不是,新创建一个B函数的实例后,应该通过A.call来继承A的name属性,但是由于A函数的作用域是安全的,this并不是A函数的实例,所以会创建一个新的A对象,B中的this并没有得到增长,同事A.call(name)返回的值也没有用到,所以b1中就不会有name属性。
但是构造函数窃取模式使用原型链或者寄生组合模式就可以解决这个问题。举个例子。
解释:这里是因为 a instanceof b 操作符只要a实例的原型链上能找到b都会返回true
function A (name) {
if (!(this instanceof A)) {
return new A(name)
}
this.name = name
this.showInfo = function () {
console.log(this.name)
}
}
function B (name, age) {
A.call(this, name)
this.age = age
this.showAge = function () {
console.log(this.age)
}
}
B.prototype = new A()
let b2 = new B('qianlongo', 1)
b2.name // qianlongo
浏览器之间有各种差异,所以大多数的js代码包含了大量的if语句,将执行引导到正确的代码中。看下面的代码
function createXHR () {
if (typeof XMLHttpRequest != 'undefined') {
return new XMLHttpRequest()
} else if (typeof ActiveXObject != 'undefined') {
// xxx
} else {
throw new Error('No XHR object available')
}
}
每次调用函数都需要对浏览器支持的功能检查一遍,但其实每次调用的结果都是不变的,所以没有必要每次都重新判断执行一遍,解决方案就是使用惰性载入的技巧。
惰性载入表示函数执行的分支置灰发生一次,有两种实现惰性载入的方式,第一种就是在函数被调用时再处理函数,第一次调用时,该函数会被覆盖为另外一个按合适的方式执行的函数,这样任何对原函数的调用都不会再经过执行的分支了。
方式一
function createXHR () {
if (typeof XMLHttpRequest != 'undefined') {
createXHR = function () {
return new XMLHttpRequest()
}
} else if (typeof ActiveXObject != 'undefined') {
createXHR = function () {
// xxx
}
} else {
createXHR = function () {
throw new Error('No XHR object available')
}
}
return createXHR()
}
第二种实现惰性载入的方式是在声明函数的时候就指定适当的函数,这样,第一次调用函数的时候就不会损失性能了,而在代码首次加载时会损失一点性能。
let createXHR = (() => {
if (typeof XMLHttpRequest != 'undefined') {
return () => {
return new XMLHttpRequest()
}
} else if (typeof ActiveXObject != 'undefined') {
return () => {
// xxx
}
} else {
return () => {
throw new Error('No XHR object available')
}
}
})()