博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
编写一个JS组件来说说call和apply的用法
阅读量:6315 次
发布时间:2019-06-22

本文共 10575 字,大约阅读时间需要 35 分钟。

hot3.png

原文地址:

    

在一个群上看到好几次问到call和apply的作用,function这两个方法的效果大家都很容易理解,但一般很难让人深刻地理解使用它们的时机。

call和apply都有一个功能:改变函数的上下文,也就是在调用函数的同时,改变函数内部this的指向的对象。apply还可以向函数传递参数。如果一个函数的调用必须给定相应的参数,则只能够用apply方法。
下面通过编写一个JS组件来说明这两个方法在什么时机下使用,主要用在事件处理上。
在制作表单时,常常需要让用户输入一定范围内的数据,超出这个范围的数据视为非法。如人的年龄,世界上没有一个人的年龄为-1岁。如果采用下列列表让用户输入,列表可能太长而影响用户使用体验。我们可以使用一个文本框,让用户输入数据,然后验证。由于这种情况很常见,那么为用JS来编写一个组件,把一个文本框封装起来,实现验证逻辑,提高代码的可重用性。
完整的代码如下:

//为Array类添加两个新方法Array.prototype.Add = function(item){	for(var i=0;i
max) throw new Error('min不能大于max'); this._dom = null; //组件的DOM对象 this._invalidTypeHandler = []; //数据类型不正确时调用的函数数组 this._overflowHandler = []; //数据超出范围时调用的函数数组 this._min = min; //最小值 this._max = max; //最大值 this._init(); //初始化}NumTextBox.prototype = { getValue : function(){ //获取当前值 return this._dom.value*1; }, setValue : function(value){ //设置当前值 if (isNaN(value)) throw new Error('参数value必须为数字'); value = value*1; if (value
this._max) throw new Error('数据不合法');//数据不合法 this._dom.value = value; }, getMin : function(){ //获取最小值 return this._min; }, setMin : function(value){//设置最小值 if (isNaN(value) || value*1 > this._max) throw new Error('参数value不是数字或大于max'); this._min = value*1; }, getMax : function(){//获取最大值 return this._max; }, setMax : function(value){//设置最大值 if (isNaN(value) || value*1 < this._min) throw new Error('参数value不是数字或大于max'); this._max = value; }, AddInvalidTypeEventHandler : function(handler){//添加非法数据类型处理函数 if (typeof(handler)!='function') throw new Error('参数handler必须为函数'); this._invalidTypeHandler.Add(handler); }, RemoveInvalidTypeEventHandler : function(handler){//移除非法数据类型处理函数 if (typeof(handler)!='function') throw new Error('参数handler必须为函数'); this._invalidTypeHandler.Remove(handler); }, AddOverflowEventHandler : function(handler){//添加溢出处理函数 if (typeof(handler)!='function') throw new Error('参数handler必须为函数'); this._overflowHandler.Add(handler); }, RemoveOverflowEventHandler : function(handler){//移除溢出处理函数 if (typeof(handler)!='function') throw new Error('参数handler必须为函数'); this._overflowHandler.Remove(handler); }, _raiseInvalidTypeEvent : function(ext){//触发非法数据类型事件 var cancel = false; for(var i=0;i
=oNumTextBox._min && value <=oNumTextBox._max)) oNumTextBox._raiseOverflowEvent.apply(oNumTextBox,[ext]); }, _init : function(controlId,min,max){ //创建DOM this._dom = document.createElement('input'); this._dom.type = 'text'; this._dom.id = controlId; this._dom.name = controlId; this._dom.oNumTextBox = this; this._dom.onblur = this._checkValue;//事件绑定 document.body.appendChild(this._dom);//放入网页中 }}function invalid1(ext){ alert("valid1\n,当前值为"+this.getValue());}function invalid2(ext){ alert("valid2,测试多处理函数和保留焦点"); return true;}function overflow(ext){ alert("输入的值必须在"+this.getMin()+"和"+this.getMax()+"之间"); return true;}var test = new NumTextBox("test",18,60);test.AddInvalidTypeEventHandler(invalid1);//添加非法数据类型事件处理函数test.AddInvalidTypeEventHandler(invalid2);test.AddOverflowEventHandler(overflow);//添加溢出事件处理函数

凡是私有的方法和成员我都以下划线(_)作为变量名的开头,使用类时,不应使用这些接口,否则会出现不正确的结果。
现在让我们一点点分析代码:首先是两个数组扩展函数

Array.prototype.Add = function(item){        for(var i=0;i

我们为Array类添加两个方法,这样做是为了方便后面操作数组.Add方法检查数组是含有item,如果有,什么都不操作,若没有,则添加item到数组中。Remove方法检查数组是否含有item,若有则从数组删除item,没有则什么都不做。这两个方法实际上是实现集合的添加元素和删除元素的操作,要保证集合中元素的唯一性。
对JS内置类的扩展在许多JS库中都有,如ASP.NET Ajax,对JS内置类进行丰富的扩展,开发起来极其方便和高效率。
接着看看我们的主角:NumTextBox类,它的构造器如下:

function NumTextBox(controlId,min,max){        if (!controlId || typeof(controlId)!='string')                throw new Error('参数controlId为空或不是字符串类型');        if (isNaN(min))                throw new Error('参数min必须为数字');        if (isNaN(max))                throw new Error('参数max必须为数字');        min = min*1; //如果 min = '123',转化为数字类型        max = max*1;        if (min>max)                throw new Error('min不能大于max');        this._dom = null; //组件的DOM对象        this._invalidTypeHandler = []; //数据类型不正确时调用的函数数组        this._overflowHandler = []; //数据超出范围时调用的函数数组        this._min = min; //最小值        this._max = max; //最大值        this._init(); //初始化}

controlId为组件的标识ID,待会会看到它的作用。min和max分别赋于最大值和最小值。构造器会对参数的数据合法性进行判断,如果不合法会抛出错误。

接着看看这个类的原型(prototype)的内容。

  1.         getValue : function(){ //获取当前值                return this._dom.value*1;        },        setValue : function(value){ //设置当前值                if (isNaN(value))                        throw new Error('参数value必须为数字');                value = value*1;                if (value
    this._max)                        throw new Error('数据不合法');//数据不合法                this._dom.value = value;        },        getMin : function(){ //获取最小值                return this._min;        },        setMin : function(value){//设置最小值                if (isNaN(value) || value*1 > this._max)                        throw new Error('参数value不是数字或大于max');                this._min = value*1;        },        getMax : function(){//获取最大值                return this._max;        },        setMax : function(value){//设置最大值                if (isNaN(value) || value*1 < this._min)                        throw new Error('参数value不是数字或大于max');                this._max = value;        },

复制代码

对于getValue, setValue, getMin, setMin, getMax, setMax这样的方法实际上是提供属性。因为JS不支持属性,所以我采用这样的命名方式。在ASP.NET Ajax中,也采用类似的方式。我们也可以直接调用类的 _min,_max等字段进行赋值,但这样做就不能保证数据的合法性,通过方法来赋值可以先检验数据,这也就是属性的本质作用。
接着看看

        AddInvalidTypeEventHandler : function(handler){//添加非法数据类型处理函数                if (typeof(handler)!='function')                        throw new Error('参数handler必须为函数');                this._invalidTypeHandler.Add(handler);        },        RemoveInvalidTypeEventHandler : function(handler){//移除非法数据类型处理函数                if (typeof(handler)!='function')                        throw new Error('参数handler必须为函数');                this._invalidTypeHandler.Remove(handler);        },        AddOverflowEventHandler : function(handler){//添加溢出处理函数                if (typeof(handler)!='function')                        throw new Error('参数handler必须为函数');                this._overflowHandler.Add(handler);        },        RemoveOverflowEventHandler : function(handler){//移除溢出处理函数                if (typeof(handler)!='function')                        throw new Error('参数handler必须为函数');                this._overflowHandler.Remove(handler);        },
  • 复制代码

这4个函数其实是对外提供两个事件:InvalidType和Overflow。现在对Array类进行扩展的两个方法:Add和Remove在这里用上了。由于IE不完美支持DOM事件模型,我们只好自己实现。
我们可以把多个函数绑定到一个事件中,而不用将代码全部挤在一个函数体内。通过Add***,我们可以往事件添加处理函数,当事件发生时,会逐个调用这些函数。通过Remove***,我们可以移除这些处理函数。在这里,造器中的两个数组——_invalidTypeHandler 和 _overflowHandler——是用来存放函数指针(function是一个对象,它是一个引用类型)的。
然后我们看看_init的内部方法:

        _init : function(controlId,min,max){ //创建DOM                this._dom = document.createElement('input');                this._dom.type = 'text';                this._dom.id = controlId;                this._dom.name = controlId;                this._dom.oNumTextBox = this;                this._dom.onblur = this._checkValue;//事件绑定                document.body.appendChild(this._dom);//放入网页中        }

我想除了倒数第二、三句:this._dom.oNumTextBox = this;this._dom.onblur = this._checkValue,大家都明白其他语句的作用。这个方法是创建一个文本框,设定id和name属性,构造器的controlId参数赋给id属性,其他JS代码可通过document.getElementById来获得这个文本框。
this._dom.oNumTextBox = this; 这条代码是将NumTextBox类的一个实例对象附加到新创建的文本框的oNumTextBox属性中,之所以添加这个属性,待会说明。this._dom.onblur是绑定文本框失去焦点的事件,处理函数为NumTextBox的_checkValue方法。也就是说,我们封装验证代码的入口就在这里。通过把_checkValue绑定到文本框的事件上,我们可以搞许多花样。看看_checkValue怎么写的。

         _checkValue : function(ext){//检查数据                ext = ext ? ext : window.event;                var oNumTextBox = this.oNumTextBox;                if (this.value == "")            oNumTextBox._raiseInvalidTypeEvent.apply(oNumTextBox,[ext]);                        return;                }                var value = this.value*1;                if (!(value>=oNumTextBox._min && value <=oNumTextBox._max))                        oNumTextBox._raiseOverflowEvent.apply(oNumTextBox,[ext]);        },                   return;                if (isNaN(this.value)){                        oNumTextBox._raiseInvalidTypeEvent.apply(oNumTextBox,[ext]);                        return;                }                var value = this.value*1;                if (!(value>=oNumTextBox._min && value <=oNumTextBox._max))                        oNumTextBox._raiseOverflowEvent.apply(oNumTextBox,[ext]);        },

第一句大家都很明白,旨在消灭IE和Gecko核心在事件模型的差异。大家要特别注意这个函数中this的指向。在类模型的其他方法中,this指向类实例,但这里的this却是指向类创建的文本框对象(id为controlId的文本框)?为什么呢?因为这个_checkValue函数在绑定文本框的onblur事件,当文本框失去焦点时,浏览器会调用这个_checkValue,并把它的函数上下文(this)改为触发事件的html对象(也就是文本框)。所以this指向文本框。我们把许多的逻辑放到那个类对象上,那么如何找到那个对象呢?在_init中我们把类对象附加在文本框的oNumTextBox属性中,那么我们就可以通过this.oNumTextBox来获取。如_checkVallue的第二行代码所示。接着开始验证数据。

如果没有数据,则忽略(一个return直接退出).如果数据不是合法的数值,如"asdf",则调用触发InvalidType事件。所谓的触发,触发的动作就是调用_raiseInvalidTypeEvent的方法。朋友们,先暂时一下,在讲apply之前,说说事件的顺序。调用触发invalidType事件的函数后,我们直接return。这说明了两件事,invalidType事件和Overflow事件只能同时发生其中一个,而且InvaidType先发生。看看上面的代码是如何实现的。
现在我们说说这里为什么要用apply。
其实这里大可以不用apply,因为我们可以通过修改_raiseInvalidTypeEvent和_raiseOverflowEvent方法的声明,使它有两个参数,分别传递类对象和ext对象。有这两个对象,它们完全可以完成任务(什么任务,等一下说)。但我为什么要在这里apply改变上下文呢。其实是为了缩小this指向非类对象的范围。通过apply,我们立刻把函数的上下文改为类对象,使指向非类对象的this仅仅存在于_checkValue。这样,对于自己以后的修改,检查错误,提供了方便。那么为什么要用apply传递ext呢?使用call抛弃它不行吗?——这个当然行啦,我之所以用apply是为了保留额外的事件信息,使用类的人可能用到也说不定。
现在看看_raiseInvalidTypeEvent和_raiseOverfowEvent

        _raiseInvalidTypeEvent : function(ext){//触发非法数据类型事件                var cancel = false;                for(var i=0;i

其实这两个类功能一样。它们都是遍历整个函数指针数组,然后,逐个调用。这里才是apply真正的用法。

我们为什么要把类对象作为函数上下文呢?因为使用这个类的人,在外部可以通过this来访问类的方法来获取组件的接口(属性)。他们往往不在乎这个组件的其他信息(如构成这个组件的HTML代码),他们只想要知道用户输入的值。更何况这个值已转化成数字,而不是原来的字符串,那么就更方便他们的使用。

我们还要留意那个cancel变量,它是处理函数的返回值。要注意它是最后一个处理函数的返回值,因为后一个处理函数的返回值会覆盖前一个处理函数的访回值。这个cancel是根据处理函数返回的值来决定是否让文本框获得焦点。如果为true,则获得焦点,实际上是不让用户转移,直到他输入一个合法数据为止。如果为false,则用户可能留下一个非法的数据。

那看看如何使用这个类:

首先是声明三个处理函数:

function invalid1(ext){        alert("valid2,测试多处理函数和保留焦点");        return true; //保留焦点}function overflow(ext){        alert("输入的值必须在"+this.getMin()+"和"+this.getMax()+"之间");        return true;}     alert("valid1\n当前值为:"+this.getValue()); //调用了类对象的getValue方法}function invalid2(ext){        alert("valid2,测试多处理函数和保留焦点");        return true; //保留焦点}function overflow(ext){        alert("输入的值必须在"+this.getMin()+"和"+this.getMax()+"之间");        return true;}

然后是实例化NumTextBox类

var test = new NumTextBox("test",18,60);

接着是把处理函数绑定到类的事件中:

test.AddInvalidTypeEventHandler(invalid1);//添加非法数据类型事件处理函数test.AddInvalidTypeEventHandler(invalid2);test.AddOverflowEventHandler(overflow);//添加溢出事件处理函数

这样就可以了。实际的使用就是如此简单。可重用性大大增强

转载于:https://my.oschina.net/u/912810/blog/303795

你可能感兴趣的文章
答辩的一般流程、技巧和常见问题
查看>>
杭电1171 Big Event in HDU(母函数+多重背包解法)
查看>>
STM32 USB 问题汇总(转)
查看>>
android的onCreateOptionsMenu()创建菜单Menu详解
查看>>
js swipe 图片滑动控件实现 任意尺寸适用任意屏幕
查看>>
截取字符串替换成星号
查看>>
Kinect for Windows V2.0 新功能
查看>>
解析:使用easyui的form提交表单,在IE下出现类似附件下载时提示是否保存的现象...
查看>>
PHP 错误与异常 笔记与总结(17 )像处理异常一样处理 PHP 错误
查看>>
算法-数组中重复的数字
查看>>
Linux下samba的安装与配置
查看>>
分析Cocos2d-x横版ACT手游源 1、登录
查看>>
SVG在网页中的四种使用方式
查看>>
Unity多玩家网络游戏开发教程1章Unity带有网络功能
查看>>
PEM (Privacy Enhanced Mail) Encoding
查看>>
100种不同图片切换效果插件pageSwitch
查看>>
Android EditText setOnClickListener事件 只有获取焦点才能响应 采用setOnTouchListener解决...
查看>>
Redis入门很简单之六【Jedis常见操作】
查看>>
Java中的Enum的使用与分析
查看>>
WebView之2
查看>>