1.为什么要用this
this提供了一种更优雅的方式来隐式“传递”一个对象引用,更加简洁并且易于复用。
var me = {
name:'Kyle'
}
function identtify(){
return this.name;
}
identtify.call(me);// Kyle
// 如果不使用this,需要给identtify()显示传入一个上下文对象
var me = {
name:'Kyle'
}
function identtify(context){
return context.name;
}
identtify(me);// Kyle
2.this两种误解
2.1 指向自身
console.log()打印了4次,说明foo()被调用了4次,但是为啥foo.count === 0? 执行foo.count = 0时候,向函数对象foo添加了一个属性count。但是函数内部代码this.count中的this并不是指向函数对象,所以虽然属性名相同,根对象并不相同。其实this.count无意中创建了一个全局变量count,它的值为NaN。
function foo(num) {
console.log('foo:',num);
// 记录foo被调用的次数
this.count++;
}
foo.count = 0;
for (var index = 0; index < 10; index++) {
if (index > 5) {
foo(index)
}
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9
// foo被调用多少次
console.log(foo.count);// 0
如果要从函数对象内部引用它自身,有下面两种方法:
// 第一种,通过一个指向函数对象的词法标识符(变量)来引用它
function foo(num) {
console.log('foo:',num);
// 记录foo被调用的次数
foo.count++;
}
foo.count = 0;
for (var index = 0; index < 10; index++) {
if (index > 5) {
foo(index)
}
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9
// foo被调用多少次
console.log(foo.count);// 4
// 第二种,强制this指向foo函数对象
function foo(num) {
console.log('foo:',num);
// 记录foo被调用的次数
this.count++;
}
foo.count = 0;
for (var index = 0; index < 10; index++) {
if (index > 5) {
foo.call(foo,index)
}
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9
// foo被调用多少次
console.log(foo.count);// 4
2.2 它的作用域
在某种情况下,它是正确的,但是在其他情况下他却是错误的。但是需要明确的是,this在任何情况下都不指向函数的词法作用域。 这段代码试图通过this.bar()来引用bar函数。这是不可能成功;此外还试图使用this联通foo()和bar()的词法作用域,从而让bar()可以访问foo()作用域里的变量a,这也是不可能实现的,不能使用this来引用一个词法作用域内部的东西。
function foo() {
var a = 2;
this.bar();
}
function bar() {
console.log(this.a);
}
foo();// this.bar is not a function
3.this到底是什么?到底是一种什么样的机制?
this是在运行的时候进行绑定的,并不是编写时候绑定的。this的绑定和函数声明的位置没有任何关系,this的指向完全取决于函数的“调用位置”。
4.调用位置
“调用位置”就是函数在代码中被调用的位置,而不是声明位置。如何找到“函数被调用的位置”?需要分析“调用栈”(就是为了到达当前执行位置所调用的所有函数),“调用位置”就是在当前正在执行的函数的前一个调用中。
function baz() {
// 当前调用栈:baz
// 当前调用位置:全局作用域
console.log('baz');
bar();// bar的调用位置
}
function bar() {
// 当前调用栈:baz -> bar
// 当前调用位置:在baz中
console.log('bar');
foo();// foo的调用位置
}
function foo() {
// 当前调用栈:baz -> bar -> foo
// 当前调用位置:在bar中
console.log('foo');
}
baz();// baz的调用位置
5.绑定规则
函数在执行过程中调用位置如何决定this的绑定对象?首先需要找到调用位置,然后判断符合下面四条规则中的那一条(如果符合多条规则,那么是有优先级的,后面会谈到这点)。
5.1 默认绑定
在本例中,函数调用时应用了this的默认绑定,因此this指向全局对象。通过分析调用位置来看,foo()是直接使用不带任何修饰的函数引用进行调用的,因此只能使用默认绑定。
function foo() {
console.log(this.a);
}
var a = 2;
foo();// 2
// 注意:如果使用严格模式,那么全局对象无法使用默认绑定,因为this会绑定到undefined
function foo() {
// "use strict"
console.log(this.a);
}
var a = 2;
foo();// this is undefined
5.2 隐式绑定
隐式绑定需要考虑调用位置是否有上下文对象。
当foo()被调用时,函数引用有上下文对象obj,根据隐式绑定规则,会把函数调用中的this绑定到这个上下文对象,因此this.a 等价于 obj.a。
function foo() {
console.log(this.a);
}
var obj = {
a:2,
foo:foo
}
obj.foo();// 2
// 注意:对象属性引用链中只有最后一层会影响调用位置
function foo() {
console.log(this.a);
}
var obj2 = {
a:42,
foo:foo
}
var obj1 = {
a:2,
obj2:obj2
}
obj1.obj2.foo();// 42
隐式丢失:被隐式绑定的函数会丢失绑定对象,从而应用默认绑定,因此this绑定到了全局对象或undefined(严格模式)
在本例中,虽然bar()是obj.foo的一个引用,但是它引用的是foo函数本身,因此此时bar()其实是一个不带任何修饰的函数调用,因此应用了默认绑定。
function foo() {
console.log(this.a);
}
var obj = {
a:2,
foo:foo
}
var bar = obj.foo;// 函数别名
var a = 3;// a是全局对象的属性
bar()// 3
// 发生在回调函数时的隐式丢失(参数传递其实就是一种隐式赋值,故结果和上面一样)
function foo() {
console.log(this.a);
}
function doFoo(fun) {
// fun其实引用的是foo
fun();
}
var obj = {
a:2,
foo:foo
}
var a = 3;
doFoo(obj.foo); // 3
5.3显示绑定
call(...)和apply(...)方法,它们的第一个参数是一个对象,它们会把这个对象绑定到this,接着在调用函数时指定这个this。(它们的区别自行百度)
// 通过foo.call(...),我们可以在调用foo时强制把它的this绑定到obj
function foo() {
console.log(this.a);
}
var obj = {
a:2
}
foo.call(obj);// 2
但是,显示绑定仍然无法解决前面说的丢失绑定问题,因此有下面这种方式:
5.3.1硬绑定
应用场景:创建一个包裹函数,传入所有参数并返回接收到的所有值
// 通过foo.apply(obj,arguments);强制把foo的this绑定到了obj。无论之后如何调用函数bar,它总会手动在obj上调用foo。
function foo(b) {
console.log(this.a,b);
}
var obj = {
a:2
}
var bar = function () {
return foo.apply(obj,arguments);
}
bar(3); // 2 3
// 因为硬绑定是一种常用模式,在ES5中提供内置方法 Function.prototype.bind,bind(...)会返回一个硬绑定的新函数,用法如下:
function foo(b) {
console.log(this.a,b);
}
var obj = {
a:2
}
var bar = foo.bind(obj);
bar(3); // 2 3
5.4new 绑定
前言:在Js中,构造函数只是一些使用new操作符时被调用的函数。他们并不属于某个类,也不会实例化一个类,它们只是被new操作符调用的普通函数而已。(实际上并不存在所谓"构造函数",只有对于函数的"构造调用")。
使用new来调用函数,或说发生构造函数调用时候,会执行下面操作:
. 创建一个全新的对象
. 新对象会被执行[[原型]]连接
. 这个新对象会被绑定到函数调用的this
. 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象
// 使用new 来调用foo(...)时,生成一个新对象,并把新对象绑定到foo(...)中调用的this,这种绑定称之为new绑定
function foo(a) {
this.a = a;
}
var bar = new foo(2);
console.log(bar.a);//2
6 优先级
6.1 默认绑定的优先级最低
6.2 显示绑定 > 隐式绑定
function foo(a) {
console.log(this.a);
}
var obj1 = {
a:2,
foo:foo
}
var obj2 = {
a:3,
foo:foo
}
// 显而易见,显示绑定 > 隐式绑定
obj1.foo.call(obj2);// 3
obj2.foo.call(obj1);// 2
6.3 new绑定 > 隐式绑定
function foo(a) {
this.a = a;
}
var obj = {
foo:foo
}
obj.foo(2);
console.log(obj.a);// 2
var bar = new obj.foo(4);
console.log(obj.a);//2
// 显而易见,new绑定 > 隐式绑定
console.log(bar.a);//4
6.4 new绑定 > 显示绑定
new 和 call/apply无法一起使用,不能通过new foo.call(obj)来直接测试,这里使用硬绑定来测试他们的优先级。
function foo(a) {
this.a = a;
}
var obj = {}
var bar = foo.bind(obj);
bar(2);
console.log(obj.a);//2
var baz = new bar(3);
console.log(obj.a);//2
// 出乎意料,前面讲过foo(...)通过bind硬绑定后,foo(...)中this会被绑定到指定的对象,并返回一个新函数bar(...)。无论之后如何调用函数bar,它总会手动在指定的对象上调用foo。
// 但是,为何这里通过new 来调用bar的时候,this被重新绑定了呢?
// 原因:在ES5中内置的Function.prototype.bind(...)中是会判断硬绑定函数是否被new调用,如果是的话就是使用新创建的this替换硬绑定的this
console.log(baz.a);//3
总结:new绑定 > 显示绑定 > 隐式绑定 > 默认绑定
7.规则总有例外
7.1被忽略的this
如果把null或undefined作为this的绑定对象传入call,apply,bind,这些值在调用时会被忽略,实际应用默认绑定规则:
function foo(a) {
console.log(this.a);
}
var a = 2;
foo.call(null);//2
7.2间接引用
创建一个函数的“间接引用”,在这种情况下会应用默认绑定规则
function foo(a) {
console.log(this.a);
}
var a = 2;
var obj1 = {
a:3,
foo:foo
}
var obj2 ={
a:4
}
obj1.foo();// 3
(obj2.foo = obj1.foo)();//2
8.ES6中的箭头函数
箭头函数不使用this的四种规则标准,而是根据外层(函数或全局)作用域来决定this
function foo() {
return (a)=>{
//this继承foo()
console.log(this.a);
}
}
var obj1 = {
a:2
}
var obj2 ={
a:3
}
var bar = foo.call(obj1);
// foo()内部创建的箭头函数会捕获调用foo()的this。由于foo()的this绑定到obj1,bar(引用箭头函数)的this也会绑定到obj1,并且箭头函数的绑定无法被修改。(new 也不行!)
bar.call(obj2);// 2 ,不是 3
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!