js执⾏iframe中的函数_JS⾼级技巧
本篇是看的《JS⾼级程序设计》第23章《⾼级技巧》做的读书分享。本篇按照书⾥的思路根据⾃⼰的理解和经验,进⾏扩展延伸,同时指出书⾥的⼀些问题。将会讨论安全的类型检测、惰性载⼊函数、冻结对象、定时器等话题。
1. 安全的类型检测
这个问题是怎么安全地检测⼀个变量的类型,例如判断⼀个变量是否为⼀个数组。通常的做法是使⽤instanceof,如下代码所⽰:
let
但是上⾯的判断在⼀定条件下会失败——就是在iframe⾥⾯判断⼀个⽗窗⼝的变量的时候。写个demo验证⼀下,如下主页⾯的
main.html:
<
在iframe.html判断⼀下⽗窗⼝的变量类型:
<
在iframe⾥⾯使⽤window.parent得到⽗窗⼝的全局window对象,这个不管跨不跨域都没有问题,进⽽可以得到⽗窗⼝的变量,然后⽤instanceof判断。最后运⾏结果如下:
可以看到⽗窗⼝的判断是正确的,⽽⼦窗⼝的判断是false,因此⼀个变量明明是Array,但却不是Array,这是为什么呢?既然这个是⽗⼦窗⼝才会有的问题,于是试⼀下把Array改成⽗窗⼝的Array,即window.parent.Array,如下图所⽰:
这次返回了true,然后再变换⼀下其它的判断,如上图,最后可以知道根本原因是上图最后⼀个判断:
Array !== window.parent.Array
它们分别是两个函数,⽗窗⼝定义了⼀个,⼦窗⼝⼜定义了⼀个,内存地址不⼀样,内存地址不⼀样的Object等式判断不成⽴,⽽window.structor返回的是⽗窗⼝的Array,⽐较的时候是在⼦窗⼝,使⽤的是⼦窗⼝的Array,这两个Array不相等,所以导致判断不成⽴。
那怎么办呢?
由于不能使⽤Object的内存地址判断,可以使⽤字符串的⽅式,因为字符串是基本类型,字符串⽐较只要每个字符都相等就好了。ES5提供了这么⼀个⽅法String,我们先⼩试⽜⼑,试⼀下不同变量的返回值:
可以看到如果是数组返回"[object Array]",ES5对这个函数是这么规定的:
也就是说这个函数的返回值是“[object ”开头,后⾯带上变量类型的名称和右括号。因此既然它是⼀个标准语法规范,所以可以⽤这个函数安全地判断变量是不是数组。
可以这么写:
Object
注意要使⽤call,⽽不是直接调⽤,call的第⼀个参数是context执⾏上下⽂,把数组传给它作为执⾏上下⽂。
有⼀个⽐较有趣的现象是ES6的class也是返回function:
所以可以知道class也是⽤function实现的原型,也就是说class和function本质上是⼀样的,只是写法上不⼀样。
本页⾯的变量还是可以那是不是说不能再使⽤instanceof判断变量类型了?不是的,当你需要检测⽗页⾯的变量类型就得使⽤这种⽅法,本页⾯的变量还是可以使⽤instanceof或者constructor的⽅法判断,只要你能确保这个变量不会跨页⾯。因为对于⼤多数⼈来说,很少会写iframe的代码,使⽤instanceof或者constructor的⽅法判断
所以没有必要搞⼀个⽐较⿇烦的⽅式,还是⽤简单的⽅式就好了。
2. 惰性载⼊函数
有时候需要在代码⾥⾯做⼀些兼容性判断,或者是做⼀些UA的判断,如下代码所⽰:
//UA的类型
这个函数的作⽤是判断⽤户是在哪个环境打开的⽹页,以便于统计哪个渠道的效果⽐较好。
这种类型的判断都有⼀个特点,就是它的结果是死的,不管执⾏判断多少次,都会返回相同的结果,例如⽤户的UA在这个⽹页不可能会发⽣变化(除了调试设定的之外)。所以为了优化,才有了惰性函数⼀说,上⾯的代码可以改成:
//UA的类型
在每次判断之后,把getUAType这个函数重新赋值,变成⼀个新的function,⽽这个function直接返回⼀个确定的变量,这样以后的每次获取都不⽤再判断了,这就是惰性函数的作⽤。你可能会说这么⼏个判断能优化多少时间呢,这么点时间对于⽤户来说⼏乎是没有区别的呀。确实如此,但是作为⼀个有追求的码农,还是会想办法尽可能优化⾃⼰的代码,⽽不是只是为了完成需求完成功能。并且当你的这些优化累积到⼀个量的时候就会发⽣质变。我上⼤学的时候C++的⽼师举了⼀个例⼦,说有个系统⽐较慢她去看⼀下,其中她做的⼀个优化是把⼩数的双精度改成单精度,最后是快了不少。
但其实上⾯的例⼦我们有⼀个更简单的实现,那就是直接搞个变量存起来就好了:
let
连函数都不⽤写了,缺点是即使没有使⽤到UAType这个变量,也会执⾏⼀次判断,但是我们认为这个变量被⽤到的概率还是很⾼的。
我们再举⼀个⽐较有⽤的例⼦,由于Safari的⽆痕浏览会禁掉本地存储,因此需要搞⼀个兼容性判断:
Data
在设置本地数据的时候,需要判断⼀下是不是⽀持本地存储,如果是的话就⽤localStorage,否则改⽤cookie。可以⽤惰性函数改造⼀下:
setLocalData
这⾥可以减少⼀次if/else的判断,但好像不是特别实惠,毕竟为了减少⼀次判断,引⼊了⼀个惰性函数的概念,所以你可能要权衡⼀下这种引⼊是否值得,如果有三五个判断应该还是⽐较好的。
3. 函数绑定
有时候要把⼀个函数当作参数传递给另⼀个函数执⾏,此时函数的执⾏上下⽂往往会发⽣变化,如下代码:
class
click事件的执⾏回调⾥⾯this不是指向了DrawTool的实例了,所以⾥⾯的this.points将会返回undefined。第⼀种解决⽅法是使⽤闭包,先把this缓存⼀下,变成that:
class
由于回调函数是⽤that执⾏的,⽽that是指向DrawTool的实例⼦,因此就没有问题了。相反如果没有that它就⽤的this,所以就要看this指向哪⾥了。
因为我们⽤了箭头函数,⽽箭头函数的this还是指向⽗级的上下⽂,因此这⾥不⽤⾃⼰创建⼀个闭包,直接⽤this就可以:
init
这种⽅式更加简单,第⼆种⽅法是使⽤ES5的bind函数绑定,如下代码:
init
这个bind看起来好像很神奇,但其实只要⼀⾏代码就可以实现⼀个bind函数:
Function
就是返回⼀个函数,这个函数的this是指向的原始函数,然后让它call(context)绑定⼀下执⾏上下⽂就可以了。
4. 柯⾥化
柯⾥化就是函数和参数值结合产⽣⼀个新的函数,如下代码,假设有⼀个curry的函数:
function
怎么实现这样⼀个curry的函数?它的重点是要返回⼀个函数,这个函数有⼀些闭包的变量记录了创建时的默认参数,然后执⾏这个返回函数的时候,把新传进来的参数和默认参数拼⼀下变成完整参数列表去调原本的函数,所以有了以下代码:
Function
sort函数 js但是由于参数不是⼀个数组,没有concat函数,所以需要把伪数组转成⼀个伪数组,可以⽤Array.prototype.slice:
Function
现在举⼀下柯⾥化⼀个有⽤的例⼦,当需要把⼀个数组降序排序的时候,需要这样写:
let
给sort传⼀个函数的参数,但是如果你的降序操作⽐较多,每次都写⼀个函数参数还是有点烦的,因此可以⽤柯⾥化把这个参数固化起来:Array
这样就⽅便多了:
let
5. 防⽌篡改对象
有时候你可能怕你的对象被误改了,所以需要把它保护起来。
(1)Object.seal防⽌新增和删除属性
如下代码,当把⼀个对象seal之后,将不能添加和删除属性:
当使⽤严格模式将会抛异常:
(2)Object.freeze冻结对象
这个是不能改属性值,如下图所⽰:
同时可以使⽤Object.isFrozen、Object.isSealed、Object.isExtensible判断当前对象的状态。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论