JavaScript 高级程序设计精要(22)

准备把最近阅读过的JavaScript 高级程序设计分享出来,可以为大家节省时间,也用来做自己的备忘。如果有不懂或者错误的地方,欢迎留言或者私信。

1 高级技巧

  • 使用高级函数
  • 防篡改对象
  • Yielding Timers
1.1 安全的类型检测

在web开发中能够区分原生与非原生JavaScript对象非常难哟,只有这样才能确切知道某个对象到底有哪些功能。

typeof
由于它一些无法与指导额行为,经常会导致检测数据得到不靠谱的结果。


instanceof
对于一个网页,或者一个全局作用域而言,使用instanceof操作符就能得到满意的结果 。它的问题在于假定单一的全局执行环境,如果网页中包含多个框架,那实际上就存在两个以上不同的全局执行环境,从而存在两个以上不同版本的Array构造函数。

if(value instanceof Array) {
 //对数组执行某些操作
}

instanceof操作符存在多个全局作用域的情况下(例如包含多个iframe)


Array.isArray()
这个方法的目的是最终确定某个值到底是不是数组,而不管它是在哪个全局执行环境中创建的。

if(Array.isArray(value)){
  //对数组执行某些操作
}

检测某个对象到底是原生对象还是开发人员自定义的对象。

浏览器开始原生支持JSON对象,因为很多人一直子啊使用Douglas Crockford的JSON库,而该库定义了一个全局JSON对象。于是开发人员很难确定页面中的JSON对象到底是不是原生的。

alert(Object.prototype.soString.call(value)) //[object Array]

由于原生数组的构造函数名与全局作用域无关,因此使用toString()就能保证返回一致的值。

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]'; //检测正则表达式
}

不过要注意,对于在IE中以COM对象形式实现的任何函数,isFunction () 都将返回false(因为它们并非原生的JavaScript函数)


1.2 作用域安全的构造函数

多人在同一个页面上写JavaScript代码环境中,作用域安全构造函数就很有用。

function Person(name, age, job) {
  this.name = name;
  this.age = age;
  this.job = job;
}

var person = new Person('Nicholas', 29,'Software Engineer');
console.log (person.name) //Nicholas
var otherPerson = Person('Nicholas', 29,'Software Engineer');
console.log (window.name) //Nicholas
console.log (otherPerson.name) //报错 

当和new操作符连用时,则会创建一个新的Person对象,同事会给它分配这些属性,当没有new操作符来调用该构造函数的情况时,由于该this对象是在运行时绑定的,所以直接调用Person(),this会映射到全局对象window上,导致错误对象的意外增加。

作用域安全的构造函数在进行任何更改前,首先确认this对象是正确类型的实例。如果不是,那么会创建新的实例并返回。

function Person (name, age, job) {
  if(this instanceof Person){
    this.name = name;
    this.age = age;
    this.job = job;
  } else {
    return new Person (name, age, job);
  }
}
var person1 = Person('Nicholas', 29, 'Software Engineer');
console.log(window.name); //''
console.log(person1.name) //Nicholas

var person2 = new Person('Shelby', 34, 'Ergonomist');
console.log(person2.name) //Shelby

调用Person构造函数时无论是否使用new操作符,都会返回一个Person 的新实例,这就避免了在全局独享上意外设置属性。

关于作用域安全的构造函数的贴心提示。实现这个模式后,你就锁定了可以使用构造函数的环境。

    function Polygon (sides) {
        if(this instanceof Polygon){
            this.sides = sides;
            this.getArea = function () {
                return 0;
            }
        } else {
            return new Polygon(sides);
        }
    }
    function Rectangle(width,height){
        Polygon.call(this,2); //this 调用 Polygon的方法
        this.width = width;
        this.height = height;
        this.getArea = function () {
            return this.width * this.height
        }
    }
    var rect = new Rectangle (5, 10)
    console.log(rect.sides) //undefined

这段代码里,Polygon构造函数是作用域安全的,然而Rectangle构造函数不是,新创建一个Rectangle实例之后,这个实例应该通过Polygon.call() 来继承Polygon的sides属性。

由于Polygon返回的是一个Polygon的实例,Rectangle构造函数中的this对象并没有得到增长,同时Polygon.call() 返回的值也没有用到,所以Rectangle实例中就不会有sides属性。

    function Polygon (sides) {
        if(this instanceof Polygon){
            this.sides = sides;
            this.getArea = function () {
                return 0;
            }
        } else {
            return new Polygon(sides);
        }
    }

    function Rectangle(width,height){
        Polygon.call(this,2); //this 调用 Polygon的方法
        this.width = width;
        this.height = height;
        this.getArea = function () {
            return this.width * this.height
        }
    }
    Rectangle.prototype = new Polygon();
    var rect = new Rectangle (5, 10)
    console.log(rect.sides) //2

Rectangle实例同时也是一个Polygon实例,所以 Polygon.call会照原意执行,最终为Rectangle实例添加sides属性


1.3 惰性载入函数

因为浏览器之间行为的差异,多数JavaScript 代码包含了大量的 if 语句,将执行引导到正确的代码中

    let num = 0
    function createXHR(){
        console.log('调用==>', num ++) // 打印2次
        if (typeof XMLHttpRequest != "undefined"){
            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"],
                    i,len;
                for (i=0,len=versions.length; i < len; i++){
                    try {
                        new ActiveXObject(versions[i]);
                        arguments.callee.activeXString = versions[i];
                        break;
                    } catch (ex){

                    }
                }
            }
            return new ActiveXObject(arguments.callee.activeXString);
        } else {
            throw new Error("No XHR object available.");
        }
    }
    createXHR()
    setTimeout(
        () => {
                createXHR()
            }, 2000
    )

每次调用createXHR()的时候,它都要对浏览器所支持的能力仔细检查,首先检查内置的XHR,然后测试有没有基于ActiveX的XHR,最后如果都没有发现的话,就抛出一个错误。

如果if语句不必每次执行,代码可以运行地更快一些,解决方案就是称之为惰性载入的技巧。

有2种实现惰性载入的方式
1、第一种就是在函数被调用时再处理函数。

    let num = 0
    function createXHR(){
        console.log('调用 ===>',num++); //打印一次
        if (typeof XMLHttpRequest != "undefined"){     

            createXHR = function (){
                return new XMLHttpRequest()
            }   
        } else if (typeof ActiveXObject != "undefined"){
            createXHR = function (){
                if (typeof arguments.callee.activeXString != "string"){
                    var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0",
                            "MSXML2.XMLHttp"],
                        i,len;
                    for (i=0,len=versions.length; i < len; i++){
                        try {
                            new ActiveXObject(versions[i]);
                            arguments.callee.activeXString = versions[i];
                            break;
                        } catch (ex){

                        }
                    }
                }
                return new ActiveXObject(arguments.callee.activeXString)
            }   
        } else {
            createXHR = function () {
                throw new Error("No XHR object available.");
            }     
        }
        console.log(createXHR);//function (){return new XMLHttpRequest()}
        return createXHR()
    }
    createXHR()
    setTimeout(
        () => {
                createXHR()
            }, 2000
    )

2、第二种就是在声明函数时就指定适当的函数。

    let num = 0;
    var createXHR = (function () {
            console.log(num++);
            if (typeof XMLHttpRequest !== 'undefined'){
                return function () {
                    return new XMLHttpRequest();
                };
            }else if (typeof ActiveXObject != 'undefined') {
                return function () {
                    if(typeof arguments.callee.activeXString != 'string') {
                        var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0", "MSXML2.XMLHttp"], i ,len ;
                        for ( i = 0, len = versions.length;i< len; i ++){
                            try {
                                new ActiveXObject(versions[i]);
                                arguments.callee.activeXstring = versions[i]
                                break;
                            }
                            catch (ex) {
                                //skip
                            }
                        }       
                    }
                    return new ActiveXObject(arguments.callee.activeXString);
                }
            } else {
                return function () {
                    throw new Error(
                        'No XHRobject available'
                    )
                }
            }
        })()
    createXHR()
    setTimeout(
        () => {
                createXHR()
            }, 2000
    )

第二个例子,技巧是创建一个匿名、自执行的函数,用于确定应该使用哪一个函数实现。


1.4函数绑定

函数绑定要创建一个函数,可以在特定的this 环境中以指定参数调用另一个函数。

bind

一个简单的bind()函数接受一个函数和一个环境,并返回一个在给定环境中调用给定函数的函数,并且将所有参数原封不动传递过去。
只要是将某个函数指针以值的形式进行传递,同时该函数必须在特定环境中执行,被绑定函数的效用就凸显出来了。

然而,被绑定函数与普通函数相比有更多开销,他们需要更多内存。同时也因为多重函数调用稍微慢一点,所以最好只在必要时使用。


1.4函数柯里化

所谓"柯里化",就是把一个多参数的函数,转化为单参数函数

用于创建已经设置好了一个或多个参数的函数。函数柯里化的基本方法和函数绑定是一样的:使用一个闭包返回一个函数。
区别在于:当函数被调用时,返回的函数还需要设置一些传入的参数。

柯里化函数通常由一下步骤动态创建:
调用另一个函数并为它传入要柯里化的函数和必要参数。

    function add(num1, num2){
        return num1 + num2
    }
    function curriedAdd(num2){
        return add (5, num2)
    }
    alert(add(2,3))
    alert(curriedAdd(3)) //与柯里化有异曲同工之妙
    function curry (fn){
        var args = Array.prototype.slice.call(arguments,1);
        return function (){
            var innerArgs = Array.prototype.slice.call(arguments);
            var finalArgs = args.concat(innerArgs);
            return fn.apply(null, finalArgs)
        }
    }

bind()函数提供了强大的动态函数创建功能。


2 防篡改对象

可以手工设置每个属性的[ [ Configurable ] ]、[ [ Writable ] ]、[ [ Enumerable ] ]、[ [ Value ] ]、[ [ Get ] ]、以及[ [ Set ] ]

不过注意,一旦把对象定义为防篡改,就无法撤销了。

2.1 不可拓展对象

默认情况下,所有对象都是可以扩展的。
Object.preventExtensions(obj)

    var person = { name : 'Nicholas' }
    Object.preventExtensions(person)

    person.age = 29;
    person.name = 'cher'
    console.log( person.age ) // undefined 调用了Object.preventExtensions()方法后,就不能给person对象添加新属性和方法了。在非严格模式下,给对象添加新成员会导致静默失败,即为undefined,严格模式下,会报错。
    console.log( person.name ) // cher 虽然不能给对象添加新成员,但是已有的成员不受影响,仍然可以修改和删除已有的成员。

Object.isExtensible(obj)
可确定对象是否可以扩展


2.2 密封的对象 sealed object

密封的对象不可扩展,而且已有成员[ [ Configurable ] ] 特性将被设置为false,这就意味着不能删除属性和方法。
Object.seal()

    var person = { name : 'Nicholas' }
    Object.seal(person);
    person.age = 29;
    alert(person.age); //undefined
    delete person.name;
    alert(person.name); //Nicholas

Object.isSealed()
可以确定独享是否被密封


2.3 冻结的对象frozen object

冻结的对象既不可扩展,又是密封的,而且对象数据属性的[[ Writable ]]特性会被设置为false。
如果定义[[ Set ]]函数,访问器属性仍然是可写的。
Object.freeze()
Object.isFrozen()

对于JavaScript库的作者而言,冻结对象是很有用的(封装),因为怕使用者更改库核心代码。


3 高级定时器 setTimeout setInterval

JavaScript是运行与单线程的环境中的。定时器仅仅只是计划代码在未来的某个时间执行。执行时机是不能保证的。
在JavaScript中没有任何代码是立刻执行的,但一旦进程空闲则尽快执行。
setInterval()创建的定时器确保了定时器代码规则的插入队列中。定时器代码可能在代码再次被添加到队列之前还没有完成执行。当使用setInterval()时,仅当没有该定时器的任何其他代码实例时,才将定时器加入到队列中的最小时间间隔为指定间隔。

3.1函数防抖
某些代码不可以在没有间断的情况连续重复执行。
在window.resize时建议使用。

    function resiveDiv() {
        console.log('resiveDiv调用了')
        var div = document.getElementById('myDiv');
        div.style.height = div.offsetWidth + 'px'
    }

    function debounce(method, context) {     
        clearTimeout(method.tId);     
        method.tId= setTimeout(function(){         
            method.call(context);     
            }, 100);
    }
    window.onresize = function () {
        // debounce(resiveDiv)
        resiveDiv() //即时调用了
    }
    function resiveDiv() {
        console.log('resiveDiv调用了')
        var div = document.getElementById('myDiv');
        div.style.height = div.offsetWidth + 'px'
    }

    function debounce(method, context) {     
        clearTimeout(method.tId);     
        method.tId= setTimeout(function(){         
            method.call(context);     
            }, 100);
    }
    window.onresize = function () {
        debounce(resiveDiv) //进行了函数防抖
    }

3.2函数节流

// 函数节流
var canRun = true;
document.getElementById("throttle").onscroll = function(){
if(!canRun){
  return
}
canRun = false
setTimeout( function () {
    console.log("函数节流")
    canRun = true
  }, 500)
}

4 自定义事件

事件是JavaScript与浏览器交互的主要途径。
事件是一种叫做观察者的设计模式,这是一种创建松散耦合代码的技术。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容