Obeta

Javascript中this解析

javascript中的this总是指向一个对象,而具体的对象并不是固定的,也就是说是跟运行时的函数执行环境有关,动态绑定于函数的执行环境,而非函数被声明时的环境。

this 指向的分类

总共有以下五种:

  1. 作为对象的方法调用.
  2. 作为普通函数调用.
  3. 构造器调用.
  4. Function.prototype.callFunction.prototype.apply调用.
  5. witheval.

下面详细介绍一下各个分类的区别和使用.

1.作为对象的方法

当函数作为对象的方法调用时候,this 指向该对象.

const obj = {
	a: 1,
	getA() {
		alert(this === obj); //输出:true
		alert(this.a); //输出:1
	},
};
obj.getA();

2.普通函数

当函数不以一个对象的属性调用时候(也就是普通函数),此时的 this 总是指向全局对象,在浏览器里这个全局对象就是 window.

window.name = 'globalname';

const getName = function() {
	return this.name; //此时的this指向window
};

console.log(getName()); //输出:globalname

注意:需要指出的是在 ECMAScript5 中的 stric 模式下,这种情况的 this 被规定不会指向全局对象,而是undefined.

3.构造器

使用 new 运算符创建对象。构造器和普通函数很像,当用 new 运算符调用函数的时候,该函数会返回一个对象,通常情况下构造器里的 this 指向的是返回的对象.

const MyClass = function() {
	this.name = 'sven';
};

const obj = new MyClass();
alert(obj.name); //输出:sven

4.Function.prototype.callFunction.prototype.apply

和普通函数相比,Function.prototype.callFunction.prototype.apply可以动态的改变传入的函数的this.

const obj1 = function() {
  name:'sven',
  getName(){
    return this.name;
  }
};

const obj2 = {
  name:'anne'
};

console.log(obj1.getName());//输出sven
console.log(obj1.getName.call(obj2));//输出anne

callapply的区别就是多个参数的时候call需要一个个的传递,而apply接收的是一个参数数组.

5.witheval

witheval是很特别的一种存在,同时也很强大,但是除非你有特殊的需求,否则我不建议使用它们,下面我将会解释为何不用它们.

eval:此函数可以接受一个字符串作为参数,并将其中的内容视为好像书写时就已经存在程序中这个位置的代码一样.

function foo(str, a) {
	eval(str);
	console.log(a, b);
}

const b = 2;

foo('const b=3;', 1); //输出:1,3

严格模式下 eval 有自己的词法作用域,所以上面程序运行在严格模式下的话输出的就是1,2,且 eval 会在运行时修改书写时的作用域,如果让你的用户输入了命令就会导致各种严重的问题,总之在程序中动态生成代码的使用场景并不场见,因为它所带来的好处无法抵消性能上的损失.

with:通常被用来当做重复引用一个对象的多个属性的快捷方式,可以不需要重复引用对象本身.

const obj = {
	a: 1,
	b: 2,
	c: 3,
};

//重复引用对象obj
obj.a = 2;
obj.b = 3;
obj.c = 4;

//使用了with的快捷方式
with (obj) {
	a = 3;
	b = 4;
	c = 5;
}

//使用问题:变量泄露
with (obj) {
	d = 6;
}

console.log(obj.d); //undefined
console.log(d); //6

使用 with 有一个问题就是如果给对象赋值一个对象不存在的变量,会导致此变量泄露到全局作用域中,这是一个奇怪的副作用,因此在严格模式下 with 被完全禁止了.

使用 this 的各种问题

这里再介绍一下平时工作中常见的 this 丢失问题.

使用 this 最常见的问题就是 this 丢失了,相信你没遇到过也见过吧?第一个例子是很常见的,并且很多库都有这种设计,如果你不懂或者没见过那可能会导致一些奇怪的问题.

//例子1
//创建一个包含方法的对象
const obj = function() {
  myName: 'sven',
  getName() {
    return this.myName;
  }
};

console.log(obj.getName());//输出:sven

const getName2=obj.getName;
//将对象里的函数复制到全局作用域window里,
//这时函数里的this指向的是全局作用域
//而全局作用域里没有定义变量myName

console.log(getName2());//输出:undefined

this 总是指向运行时的当前作用域,所以当你将函数getName放到全局作用域中执行,this就指向了window,而在 nodejs 中是指向了global.

下面再延时一个实际的栗子:把document.getElementById这个长的不像话的方法名用一个短的函数来代替它,,现在赶紧去试试吧.

好了,把你的答案拿出来看看你,会不会和下面的一样.

//例子2
const getId = function(id) {
	return document.getElementById(id);
};

getId('div1');

或者你想下面这样?

//例子3
const getId = document.getElementById;
getId('div1');

如果你理解了this丢失的问题,那么你应该很快反应过来这样是不行的.

getElementById方法中使用了this,和例子 1 一样,当你将getId来引用方法getElementById后,再调用getId时候this就指向的是window,而不是原来的document.

既然知道问题所在了,那也好解决了,我们可以使用apply或者call.

document.getElementById = (function(func) {
	return function() {
		return func.apply(document, arguments);
	};
})(document.getElementById);

const getId = document.getElementById;
const div = getId('div1');

alert(div.id); //输出:div1

或者:

const getId = function() {
	return document.getElementById.apply(document, arguments);
};

alert(getId('div1')); //输出:div1

个人随笔记录,内容不保证完全正确,若需要转载,请注明作者和出处.