浓缩解读《JavaScript设计模式与开发实践》②

this、call和apply

QQ图片20170103163230.jpg

2.1 this

  • JavaScript所有函数作用域内部都有一个this对象,它代表调用该函数的对象,具体的值则是由函数的执行环境来动态绑定。(简单理解为,谁调用就指向谁)
2.1.1 this的指向
  • 除去witheval的情况,this的指向大致可以分为以下4种情况:
    • 函数作为对象的方法被调用;
    • 函数作为普通函数被调用;
    • 函数作为构造器被调用;
    • Function.prototype.callFunction.prototype.apply被调用;
  • 函数作为对象的方法被调用时,this指向该对象
var object = {
    str : "object str",
    print : function(){
        return this.str;
    }
};
console.log(object.print());
  • 作为普通函数被调用时,this指向全局对象。如果在浏览器环境中,这个全局对象就是window对象。
var str = "global"; //全局变量str
var object = {
    //对象属性str
    str : "object str",
    print : function(){
        return this.str;
    }
};
//将对象方法赋值给全局函数
var fn = object.print;
console.log(fn());
  • Web前端中,我们熟悉的callback()函数,就经常会出现由于被当做普通函数调用,导致this指向被篡改的问题
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
        <!-- DOM节点的ID属性 -->
        <input id="btn_id" type="button" value="click" />
        <script type="text/javascript">
            //全局id
            var id = "global_id";
            document.getElementById('btn_id').onclick = function(){
                //以对象的方法执行,此时this指向DOM对象,输出"div_id"
                console.log(this.id);
                //声明一个回调函数
                var callback = function(){
                    console.log(this.id);
                }
                //dosomething之后
                //由于以普通函数的方式执行,所以输出"global_id"
                callback();
            };
        </script>
    </body>
</html>
  • 上一章我们了解到什么是构造函数(或者说构造器),它和普通函数的区别只是会以大写字母开头,以new关键字来调用。而构造函数顾名思义,它的作用就是构造一个新对象,所以构造函数在执行后总会返回一个对象。所以,函数作为构造器被调用时,this指向的是这个返回的对象。
var Person = function(){
    this.str = "person str";
};
var person1 = new Person();
console.log(person1.str);    //输出“person str”
  • 但值得注意的是,如果构造函数显式的return一个对象,那this指向的就是这个对象,而不是构造函数的执行代码块的局域变量。
var Person = function(){
    this.str = "person str";
    return {
        this.str = "new person str";
    }
};
var person1 = new Person();
console.log(person1.str);   //输出"new person str"
  • 如果通过Function.prototype.callFunction.prototype.apply调用函数,则可以自定义this的指向。
var str = "global"; //全局变量str
var object1 = {
    str : "object1 str",
    print : function(){
        return this.str;
    }
};
var object2 = {
    str : "object2 str"
};
console.log(object1.print());   //输出"object1 str"
//可以理解成,通过object2对象来执行object1.print()方法。所以this指向的是object2对象,输出"object2 str"
console.log(object1.print.call(object2));
2.1.2 this丢失的情况
  • 类似callback()函数的情况,我们举另外一个例子:document.getElementById()是大家都熟悉的原生JS方法,但方法名太长了,我想用以名字很短的函数来代替它,看看是否可行。
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
        <!-- DOM节点的ID属性 -->
        <input id="btn_id" type="button" value="click" />
        <script type="text/javascript">
            /*
             * 问题示例 
             */
            //将getElementById赋值给getId方法
            var getId = doc'ument.getElementById;
            getId("btn_id");'
            //控制台报错"Uncaught TypeError: Illegal invocation"
            
            /*
             * 示例修改
             */
            //这是因为getElementById()方法内部实现中用到了this了,本来默认this是指向ducument对象的,当做普通函数执行后,this指向就指向了window对象
            //修改:用getId方法包装一层,返回的是通过ducumet对象执行的getElementById()方法的结果
            var getId = function(id){
                return document.getElementById(id);
            };
            getId('btn_id');
        </script>
    </body>
</html>

2.2 call、apply和bind

  • ECMAScript3给Function原型定义了Function.prototype.callFunction.prototype.apply两个方法,不管是函数式风格的代码编写,还是JavaScript的设计模式,这两个方法都应用非常广泛,熟练运用call()apply()方法也是真正称为JavaScript程序员的重要一步。
2.2.1 call
  • call()方法是传入 指定this参数 执行某个函数。
//全局变量
var name = "window";
//公共的执行函数
function print(label){
    console.log(label + ":" + this.name);
}
//对象1
var person1 = {
    name : "Grubby"
};
//对象2
var person2 = {
    name : "Moon"
};print.call(this,"global");    //输出"global:window"
print.call(person1,"person1");  //输出"person1:Grubby"
print.call(person2,"person2");  //输出"person2:Moon"
2.2.2 apply
  • apply()的工作方式和call()完全一样,唯一的区别是需要被传入函数的参数,apply()是通过arguments类似数组的对象传入参数。
print.apply(this,["global"]);   //输出"global:window"
print.apply(person1,["person1"]);   //输出"person1:Grubby"
print.apply(person2,["person2"])    //输出"person2:Moon"
//两者的区别
/*
fn.call(obj, arg1, arg2, arg3...);
fn.apply(obj, [arg1, arg2, arg3...]);
*/
  • 在执行函数时,JavaScript解析器实际上并不会计较形参和实参在数量、类型以及顺序上的区别,(这也导致JavaScript没有Java的方法重写)JavaScript的参数在内部就是一个arguments对象来表示。经过对比可以发现,其实call()是包装apply()的语法糖,不用刻意传入一个数组,不管多少个参数,只管在后面追加就是了。
  • call()apply()一样,如果传入this值是null,那么将指向默认的宿主对象,如果在浏览器中指向是window
2.2.3 bind
  • bind()是ECMAScript5新加的方法,和前两个方法相比, call()apply()是改变了this的值,然后执行函数,而bind()是改变了this值后,返回这个函数。
//不出所料,输出"person1:Grubby"
var printForPerson1 = print.bind(person1);
printForPerson1("person1");
//输出"person2:Moon",绑定的时候传参和执行的时候传参都可以
var printForPerson2 = print.bind(person2,"person2");
printForPerson2();
//printForPerson1()中的this依据被绑定,纵使以person2.print()方法的方式执行,依然输出person1的name
person2.print = printForPerson1;
person2.print("person2");
2.2.3 用途
  • 通过借用其他对象的方法,实现类似继承的效果
//Person构造函数,可以指定name
var Person = function(name){
    this.name = name;
};
//Student构造函数
var Student = function(){
    Person.apply(this,arguments);   //借用Person的构造函数
};
//为学生添加一个说出自己名字的方法
Student.prototype.sayName = function(){
    return this.name;
};
//实例化一个William学生
var student1 = new Student("William");
//虽然学生本身没有name属性,但是最终输出 "William"
console.log(student1.sayName());
  • 利用callapply求一个数组的最大或者最小值
var arr = [45,88,634,22,436,879];
var maxNum = Math.max.apply(Math, arr);
console.log(maxNum);
var minNum = Math.min.call(Math,45,88,634,22,436,879);
console.log(minNum);
  • 利用callapply合并数组
var arr1 = [1,2,3];
var arr2 = [4,5,6];
[].push.apply(arr1, arr2);
console.log(arr1);
//输出: [1, 2, 3, 4, 5, 6]
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容