Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

this关键字 #1

Open
ChaoYuLeo opened this issue Apr 8, 2017 · 0 comments
Open

this关键字 #1

ChaoYuLeo opened this issue Apr 8, 2017 · 0 comments

Comments

@ChaoYuLeo
Copy link
Owner

ChaoYuLeo commented Apr 8, 2017

this概要

创建函数时,系统会创建一个名为this的关键字,它链接到运行该函数的对象。this对其函数的作用域是可见的,而且它是函数属性/方法所在对象的一个引用。

首先我们看两个例子:

1.对象中的this

var cody = {
	living: true,
	age: 20,
	gender: 'male',
	getGender: function() {
		return cody.gender;
	}
};
console.log(cody.getGender()); // 输出 'male'

在这个例子中,我们使用了点表示法在cody对象上访问gender属性。

下面将使用this重写来访问cody对象

var cody = {
	living: true,
	age: 20,
	gender: 'male',
	getGender: function() {
		return this.gender; //这里使用了this
	}
};
console.log(cody.getGender()); // 输出 'male'

this.gender中使用的this只引用该函数所要操作的cody对象。

2.函数中的this

通过上一个小例子,你恐怕以为this指向对象或者函数自身,但是事实并不是这样,请看下面的例子:

function foo() {
	this.realName = "Bob";
}
foo.realName = "Leo";
foo();
console.log(foo.realName); // 输出"Leo"
console.log(window.realName) // 输出"Bob"

如果以this指向对象或者函数自身这种观点来看的话,第一行console.log应该输出"Bob"(然而却输出了"Leo"),如此看来,此时的this并没有指向函数自身。

第二行console.log(window.realName);印证了我们的想法。this并没有指向函数自身,而是指向了全局对象,因此这里的函数操作无意中创建了一个全局变量realName并且赋值为Bob

那么this的值应该怎么确定呢?

如何确定this值

在讲如何确定this值之前,我们必须先明确一个概念:this在任何情况下都不指向函数的词法作用域。this的具体指向是在运行时进行绑定的,而并不是在编写时绑定的。this的绑定和函数申明的位置没有任何关系,只取决于函数的调用方法。

明确了这一点,我们接下来就试着寻找函数的调用位置,并判断函数执行时绑定的this。

var foo = 'foo';

var obj = {foo: 'obj.foo'};

var printFoo = function() {
	console.log(this.foo);
};

printFoo(); // 输出"foo"

obj.printFoo = printFoo; //给obj对象设置一个新属性printFoo,并指向函数printFoo

obj.printFoo(); // 输出"obj.foo"

通过上面这段代码可以很明显的看出,this的值是基于函数调用的上下文的。obj.printFoo和printFoo都指向了相同的函数,但是调用printFoo的位置不同,this的值也不同。

嵌套函数中的this

一个很常见的问题就是在嵌套函数中使用this,这样嵌套函数会丢失绑定对象,指向了全局对象window,而不是定义函数所在的对象;

var obj = {
	func1: function(){
		console.log(this); // 输出 obj
		var func2 = function() {
			console.log(this); // 输出 window
		}();
	}
};

在这个例子中,我们可以很明显的看出在嵌套函数func2中,this失去了方向,没有引用obj对象,而是指向了window。

另一个常见的例子发生在使用定时器时:
将函数传入语言内置的函数中时,this也同样指向了window。

function foo(){
	console.log(this);
}

var obj = {
	a: 2,
	foo: foo
};

var a = "window.a";

setTimeout(obj.foo, 100); // 输出 "window.a"

这是一个很有意思的现象,我们可以把语言内置的setTimeout函数理解成这样一种形式:

fucntion setTimeout(fn, delay) {
	//等待delay毫秒
	fn(); // fn调用位置
}

事件绑定函数中的this

在使用事件绑定函数时,一个简单的代码如下:

var oBtn = document.getElementById("btn1");
var id = 1;
function foo() {
	console.log(this.id); // 第一个this
	oBtn.onclick = function() {
    	console.log(this.id); // 第二个this
    };
}

obj = {
	id: 2,
	foo: foo
};

foo(); // 输出 1
obj.foo(); // 输出 2

我们在html文档中创建了一个id为btn1的标签,在上面的代码中,当我们直接调用foo时,第一个this指向了window对象(输出1),而调用obj.foo是,第一个this指向了obj对象(输出2)。

而当我们点击按钮时,无论在什么位置(函数上下文)调用函数,最终输出结果都是该按钮的id,所以事件绑定函数中的this指向发生事件的元素

一个例外:间接引用

有一个需要注意的地方是,你有可能会创建一个函数的“间接引用”,在这种情况下,调用这个函数时,this将会绑定到window对象。

function foo() {
	console.log(this.a);
}

var a = 2;
var o = {a: 3, foo:foo };
var p = {a: 4};

o.foo(); // 输出 3

p.foo = o.foo;
p.foo(); // 输出 4

(p.foo = o.foo)(); // 输出 2

在上面的代码中,我们可以看到,最后一行赋值表达式 p.foo = o.foo的返回值是目标函数foo的引用,我们将这个表达式用括号包围是他成为一个函数表达式,并再之后使用()立即调用该函数,(p.foo = o.foo)()其实等价于foo不加任何修饰的直接调用,因此调用位置是foo()而不是p.foo,所以这里的this将会被绑定到window对象。

使用call()或apply()控制this的值

this值通常取决于调用函数的上下文(除了在使用new关键字时),但我们可以通过apply()和call()来控制this值,以便在函数调用时定义this指向哪个对象。

下面的代码创建了一个obj对象和一个foo函数,然后通过call()调用函数,以便函数内的this值将obj作为他的上下文。

var a = 1;	
var obj = { a:2 };

function foo() {
	console.log(this.a);
}

foo(); // 输出 1
foo.call(obj); // 输出 2

可以看到,通过foo.call(obj)这种调用方式,我们将foo函数内部的this绑定到了obj对象上,最后输出了obj的a属性,值为2。当然,如果这个函数具有参数,我们可以通过foo.call(obj, 参数1 , 参数2)的形式在绑定this指向的同时传递参数给该函数。

另一种方式是foo.apply(obj)这样的形式,apply()和call()的作用一致,区别在于传递参数的方式:如果使用call(),参数使用用逗号分隔的值;如果使用apply(),参数使用数组进行传递foo.apply(obj,[]);当然第二个参数始终是可选的。

构造函数内的this(new绑定)

刚才我们提到,除了在使用new关键字时,this值通常取决于调用函数的上下文。

使用new来调用函数时 如:var bar = new Foo() ,会自动执行以下操作:

  1. 创建(或者说构造)一个全新的对象;
  2. 这个新对象会被执行[[prototype]]连接;
  3. 这个新对象会绑定到函数调用的this;
  4. 如果这个函数没有返回其他对象,那么new表达式中的函数会自动返回这个新对象。

如果上面的过程(特别是第2步)不是很明晰也没有关系,我们看下面的代码:

function foo(a) {
	this.a = a;
}

var bar = new foo(2);

console.log(bar.a); // 输出 2

使用new来调用foo()时,我们会构造一个新对象并把它绑定到foo()调用中的this上。

有趣的小练习

经过上面的一些示例和讲解,相信你对this的绑定机制已经有了一个初步的了解,思考下面的代码,试着回答几个问题:

var c = 'o',
     fn;

var test = {
	a:{
		c:'a',
		b:function(){
			console.log(this.c);
		}
	},
	b:function(){
		console.log(this.c);
	},
	c:'i'
};		

test.a.b(); // 输出 ?
(test.a.b)();//  输出 ?
(fn = test.a.b)(); // 输出 ?
test.b(); // 输出 ?
test.b.call(test.a); // 输出 ?

1. 结合上述讲解的内容,思考最后五个语句分别输出的值各是多少?

2. 在不改变上述代码的情况下,如何调用test.b使其输出为 "n"?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant