this,call,apply

this 总是指向一个对象,而具体指向哪个对象是在运行时基于函数的执行环境动态绑定的,而非函数被声明时的环境

this的指向:除去不常用的with 和eval 的情况,具体到实际应用中this的指向大概可以分为以下4种

作为对象的方法调用

作为普通函数调用

构造器调用

Function.prototype.call 或 Function.prototype.apply 调用

作为对象的方法被调用,this指向该对象

var obj = {
a: 1,
getA: function(){
alert ( this === obj ); // 输出:true
alert ( this.a ); // 输出: 1
}
};
obj.getA();
作为普通函数调用
当函数不作为对象的属性被调用时,也就是我们常说的普通函数方式,此时的this总是指向全局对象。在浏览器的Javascript里,这个全局对象是window对象
window.name = 'globalName';
var getName = function(){
return this.name;
};
console.log( getName() ); // 输出: globalName
或者:
window.name = 'globalName';
var myObject = {
name: 'sven',
getName: function(){
return this.name;
}
};
var getName = myObject.getName;
console.log( getName() ); // globalName
有时候我们会遇到一些困扰,比如在div节点的时间函数内部,有一个局部的callback 方法,callback被作为普通函数调用时,callback内部的this指向了window,但我们往往是想让它指向该div节点,
<html>
<body>
<div id="div1"> 我是一个 div</div>
</body>
<script>
window.id = 'window';
document.getElementById( 'div1' ).onclick = function(){
alert ( this.id ); // 输出: 'div1'
var callback = function(){
alert ( this.id ); // 输出: 'window'
}
callback();
};
</script>
</html>
此时有一种简单的解决方案,可以用一个变量保存 div 节点的引用:
图灵社区会员 轩辕 专享 尊重版权 26 2 this call apply
document.getElementById( 'div1' ).onclick = function(){
var that = this; // 保存 div 的引用
var callback = function(){
alert ( that.id ); // 输出: 'div1'
}
callback();
};
ECMAScript 5 strict 模式下,这种情况下的 this 已经被规定为不会指向全局对象,而
undefined
function func(){
"use strict"
alert ( this ); // 输出: undefined
}
func();
3.构造器调用
javascript种没有类,但是可以从构造器中创建对象,同时也提供了new 运算符,使得构造器看起来更像一个类
除了宿主提供的一些内置函数,大部分Javascrit函数都可以当作构造器使用,构造器的外表跟普通函数一模一样,他们的区别在于被调用的方式,当用new运算符调用函数时,该函数总是会返回一个对象,通常情况下,构造器里的this就指向返回的这个对象
var MyClass = function(){
this.name = 'sven';
};
var obj = new MyClass();
alert ( obj.name ); // 输出: sven
但是如果new调用构造器时,还要注意一个问题,如果构造器显示的返回了一个object类型的对象,那么此次运算结果最终会返回这个对象,而不是我们之前期待的this
var MyClass = function(){
this.name = 'sven';
return { // 显式地返回一个对象
name: 'anne'
}
};
var obj = new MyClass();
alert ( obj.name ); // 输出: anne
如果构造器不显示的返回任何数据,或者返回一个非对象类型( 非( Object,Function,Array,String,Number,Boolean))的数据,就不会造成上述问题
var MyClass = function(){
this.name = 'sven'
return 'anne'; // 返回 string 类型
};
var obj = new MyClass();
alert ( obj.name ); // 输出: sven
Function.prototype.call 或Function.prototype.apply 可以动态的改变传入的this值
var obj1 = {
name: 'sven',
getName: function(){
return this.name;
}
};
var obj2 = {
name: 'anne'
};
console.log( obj1.getName() ); // 输出 : sven
console.log( obj1.getName.call( obj2 ) ); // 输出: anne
call 和apply 方法能更好的体现javascript的函数式语言特性,在javaacript 中,几乎每一次编写函数式语言风格的代码都离不开call和apply,在javascript诸多版本的设计模式中,也用到了call和allpy
 丢失的this
这是一个经常遇到的问题,我们先看下面的代码:
var obj = {
myName: 'sven',
getName: function(){
return this.myName;
}
};
console.log( obj.getName() ); // 输出: 'sven'
var getName2 = obj.getName;
console.log( getName2() ); // 输出: undefined
当调用 obj.getName 时, getName 方法是作为 obj 对象的属性被调用的,根据 2.1.1 节提到的规
律,此时的 this 指向 obj 对象,所以 obj.getName() 输出 'sven'
图灵社区会员 轩辕 专享 尊重版权 28 2 this call apply
当用另外一个变量 getName2 来引用 obj.getName ,并且调用 getName2 时,根据 2.1.2 节提到的
规律,此时是普通函数调用方式, this 是指向全局 window 的,所以程序的执行结果是 undefined
再看另一个例子,document.getElementById这个方法名实在有些过长,我们大概尝试过用一些短的函数来代替它,如同prototype.js等一些框架所做过的事情,
var getId=function(id){
    return document.getElementById(id)
}
getId('div1')
我们也许思考过为什么不能用下面这种更简单的方式
var getId = document.getElementById
getId('div1')
现在不妨花 1 分钟时间,让这段代码在浏览器中运行一次:
<html>
<body>
<div id="div1"> 我是一个 div</div>
</body>
<script>
var getId = document.getElementById;
getId( 'div1' );
</script>
</html>
在Chrome,Firefox,IE10中执行过后就会发现,这段代码抛出了一个异常,这是因为许多引擎的document.getElementById 方法的内部实现中需要用到this。这个this本来被期望指向document,当getElementById方法作为document对象的属性被调用时,方法内部的this确实是指向document的。
但当用getId 来引用getElementById之后,再调用getId,此时就成了普通函数的调用,函数内部的this指向了window,而不是原来的document
我们可以尝试利用apply把document当作this传入getId函数,帮助修正this
document.getElementById = (function(func){
       return function(){ 
           return func.apply(document,arguments);
 }
})(document.getElementById)
r getId = document.getElementById;
var div = getId( 'div1' );
alert (div.id); // 输出: div1
call和apply
ECAMScript3给Function的原型定义了两个方法,他们是Function.prototype.call和Function.prototype.apply。在实际开发中,特别是在一些函数式风格的代码编写中,call,apply方法尤为有用,在javascript版本的设计模式中,这两个应用也非常广泛,能熟练运用这两个方法,市我们真正成为一名javascript程序员的重要一步
call apply 的区别
Function.prototype.call 和Function.prototype.apply 都是非常常用的方法,他们的作用一模一样,区别仅在于参数形式的不同。
apply接受两个参数,第一个参数指定了函数体内this对象的指向,第二个参数为一个带下标的集合,这个集合可以为数组,也可以为类数组,apply方法把这个集合中的元素作为参数传递给被调用的函数
var func =function(a,b,c,){
    alert([a,b,c]) //输出[1,2,3]
}
func.apply(null,[1,2,3]);
在这段代码中,参数1,2,3被放在数组中一起传入func函数,他们分别对应func参数列表中的a,b,c
call传入的参数的数量不固定,跟apply 相同的是,第一个参数也是代表函数体内的this指向,从第二个参数开始往后,每个参数被依次传入函数。
var func =function(a,b,c){
    alert([a,b,c])//输出[a,b,c]
}
func.call(null,1,2,3)
当调用一个函数时,javascript的解释器并不会计较形参和实参的数量,类型以及顺序上的区别,javascript的参数在内部就是用一个数组来表示的。从这个意义上说,apply比call 的使用效率更高,我们不必关心有多少个参数被传入函数,只要用apply一股脑的推过去就行了。
call是包装在apply上面的一颗语法糖,如果我们明确的知道函数接受多少个参数,而且像一目了然的表达形参和实参的对应关系,那么也可以用call来传递参数
当使用call或者apply的时候,如果我们传入的第一个参数是null,函数体内的this会指向默认的宿主对象,在浏览器中则是window
var func = function( a, b, c ){
alert ( this === window ); // 输出 true
图灵社区会员 轩辕 专享 尊重版权 30 2 this call apply
};
func.apply( null, [ 1, 2, 3 ] );
但如果是在严格模式下,函数体内的 this 还是为 null
var func = function( a, b, c ){
"use strict";
alert ( this === null ); // 输出 true
}
func.apply( null, [ 1, 2, 3 ] );
有时候我们使用call或者apply的目的不在于指定this指向,而是另有用途,比如借用其他对象的方法,那么我们可以传入null来代替某个具体的对象
Math.max.apply(null,[1,2,3,4,5])//输出5
call和apply的用途
1、改变this的指向
call和apply最常见的用途是改变函数内部的this指向
var obj1={name:'obj1'};
var obj2={name:'obj2'};
window.name='window';
var getName=function(){alert(this.name)};
getName() //输出window
getName.call(obj1) //输出 obj1
getName.call(obj2)//输出obj2
当执行getName.call(obj1)这句代码时,getName 函数体内的this就指向obj1对象,所以此处执行的
var getName = function(){alert(this.name)}
实际上相当于:
var getName = function(){ alert(obj1.name) }
在实际开发中,经常会遇到this指向被不经意改变的场景,比如有一个div节点,div节点的onclick事件中的this本来指向的是这个div的
document.getElementById('div1').οnclick=function(){alert(this.id)} //输出div1
假如该事件函数中有一个内部函数 func,在事件内部调用func函数时,func函数体内的this就指向了window,而不是我们预期的div
document.getElementById('div1').οnclick=function(){alert(this.id);var func=function(){alert(this.id)}}//输出div1 输出underfined
这时候我们用call来修正func函数内的this,使其依然指向div
document.getElementById('div1').οnclick=function(){var func=function(){alert(this.id);func.call(this)}}//输出div1
使用call来修正this的场景,我们并非第一次遇到,在上一小节关于this的学习中,我们就曾经修正果document.getElementById函数内部“丢失”的this
document.getElementById=(function (func){
return function(){
  return func.apply(document,arguments)
}
})(document.getElementById)
var getId =document.getElementById;
var div =getId('div1')
alert(div.id)//输出 div1
2、Function.prototype.bind
大部分高级浏览器都实现了内置的Function.prototype.bind 用来指向函数内部的this指向,即使没有原生的Function.prototype.bind实现我们来模拟一个也不是难事
Function.prototype.bind = function (context) {
    var self = this; //保存原函数
    console.log(this)
    return function () {
        return self.apply(context, arguments);//执行新的函数的时候,会把之前的context,当作新函数体内的this
    }
}
var obj = {
    name: 'obj1'
}
var func =function(){
    alert(this.name) //输出obj1
}.bind(obj)

func()

我们通过Function.prototype.bind 来包装func 函数,并且传入一个对象context当作参数,这个context对象就是我们想修正的this对象,

在Function.prototype.bind 的内部实现中,我们先把func函数的引用保存起来,然后返回一个新的函数,当我们将来执行func函数时,实际上执行的是这个刚刚返回的新函数,在新函数内部。self.apply(context,arguments)这句代码才是执行原来的func函数,并且指定context对象为fun函数体内的this

这是一个简化版的Function.prototype.bind实现,通常我们还会把它实现的稍微复杂一点,使得可以往函数中预先填入一些参数,

Function.prototype.bind = function () {
    var self = this; // 保存原函数
    var context = [].shift.call(arguments); // 需要绑定的 this 上下文
    console.log(context);

    var args = [].slice.call(arguments); // 剩余的参数转成数组
    console.log(args);
    return function () { // 返回一个新的函数
        return self.apply(context, [].concat.call(args, [].slice.call(arguments)));
        // 执行新的函数的时候,会把之前传入的 context 当作新函数体内的 this
        // 并且组合两次分别传入的参数,作为新函数的参数
    }
};
var obj = {
    name: 'sven'
};
var func = function (a, b, c, d) {
    console.log(this.name); // 输出:sven
    console.log([a, b, c, d]) // 输出:[ 1, 2, 3, 4 ]
}.bind(obj, 1, 2);
func(3, 4);

3、借用其它对象的方法

我们知道,杜鹃既不会筑巢,也不会孵雏,而是把自己的蛋寄托给云雀等其他的鸟类,让他们代为孵化和养育,同样,在javascript中也存在类似的借用现象。

借用方法的第一种场景是“借用构造函数”,通过这种技术,可以实现一些类似继承的效果。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值