为什么需要this?
this代表的是当前调用对象,指向当前函数的调用者,并不是函数的本身。在开发过程中,如果没有this,也是能够进行开发的,也有很多种解决方案,但是没有this会让我们代码编写非常不方便,this可以隐式的传递一个对象的引用,进行及其灵活的调用,极大的提高开发效率。
例如: 当我们创建一个对象的时候,内部存在eating和study方法,我们分别使用this和obj来获取当前对象的name属性,都是可以获取到的,当我们需要创建多个相同对象的时候,我们cv的时候,obj对象改变成test对象,那么study方法中name的调用者也就变成了test,需要改变 (只是简单举个例子)
let obj ={
name: 'qqq',
eating: function(){
console.log(this.name, '吃吃吃')
}
study: function(){ // 不使用this
console.log(obj.name, '吃吃吃')
}
}
this的指向
this在全局作用域下指向 浏览器指向window node环境中指向{}
this指向什么和函数所处在的位置是没有关系的,函数在调用的时候,js会给this绑定一个默认的值,跟函数被调用的方式以及调用位置有关。(this是在运行的时候才被绑定的)
this的绑定规则
- 默认绑定
- 隐式绑定
- 显示绑定
- new绑定
默认绑定
当我们这个函数是被独立调用的时候,该函数就是默认绑定
例:
function test(){
console.log(this)
}
test()
// 独立调用,默认绑定,在浏览器下指向window
function done(){
console.long(this)
test()
}
done()
// 输出为两个window, 我们不看函数所处位置,看函数调用方式和调用位置,test还是独立调用的
// 独立调用,默认绑定,在浏览器下指向window
隐式绑定
是通过某个对象来进行调用的,调用的位置存在上下文对象。
例:
let name = 'window'
function tt(){
console.log(this)
}
var obj = {
name: 'qqq',
test: tt,
demo: function () {
console.log(this.name) //qqq
},
sayHi: function(){
setTimeout(function(){
console.log('Hello,',this);
})
}
}
obj.test()
// 该test函数是通过obj来调用的,那么他绑定的this就是obj对象
obj.sayHi()
若函数呗setTimeout等包裹,在通过obj调用,setTimeout中this指向window(先不考虑箭头函数的影响),如本例子中,obj调用sayHi,该this指向window,我们可以把setTimeout中的function理解为一个callback函数,如下述代码,我们看这个fn(),就相当于是独立函数调用,所以指向window
function callback(){}
setTimeout(callback, time)// setTimeout中传入了callback函数
// setTimeout执行
function setTimeout(fn, delay) {
fn()
}
setTimeout()
显示绑定
显示绑定,就是通过call,apply,bind的方式,显示的改变this的指向方式。
call() 和 apply() 能够传入第一个参数来改变this 的指向,第二个参数:call和apply两者传参不同,一者是一个个字符(以剩余参数的形式),一者是数组传入参数。
如果我们将null或者是undefined作为this的绑定对象传入call、apply或者是bind,这些值在调用时会被忽略,实际应用的是默认绑定规则。
例: call/apply
function demo() {
console.log('调用函数',this)
}
// foo() // 直接调用,指向全局对象,即window
// foo.call(); foo.apply(); // 这样可以直接调用,不绑定this, 默认是window
// call() 和 apply() 能够传入一个参数来改变this 的指向
demo.call('call')
demo.apply('apply')
例: bind
function demo() {
console.log('调用函数',this)
}
var newFn = demo.bind('new')
// 该处,可能会以为这里显示绑定与默认绑定冲突了,但是显示绑定优先级更高,以显示绑定为主
newFn() // this指向new
new绑定
会帮我们重新创建一个新的对象,构造函数中的this,指向这个空的对象,这个新的对象被执行,执行构造函数和方法,属性和方法被添加到this引用的对象中。
function _new() {
let target = {}; //创建的新对象
//第一个参数是构造函数
let [constructor, ...args] = [...arguments];
//执行[[原型]]连接;target 是 constructor 的实例
target.__proto__ = constructor.prototype;
//执行构造函数,将属性或方法添加到创建的空对象上
let result = constructor.apply(target, args);
if (result && (typeof (result) == "object" || typeof (result) == "function")) {
//如果构造函数执行的结构返回的是一个对象,那么返回这个对象
return result;
}
//如果构造函数返回的不是一个对象,返回创建的新对象
return target;
}
例: new绑定,我们通过new关键字调用一个函数(构造函数)的时候,这时候的this是在调用这个函数创建出来的对象。
function Person(name, age) {
this.name = name
this.age = age
}
var p1 = new Person('qq', 1)
console.log(p1.name, p1.age) //'qq' ,1
关于内置函数中this绑定
// 1、setInterval()
function callback(){}
setInterval(callback, time)// setInterval
// setTimeout执行
function setInterval(fn, delay) {
fn() // window
}
setTimeout()
// 2、监听点击事件的时候
const box = document.querySelector('.box')
box.onclick = function(){
// 内部做了 box.onclick() 通过隐式绑定来执行的
console.log(this) // 返回给我们该元素的对象
}
// 3、数组foreach、map等引用的时候
var demo = [1,2,3,4]
demo.forEach(function() {
// 是通过内部的回调来执行的
console.log(this) // window
}, 'xxx'可以改变this的指向)
绑定的优先级
new绑定 > 显式绑定 > 隐式绑定 > 默认绑定
默认绑定优先级一定是最低的。存在其他规定的时候,默认优先级一定会被覆盖的
// demo 显示高于隐式
var obj = {
name :'213',
foo: function(){
console.log(this)
}
}
obj.foo.apply('asd') // asd
// 输出为asd,显示高于隐式
let test = new obj.foo()
test() // foo的函数对象,new高于隐式
// 显示与new相比较
// new 和 apply call不能一起使用,只能和bind相比较
function tt() {
console.log(this)
}
var bar = tt.bind('123')
var o = new bar()
o() // 打印函数对象,new 高于显示
箭头函数
箭头函数是ES6中新增的,他和普通函数有一些区别,箭头函数不会绑定this,arguments属性,箭头函数也不能当作构造函数来使用。
箭头函数体内的this,是继承外层代码块的this。
var demo = {
frist: function(){
console.log('frist', this)
return () =>{
console.log('frist-2', this)
}
},
second: () => {
// 指向上一层的this,该处上一层是window,作用域只有函数作用域和全局作用域
console.log(this)
}
}
demo.frist() // demo
demo.frist()() // demo
demo.second() // window
高阶函数中使用箭头函数
let arr = [1,2,3,4,5]
arr.forEach((item)=>{
console.log(item, this) // 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(); //perosn1
person1.foo1.call(person2); //person2
person1.foo2(); //perosn1
person1.foo2.call(person2); //person1
person1.foo3(); //window
person1.foo3.call(person2)(); //window
person1.foo3().call(person2); //person2
person1.foo4()(); //perosn1
person1.foo4.call(person2)(); //person2
person1.foo4().call(person2); //perosn1
\