前端攻城狮查缺补漏系列-this、call、apply、bind

72 阅读3分钟

this

官方定义:this是当前执行的环境。(下面好理解)

可以这样理解,在JavaScript中,this的指向是调用时决定的,而不是创建时决定的(敲黑板,作用域是创建时决定的)。谁调用了这个函数,那么这个函数中的this就指向谁。例如:obj.sayHi(),sayHi函数是被obj调用的,所以sayHi函数中的this就是obj,this.name其实就是访问在obj中定义的name。(我的言语组织能力就到这了。。)

全局上下文

在全局执行上下文中this都指代全局对象。this等价于window对象,var === this. === winodw.

console.log(window === this); // true
var a = 1;
this.b = 2;
window.c = 3;
console.log(a + b + c); // 6

函数上下文

在函数内部,this的值取决于 函数被调用 的方式。

function foo(){
  return this;
}
console.log(foo() === window); // true

call、apply

this指向绑定到对象上。(直接调用函数)

改变this指向代码指向

var person = {
  name: "axuebin",
  age: 25
};
function say(job){
  console.log(this.name+":"+this.age+" "+job);
}
say.call(person,"FE"); // axuebin:25
say.apply(person,["FE"]); // axuebin:25
可以看到,定义了一个say函数是用来输出name、age和job,其中本身没有name和age属性,我们将这个函数绑定到person这个对象上,输出了本属于person的属性,说明此时this是指向对象person的。call和apply的区别是第二个参数的传入格式不同。

bind

返回新的函数并将this指向绑定到bind的第一个参数。(返回一个新函数)

改变this指向代码演示

this.name="jack";
var demo={
  name:"rose",
  getName:function(){return this.name;}
}
console.log(demo.getName());//输出rose 这里的this指向demo

var another=demo.getName;
console.log(another())//输出jack 这里的this指向全局对象

//运用bind方法更改this指向
var another2=another.bind(demo);
console.log(another2());//输出rose 这里this指向了demo对象了

bind可以对一个函数预设初始参数。代码演示

预设初始参数的意思是:使用bind可以让返回的函数有默认的实参,如下代码中,1,2其实就是实参,后面不管在调用b方法时传入多少参数都不会影响1,2这两个实参。其实在就和 柯里化差不多。

function a() {
  return Array.prototype.slice.call(arguments)
}
var b = a.bind(this,1,2)
console.log(b()) // [1,2]
var c = b(3,4,5)
console.log(c()) // [1,2,3,4,5]

拓展

用原生javascript 实现call

Function.prototype.myCall = function(context){
  context = context || window;
  context.fn = this;
  var args = [];
  if (arguments[1] === void 0) {
    context.fn()
    delete context.fn
    return
  }
  for (var i=0; i<arguments.length; i++) {
    args.push('arguments['+i+']')
  }
  eval('context.fn('+args+')')
  delete context.fn
}
var name = '马士春'
var obj = {
  name : 'msc',
  sayHi: function() {
    console.log(this.name, arguments)
  }
}
obj.sayHi.myCall(window,3) // 输出: '马士春,[window,3]'

用原生javascript 实现apply (和call实现方法类似)

Function.prototype.myApply = function(context){
  //如果第一个参数没有传或者为null、undefined,默认传入window
  var context = context || window; 

  // this是执行的函数,并将其复制content的fn函数上。
  context.fn = this; 

  // 获取第二个参数
  var args = [];

  // 如果没有传第二个参数,就直接执行
  if ( arguments[1] === void 0) {
    context.fn()
    delete context.fn
    return 
  } else if(Array.isArray(arguments[1])) { // 第二个参数是数组
    // 这里的技巧很重要
    for (var i=0; i<arguments[1].length; i++) {
      args.push('arguments['+i+']')
    }
    //eval执行fn
    eval('context.fn(' + args + ')')
  } else { // 只接受数组或者不传为空,不能为其他类型
     throw new Error ("CreateListFromArrayLike called on non-object");
  }
  delete context.fn
}
var name = '张三'
var obj = {
  name: 'msc',
  sayHi: function() {
    console.log(this.name,Array.prototype.slice.call(arguments))
  }
}
var bindObj = {
  name: '马士春'
}
// 绑定到bindObj上,不传第二个参数
obj.sayHi.myApply(bindObj) // 输出'马士春,[]'说明this被绑定到了bindObj上了。

// 不传第一个参数,不传第二个参数
obj.sayHi.myApply() // 输出 '张三,[]' 说明不传第一个参数默认会绑定到window上

// 绑定到bindObj上,第二个参数传个数组
obj.sayHi.myApply(bindObj,[1,2]) 输出 '马士春,[1,2]'

用原生javascript 实现bind

//工具方法
function fnRandow(obj){
  var randow = new Date().getTime()
  if (obj.hasOwnProperty(randow)) {
    fnRandow(obj)
  } else {
    return randow
  }
}
//工具方法
function toArray(arg,start,end) {
  var result = [];
  start = start || 0;
  end = end || arg.length;
  for (var i=start; i< end; i++){
    result.push(arg[i])
  }
  return result;
}

Function.prototype.myBind = function(context){
  context = context || window;
  var args = toArray(arguments,1),self = this;
  var F = function() {}
  F.prototype = this;
  var bond = function() {
    var resultArgs = toArray(arguments).concat(args)
    return (function() {
      //生成一随机数,并确保context对象中没有这个属性。
      var FNrandom = fnRandow(context);
      context[FNrandom] = self;
      context[FNrandom](resultArgs)
      delete context[FNrandom]
    })()
  }
  bond.prototype = new F()
  return bond
}
var obj = {
  name: 'msc',
  say: function(){
    console.log(this.name, arguments)
  }
}
var newo = {
  name: '马士春'
}
var fn = obj.say.myBind(newo,1,2,3)
fn(4,5,6) //  输出 '马士春,[1,2,3,4,5,6]'