this 是执行上下文中的一个属性,指向最后一次调用这个方法的 对象。在实际开发中,this 的指向可以通过四种调用模式来判断。
第一种是函数调用模式,当一个函数不是一个对象的属性时,直接作为函数来调用时,this 指向全局对象,在严格模式下是undefined,如果调用的是let/const声明的变量是undefined。
var a = 'absc' let b = 'bsc' const c = 'csc' function initThis() { console.log(this.a)//absc console.log(this.b)//undefined console.log(this.c)//undefined } initThis()
思考:为什么全局声明的let/conts声明的变量会是undefined?
这是因为用var声明的变量是挂载到全局window上面的,而let和const声明的变量是挂载到script上的。
第二种是方法调用模式,如果一个函数作为一个对象的方法来调用时, this 指向这个对象。
const obj = { e:'eee', f:function() { console.log(this.e)//'eee' }, g:()=>{ console.log(this.e)//undefined } } obj.f() obj.g()
思考:为什么箭头函数中打印e是undefined?
箭头函数没有自己的this,它指向的this是它当前(外层)作用域下的this(但是当前作用域下必须是函数作用域(这个函数作用域必须是普通函数),不能是普通对象作用域,也就是对象,如果是在对象下定义的箭头函数,this会一直往外找,找到最近的函数作用域,如果没找到,则this指向window)
JS作用域分为全局作用域、函数作用域、let,const块级作用域,并没有对象作用域,所以这里说的this指向为当前作用域下的this的,即为函数作用域或者全局作用域。
第三种是构造器调用模式,如果一个函数用 new 调用时,函数执行 前会新创建一个对象,this 指向这个新创建的对象。
function ThisClass (a) { this.a = a }let thisClass = new ThisClass('1')console.log(thisClass.a)//1
第四种是 apply 、 call 和 bind 调用模式,这三个方法都可以显示的指定调用函数的 this 指向。
//bind()方法会创建一个函数,该函数的this指向了传入的第一个参数,当bind()的参数为空时this指向全局对象。
var hello = function(){
console.log(this.name);
};
var demo = {
name: 'demo'
};
var h = hello.bind(demo);
h(); // out 'demo'
第五种:在事件中this指向事件源。
第六种:在定时器中如果不使用箭头函数this指向全局。
这六种方式,使用构造器调用模式的优先级最高,然后是 apply、call 和 bind 调用模式,然后是方法调用模式,然后是函数调用模式。
apply 、 call 和 bind?
func.call(thisArg, param1, param2, ...)
func.apply(thisArg, [param1,param2,...])
func.bind(thisArg, param1, param2, ...)
apply:一个是 this 绑定的对象,一个是参数数组。
call:方法接收的参数, 第一个是 this 绑定的对象,后面的其余参数是传入函数执行的参数。也就是说,在使用 call() 方法时,传递给函数的参数必须逐个列举出来。
bind:通过传入一个对象,返回一个this绑定了传入对象的新函数。这个函数的 this 指向除了使用 new 时会被改变,其他情况下都不会改变。
共同能改变目标this的指向,不同点是call、apply是在改变了目标this执行之后立马执行,而bind不是马上执行。
let a = {
name: 'jack',
getName: function(msg) {
return msg + this.name;
}
}
let b = {
name: 'lily'
}
console.log(a.getName('hello~')); // hello~jack
console.log(a.getName.call(b, 'hi~')); // hi~lily
console.log(a.getName.apply(b, ['hi~'])) // hi~lily
let name = a.getName.bind(b, 'hello~');
console.log(name()); // hello~lily
使用场景
获取数组的最大/最小值
let arr = [13, 6, 10, 11, 16];
const max = Math.max.apply(Math, arr);
const min = Math.min.apply(Math, arr);
console.log(max); // 16
console.log(min); // 6
继承
function Parent3 () {
this.name = 'parent3';
this.play = [1, 2, 3];
}
Parent3.prototype.getName = function () {
return this.name;
}
function Child3() {
Parent3.call(this);
this.type = 'child3';
}
Child3.prototype = new Parent3();
Child3.prototype.constructor = Child3;
var s3 = new Child3();
console.log(s3.getName()); // 'parent3'
类数组借用方法
var arrayLike = {
0: 'java',
1: 'script',
length: 2
}
Array.prototype.push.call(arrayLike, 'jack', 'lily');
console.log(typeof arrayLike); // 'object'
console.log(arrayLike);
// {0: "java", 1: "script", 2: "jack", 3: "lily", length: 4}
数据类型判断
function getType(obj){
let type = typeof obj;
if (type !== "object") {
return type;
}
return Object.prototype.toString.call(obj).replace(/^$/, '$1');
}
apply和call的实现
Function.prototype.call = function (context, ...args) {
var context = context || window;
context.fn = this;
var result = eval('context.fn(...args)');
delete context.fn
return result;
}
Function.prototype.apply = function (context, args) {
let context = context || window;
context.fn = this;
let result = eval('context.fn(...args)');
delete context.fn
return result;
}
bind的实现
Function.prototype.bind = function (context, ...args) {
if (typeof this !== "function") {
throw new Error("this must be a function");
}
var self = this;
var fbound = function () {
self.apply(this instanceof self ? this : context, args.concat(Array.prototype.slice.call(arguments)));
}
if(this.prototype) {
fbound.prototype = Object.create(this.prototype);
}
return fbound;
}