浓缩解读《JavaScript设计模式与开发实践》②
this、call和apply
QQ图片20170103163230.jpg
2.1 this
JavaScript所有函数作用域内部都有一个
this
对象,它代表调用该函数的对象,具体的值则是由函数的执行环境来动态绑定。(简单理解为,谁调用就指向谁)
2.1.1 this的指向
除去
with
和eval
的情况,this的指向大致可以分为以下4种情况:函数作为对象的方法被调用;
函数作为普通函数被调用;
函数作为构造器被调用;
Function.prototype.call
或Function.prototype.apply
被调用;
函数作为对象的方法被调用时,
this
指向该对象
var object = { str : "object str", print : function(){ return this.str; } };console.log(object.print());
作为普通函数被调用时,
this
指向全局对象。如果在浏览器环境中,这个全局对象就是window
对象。
var str = "global"; //全局变量strvar 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'). = 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.call
或Function.prototype.apply
调用函数,则可以自定义this
的指向。
var str = "global"; //全局变量strvar 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.call
或Function.prototype.apply
两个方法,不管是函数式风格的代码编写,还是JavaScript的设计模式,这两个方法都应用非常广泛,熟练运用call()
和apply()
方法也是真正称为JavaScript程序员的重要一步。
2.2.1 call
call()
方法是传入 指定this
值 和 参数 执行某个函数。
//全局变量var name = "window";//公共的执行函数function print(label){ console.log(label + ":" + this.name); }//对象1var person1 = { name : "Grubby"};//对象2var 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的nameperson2.print = printForPerson1; person2.print("person2");
2.2.3 用途
通过借用其他对象的方法,实现类似继承的效果
//Person构造函数,可以指定namevar 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());
利用
call
和apply
求一个数组的最大或者最小值
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);
利用
call
和apply
合并数组
var arr1 = [1,2,3];var arr2 = [4,5,6]; [].push.apply(arr1, arr2);console.log(arr1);//输出: [1, 2, 3, 4, 5, 6]
作者:梁同学de自言自语
链接:https://www.jianshu.com/p/ab03036de91a
共同学习,写下你的评论
评论加载中...
作者其他优质文章