JS高级技巧(简洁版)
高级函数
由于在JS中,所有的函数都是对象,所以使用函数指针十分简单,也是这些东西使JS函数有趣且强大
安全的类型检测
JS内置的类型检测机制并不是完全可靠的
typeof
操作符返回一个字符串,表示未经计算的操作数的类型,在大多数情况下很靠谱,但是当然还有例外
正则表达式
typeof /s/ === 'function'; // Chrome 1-12 , 不符合 ECMAScript 5.1 typeof /s/ === 'object'; // Firefox 5+ , 符合 ECMAScript 5.1 复制代码
NULL
typeof null === 'object'; // 从一开始出现JavaScript就是这样的 复制代码
在 JavaScript 最初的实现中,JavaScript 中的值是由一个表示类型的标签和实际数据值表示的。对象的类型标签是 0。由于 null 代表的是空指针(大多数平台下值为 0x00),因此,null的类型标签也成为了 0,typeof null就错误的返回了object
instanceof
运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性
语法
object instanceof constructor(要检测的对象 instanceof 构造函数)
但是在浏览器中,我们的脚本可能需要在多个窗口之间进行交互。多个窗口意味着多个全局环境,不同的全局环境拥有不同的全局对象,从而拥有不同的内置类型构造函数。这可能会引发一些问题。
[] instanceof window.frames[0].Array //false 复制代码
因为 Array.prototype !== window.frames[0].Array.prototype,因此你必须使用 Array.isArray(myObj) 或者 Object.prototype.toString.call(myObj) === "[object Array]"来判断myObj是否是数组
解决以上两个问题的方案就是Object.prototype.toString
Object.prototype.toString
方法返回一个表示该对象的字符串
可以通过toString() 来获取每个对象的类型。为了每个对象都能通过 Object.prototype.toString() 来检测,需要以 Function.prototype.call() 或者 Function.prototype.apply()的形式来调用,传递要检查的对象作为第一个参数,称为thisArg
var toString = Object.prototype.toString; toString.call(new Date); // [object Date] toString.call(new String); // [object String] toString.call(Math); // [object Math] toString.call(/s/); // [object RegExp] toString.call([]); // [object Array] //Since JavaScript 1.8.5 toString.call(undefined); // [object Undefined] toString.call(null); // [object Null] 复制代码
作用域安全的构造函数
构造函数其实就是一个使用new操作符调用的函数。当使用new调用时,构造函数内用到的this对象会指向新创建的对象实例
function Person(name, age){ this.name = name; this.age = age; } let person = new Person("addone", 20); person.name // addone 复制代码
当你使用new操作符的时候,就会创建一个新的Person对象,同时分配这些属性,但是如果你没有使用new
let person = Person("addone", 20); person1.name // Cannot read property 'name' of undefined window.name // addone 复制代码
这是因为this是在执行时确认的,当你没有使用new,那么this在当前情况下就被解析成了window,属性就被分配到window上了
作用域安全的构造函数在进行更改前,首先确认this对象是正确类型的实例,如果不是,就创建新的对象并且返回
function Person(name, age){ if(this instanceof Person){ this.name = name; this.age = age; }else{ return new Person(name, age); } } let person1 = new Person("addone", 20); person1.name // addone let person2 = Person("addone", 20); person2.name // addone 复制代码
this instanceof Person检查了this对象是不是Person的实例,如果是则继续,不是则调用new
惰性载入函数
假如你要写一个函数,里面有一些判断语句
function foo(){ if(a != b){ console.log('aaa') }else{ console.log('bbb') } } 复制代码
如果你的a和b是不变的,那么这个函数不论执行多少次,结果都是不变的,但是每次执行还要进行if判断,这就造成了不必要的浪费。
惰性载入表示函数执行的分支只会发生一次,这里有两种解决方式。
在函数被调用时再处理函数
function foo(){ if(a != b){ foo = function(){ console.log('aaa') } }else{ foo = function(){ console.log('bbb') } } return foo(); } 复制代码
这样进入每个分支后都会对foo进行赋值,覆盖了之前的函数,之后每次调用foo就不会再执行if判断
在声明函数时就指定适当的函数
var foo = (function foo(){ if(a != b){ return function(){ console.log('aaa') } }else{ return function(){ console.log('bbb') } } })(); 复制代码
这里创建一个匿名,自执行的函数,用来确定应该使用哪一个函数来实现。
惰性函数的优点就是只在第一次执行分支时牺牲一点点性能
函数绑定
请使用fun.bind(thisArg[, arg1[, arg2[, ...]]])
thisArg
当绑定函数被调用时,该参数会作为原函数运行时的 this 指向。当使用new 操作符调用绑定函数时,该参数无效
arg1,arg2,...
当绑定函数被调用时,这些参数将置于实参之前传递给被绑定的方法
返回
由指定的this值和初始化参数改造的原函数拷贝
一个例子
let person = { name: 'addone', click: function(e){ console.log(this.name) } } let btn = document.getElementById('btn'); EventUtil.addHandle(btn, 'click', person.click); 复制代码
这里创建了一个person对象,然后将person.click方法分配给DOM按钮的事件处理程序,当你点击按按钮时,会打印出undefiend,原因是执行时this指向了DOM按钮而不是person
解决方案: 将this强行指向person
EventUtil.addHandle(btn, 'click', person.click.bind(person)); 复制代码
函数柯里化
函数柯里化是把接受多个参数的函数转变成接受单一参数的函数
function add(num1, num2){ return num1 + num2; } function curryAdd(num2){ return add(1, num2); } add(2, 3) // 5 curryAdd(2) // 3 复制代码
这个例子用来方便理解柯里化的概念
下面是创建函数柯里化的通用方式
function curry(fn){ var args = Array.prototype.slice.call(arguments, 1); return function(){ let innerArgs = Array.prototype.slice.call(arguments); let finalArgs = args.concat(innerArgs); return fn.apply(null, finalArgs); } } 复制代码
第一个参数是要进行柯里化的函数,其他参数是要传入的值。这里使用Array.prototype.slice.call(arguments, 1)来获取第一个参数后的所有参数(外部)。在返回的函数中,同样调用Array.prototype.slice.call(arguments)让innerArgs来存放所有的参数(内部),然后用concat将内部外部参数组合,用apply传递给函数
function add(num1, num2){ return num1 + num2; } let curryAdd1 = curry(add, 1); curryAdd1(2); // 3 let curryAdd2 = curry(add, 1, 2); curryAdd2(); // 3 复制代码
防篡改对象
Javascript中任何对象都可以被同一环境中运行的代码修改,所以开发人员有时候需要定义防篡改对象(tamper-proof object) 来保护自己
不可扩展对象
默认情况下所有对象都是可以扩展的(添加属性和方法)
let person = { name: 'addone' }; person.age = 20; 复制代码
第二行为person对象扩展了age属性,当然你可以阻止这一行为,使用Object.preventExtensions()
let person = { name: 'addone' }; Object.preventExtensions(person); person.age = 20; person.age // undefined 复制代码
你还可以用Object.isExtensible()来判断对象是不是可扩展的
let person = { name: 'addone' }; Object.isExtensible(person); // true Object.preventExtensions(person); Object.isExtensible(person); // false 复制代码
请记住这是不可扩展!!,即不能添加属性或方法
密封的对象
密封对象不可扩展,且不能删除属性和方法
let person = { name: 'addone' }; Object.seal(person); person.age = 20; delete person.name; person.age // undefined person.name // addone 复制代码
相对的也有Object.isSealed()来判断是否密封
let person = { name: 'addone' }; Object.isExtensible(person); // true Object.isSealed(person); // false Object.seal(person); Object.isExtensible(person); // false Object.isSealed(person); // true 复制代码
冻结的对象
这是最严格的防篡改级别,冻结的对象即不可扩展,又密封,且不能修改
let person = { name: 'addone' }; Object.freeze(person); person.age = 20; delete person.name; person.name = 'addtwo' person.age // undefined person.name // addone 复制代码
同样也有Object.isFrozen来检测
let person = { name: 'addone' }; Object.isExtensible(person); // true Object.isSealed(person); // false Object.isFrozen(person); // false Object.freeze(person); Object.isExtensible(person); // false Object.isSealed(person); // true Object.isFrozen(person); // true 复制代码
以上三种方法在严格模式下进行错误操作均会导致抛出错误
高级定时器
阅读前提
大概理解setTimeout的基本执行机制和js事件机制
重复的定时器
当你使用setInterval重复定义多个定时器的时候,可能会出现某个定时器代码在代码再次被添加到执行队列之前还没有完成执行,导致定时器代码连续执行多次。
机智Javascript引擎解决了这个问题,使用setInterval()的时候,仅当没有该定时器的其他代码实例时,才会将定时器代码添加到队列中。但这还会导致一些问题:
- 某些间隔被跳过
- 间隔可能比预期的小
为了避免这个两个问题,你可以使用链式setTimeout()调用
setTimeout(function(){ TODO(); setTimeout(arguments.callee, interval); }, interval) 复制代码
arguments.callee获取了当前执行函数的引用,然后为其设置另外一个定时器,这样就确保在下一次定时器代码执行前,必须等待指定的间隔。
Yielding Processes
浏览器对长时间运行的脚本进行了制约,如果代码运行超过特定的时间或者特定语句数量就不会继续执行。
如果你发现某个循环占用了大量的时间,那么对于下面这两个问题
- 该处理是否必须同步完成?
- 数据是否必须按顺序完成?
如果你的两个答案都是"否",那么你可以使用一种叫做数组分块(array chunking) 的技术。基本思路是为要处理的项目创建一个队列,然后使用定时器取出下一个要出处理的项目进行处理,然后再设置另一个定时器。
function chunk(array, process, context){ setTimeout(function(){ // 取出下一个项目进行处理 let item = array.shift(); process.call(item); if(array.length > 0){ setTimeout(arguments.callee, 100); } }, 100) } 复制代码
这里接受三个参数,要处理的数组,处理的函数,运行该函数的环境(可选),这里设置间隔100ms是个效果不错的选择
如果你一个函数需要50ms以上时间完成,那么最好看看能否将任务分割成一系列可以使用定时器的小任务
函数节流(Throttle)
节流的目的是防止某些操作执行的太快。比如在调整浏览器大小的时候会出发onresize事件,如果在其内部进行一些DOM操作,这种高频率的更爱可能会使浏览器崩溃。为了避免这种情况,可以采取函数节流的方式。
function throttle(method, context){ clearTimeout(method.tId); method.tId = setTimeout(function(){ method.call(context); }, 100) } 复制代码
这里接受两个参数,要执行的函数,执行的环境。执行时先清除之前的定时器,然后将当前定时器赋值给方法的tId,之后调用call来确定函数的执行环境。
一个应用的例子
function resizeDiv(){ let div = document.getElementById('div'); div.style.height = div.offsetWidth + "px"; } window.onresize = function(){ throttle(resizeDiv); } 复制代码
这个就不用讲了吧2333
文章参考于《JavaScript高级程序设计(第三版)》
如果你觉得我的理解有问题或者整理的太简略,那么我强烈安利你自己去读一下这本书~
原文发布时间:2018-05-14
原文作者:AddOneG
本文来源掘金如需转载请紧急联系作者
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
C# HttpWebResponse下载限速
在使用HttpWebResponse请求网络内容的时候,一般如下面这种方法: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // 非生产环境代码,请在实际使用时进行必要调整 // —— Mitchell Chu // Blog:blog.useasp.net HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); using (var response = (HttpWebResponse)request.GetResponse()) using (var stream = response.GetResponseStream()) { byte [] buffer = new byte [4096]; int read = 0; while ((read = stream.Read(buffer, 0, buffer.Length)) >0){ // buffer... } } 使用这种方式,一旦我们开始下载数据,即是全速下载——即理论上是客户端和服务端的最小速度为...
- 下一篇
译文 | Angular中的AoT编译
译者:陈旭@中兴RDK 前两天,Jigsaw七巧板上来了个issue https://github.com/rdkmaster/jigsaw/issues/113,@jackjoy 在issue中提到了一篇介绍Angular AoT的文章,我看了一下,觉得讲的非常好,还涉及到一些Angular编译原理的内容。于是打算翻译一下,让大伙都能够读一读,多了解一点AoT知识。 文中的第一人称“我”均指代作者本人(http://blog.mgechev.com) 原文地址是 http://blog.mgechev.com/2016/08/14/ahead-of-time-compilation-angular-offline-precompilation/ 最近我给angular-seed增加了对Ahead-of-Time(AoT)编译的支持,这引来了不少关于这个新特性的问题。我们从下面这些话题开始来回答这些问题: 为什么Angular需要编译? 什么东西会被编译? 他们是如何被编译的? 编译发生在什么时候?JiT vs AoT 我们从AoT中获得了什么? AoT编译是如何工作的? 我们使用Ao...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2整合Redis,开启缓存,提高访问速度
- CentOS7设置SWAP分区,小内存服务器的救世主
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- SpringBoot2全家桶,快速入门学习开发网站教程
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- CentOS7安装Docker,走上虚拟化容器引擎之路
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- CentOS8编译安装MySQL8.0.19