this的指向和分析

JavaScrip高级教程 · 143 字

this的作用

this常出现在类中,可以指向自身

this的指向

全局环境指向

  • 浏览器环境:window(globalOject)
  • Node环境:空对象{}。Node中调用JS时当作module → 加载→编译→放在函数中→执行函数.call()

不同执行方式的不同指向

动态绑定 — 函数执行时才能确定this指向谁

  • 默认绑定:没有明确的调用主体,独立的函数调用。指向window

    function foo1 () {
    	console.log(this);
    }
    function foo2 () {
    	console.log(this);
    	foo1();
    }
    function foo3 () {
    	console.log(this);
    	foo2();
    }
    // 输出结果全为window
    
    var obj = {
    	name: "why",
    	foo: function() {
    		console.log(this)
    	}
    }
    var bar = obj.foo;
    bar()
    // 输出结果为window
    
  • 隐式绑定:通过某个对象发起函数调用。object对象会被js引擎绑定到fn函数中的this里面

    function foo() {
    	console.log(this);
    }
    var obj = {
    	name: "why",
    	foo: foo
    }
    
    obj.foo()
    
  • 显式绑定:call、apply、bind

    • call与apply的区别:call传入参数列表,apply传入参数数组

    • bind:返回一个信函数,永久改变this指向,传入参数数组

      function foo() {
      	console.log(this);
      }
      
      var newFoo = foo.bind("aaa")
      
  • new绑定

    1. 创建一个全新对象
    2. 这个新对象被执行prototyp链接
    3. 这个新对象会绑定到函数调用的this上
    4. 如果函数没有返回其他对象,表达式返回这个新对象
    function Person(name, age) {
    	this.name = name;
    	this.age = age;
    }
    var p = new Person("why", 18)
    console.log(p1.name, p1.age); // why 18
    

系统API中的this指向

  1. setTimeout

    setTimeout(function() {
    	console.log(this); // window
    }, 2000)
    
  2. 监听点击

    const boxDiv = document.querySelector(".box");
    boxDiv.onClick = function() {
    	console.log(this); // 指向绑定的dom元素
    }
    
  3. 数组.forEach/map/filter…

    var names = [];
    names.forEach(function() {
    	console.log(this); // 默认时为window,第二个参数thisArg能指定this指向
    }, thisArg)
    

多个绑定规则的优先级

  1. 默认规则的优先级最低

  2. 显式绑定优先级高于隐式绑定

    function foo() {
    	console.log(this);
    }
    var obj = {
    	name: "obj",
    	foo: foo.bind("aaa")
    }
    obj.foo(); // {String: "aaa"}
    
  3. new绑定优先级高于隐式绑定

    var obj = {
    	name: "obj",
    	foo: function() {
    		console.log(this);
    	}
    }
    var f = new obj.foo(); // foo {}
    
  4. new绑定优先级高于bind

    this的指向和分析

    function foo() {
    	console.log(this);
    }
    var bar = foo.bind("aaa");
    var obj = new bar(); // foo {}
    

忽略显式绑定

apply/call/bind:当传入null/undefined时,自动将this绑定为window

间接函数引用

var obj1 = {
	name: "obj1",
	foo: function() {
		console.log(this);
	}
}
var obj2 = {
	name: "obj",
	bar: null
};
(obj2.bar = obj1.foo)(); // 独立函数调用window
/* Uncaught TypeError: Cannot set properties of undefined (setting 'bar')
    at <anonymous>:11:11*/

(obj2.bar = obj1.foo) 与上面的obj2的赋值看作是一个整体,这样执行obj2.bar时会undefined。解决方法是在obj2的赋值语句后面加一个; ,这样在进行语法分析时能清晰的知道二者为独立的语句。

箭头函数的使用

  • 箭头函数不会绑定this、arguments属性;
  • 不能作为构造函数来使用

常见的简写方法

  1. 如果参数只有一个,()可以省略 item => {}
  2. 函数执行体只有一行代码,{}可以省略,并且默认将该行代码的执行结果作为返回值 item => console.log(item)
  3. 只有一行代码,并且返回一个对象 () => ({ name: "why", age: 18 })

箭头函数中的this

不适用于this的四种规则,而是根据外层作用域来决定this,与定义的位置有关

var name = "why";
var foo = () => {
	console.log(this);
}

foo(); // window
var obj = {foo: foo};
obj.foo(); // window
foo.call("aaa"); // window

应用场景:

var obj = {
	data: [],
	getData: function() {
		// 发送网络请求,将结果放在上面data属性中
		// 在箭头函数之前的解决方案
		// var _this = this;
		// setTimeout(function() {
		// 	var result = ["abc"];
		// 	_this.data = result;
		// }, 2000)
		
		// 箭头函数
		setTimeout(() => {
			var result = ["abc"];
			this.data = result;
		}, 2000)
	}
}

面试题

this的指向和分析1

var name = 'window'
var person1 = {
  name: 'person1',
  foo1: function () {
    console.log(this.name)
  },
  foo2: () => console.log(this.name),
  foo3: function () {
    return function () {
      console.log(this.name)
    }
  },
  foo4: function () {
    return () => {
      console.log(this.name)
    }
  }
}

var person2 = { name: 'person2' }

person1.foo1();  // person1
person1.foo1.call(person2); // person2 

person1.foo2(); // windows 定义对象时不产生作用域,因此上层作用域为obj的上层作用域
person1.foo2.call(person2); // window

person1.foo3()(); // window
person1.foo3.call(person2)(); // window
person1.foo3().call(person2); // person2

person1.foo4()(); // person1 返回箭头函数,上层为foo4: function... 它的this为隐式调用确定的person1
person1.foo4.call(person2)(); // person2 同上
person1.foo4().call(person2); // person1 call不改变箭头函数的this指向
,var name = 'window'
function Person (name) {
  this.name = name
  this.foo1 = function () {
    console.log(this.name)
  },
  this.foo2 = () => console.log(this.name),
  this.foo3 = function () {
    return function () {
      console.log(this.name)
    }
  },
  this.foo4 = function () {
    return () => {
      console.log(this.name)
    }
  }
}
var person1 = new Person('person1')
var person2 = new Person('person2')

person1.foo1() // person1 隐式绑定
person1.foo1.call(person2) // person2 显式绑定给

person1.foo2() // person1 箭头函数,函数是由作用域的,上层作用域为构造函数new Person('person1')
person1.foo2.call(person2) // person1 同上

person1.foo3()() // window 独立调用
person1.foo3.call(person2)() // window 同上
person1.foo3().call(person2) // person2 显式绑定

person1.foo4()() // person1 箭头函数,上层作用域为foo4,此处绑定的为person1
person1.foo4.call(person2)() // person2 上层作用域为foo4,此处显式绑定的person2
person1.foo4().call(person2) // person1
var name = 'window'
function Person (name) {
  this.name = name
  this.obj = {
    name: 'obj',
    foo1: function () {
      return function () {
        console.log(this.name)
      }
    },
    foo2: function () {
      return () => {
        console.log(this.name)
      }
    }
  }
}
var person1 = new Person('person1')
var person2 = new Person('person2')

person1.obj.foo1()() // window 独立调用
person1.obj.foo1.call(person2)() // window 同上
person1.obj.foo1().call(person2) // person2 显式绑定

person1.obj.foo2()() // obj 箭头函数,foo2是被obj调起的,隐式绑定foo2的this为obj,因此上层作用域为obj
person1.obj.foo2.call(person2)() // person2 类似上面,但foo2被显式绑定为person2
person1.obj.foo2().call(person2) // obj 同第一个

自主实现call、apply、bind

call

Function.prototype.myCall = function(thisAgs, ...args){
    // 1. 获取被执行函数,利用隐式绑定特性
    var fn = this;

    // 2. 将thisArg转成对象类型
    thisAgs = (thisAgs !== null && thisAgs !== undefined) ? Object(thisAgs) : window;

    // 3. 调用被执行函数
    thisAgs.fn = fn;
    var result = thisAgs.fn(...args);
    delete thisAgs.fn;

    // 4. 返回执行结果
    return result;
}

apply

Function.prototype.myApply = function(thisAgs, argArray){
    // 1. 获取被执行函数,利用隐式绑定特性
    var fn = this;

    // 2. 将thisArg转成对象类型
    thisAgs = (thisAgs !== null && thisAgs !== undefined) ? Object(thisAgs) : window;

    // 3. 调用被执行函数
    thisAgs.fn = fn;
    argArray = argArray || [];
    // argArray = argArray ? argArray : []
    var result = thisAgs.fn(...argArray);
    delete thisAgs.fn;

    // 4. 返回执行结果
    return result;
}

bind

Function.prototype.myBind = function (thisAgs, ...argArray) {
    // 1. 获取被执行函数,利用隐式绑定特性
    var fn = this;

    // 2. 将thisArg转成对象类型
    thisAgs = (thisAgs !== null && thisAgs !== undefined) ? Object(thisAgs) : window;

    function proxyFn(...args) {
        // 3. 函数放到thisArgs上进行调用
        thisAgs.fn = fn;
        // 对两个传入的参数进行合并
        var finalArgs = [...argArray, ...args]
        var result = thisAgs.fn(...finalArgs);
        delete thisAgs.fn;

        // 4. 返回执行结果
        return result
    }

    return proxyFn;
}