# 作用域、this

# this指向总结

普通函数中的this:

  • this总是代表它的直接调用者, 例如 obj.func ,那么func中的this就是obj
  • 在非严格模式下,没找到直接调用者,则this指的是 window
  • 在严格模式下,没有直接调用者的函数中的this是 undefined,es6默认开启了严格模式,所以在这种情况下就是undefined

箭头函数中的 this 只取决包裹箭头函数的第一个普通函数的 this.

不管我们给函数 bind 几次,fn 中的 this 永远由第一次 bind 决定,如果第一个参数为空,那么就是 window。

看题

function foo() {
  console.log(this.a)
}
var a = 1
foo()   // 1

const obj = {
  a: 2,
  foo: foo
}
obj.foo()  // 2

const c = new foo()  // undefined
1
2
3
4
5
6
7
8
9
10
11
12
13
  • 对于直接调用 foo 来说,不管 foo 函数被放在了什么地方,this 一定是 window
  • 对于 obj.foo() 来说,我们只需要记住,谁调用了函数,谁就是 this,所以在这个场景下 foo 函数中的 this 就是 obj 对象
  • 对于 new 的方式来说,this 被永远绑定在了 c 上面,不会被任何方式改变 this
let a = {}
let fn = function () { console.log(this) }
fn.bind().bind(a)() // window
1
2
3

# 优先级

首先,new 的方式优先级最高,接下来是 bind 这些函数,然后是 obj.foo() 这种调用方式,最后是 foo 这种调用方式,同时,箭头函数的 this 一旦被绑定,就不会再被任何方式所改变。

image

# 1、下面的输出结果是?

function A(name) {
  this.name = name || 'Tom'
  this.msg = "use 'this.' set in function"
}

function B() {};
B.prototype = A;

var b = new B();
console.log(b.name);  // A,输出的是函数A的名字
console.log(b.msg);  // undefined
1
2
3
4
5
6
7
8
9
10
11

分析:A里面,this.name和this.msg是实例上才会有的属性。B.prototype = A ,A没有经过new,因此不会有name和msg。

b.name返回 A,是因为b上面没有name属性,他就会沿着原型链向上查找,然而 b.proto 为函数A,每一个函数都有一个属性为name,其值是函数的名字。

function abc() { /* 这是一个名为'abc'的函数 */ }
abc.name // 'abc'
1
2

# 2、分别写出以下答案

function Foo() {
    getName = function() {
        console.log(1);
    };
    return this;
}
Foo.getName = function() {
    console.log(2);
};
Foo.prototype.getName = function() {
    console.log(3);
};
var getName = function() {
    console.log(4);
};

function getName() {
    console.log(5);
}

//请写出以下输出结果:
Foo.getName();      //-> 2    Foo对象上的getName() ,这里不会是3,因为只有Foo的实例对象才会是3,Foo上面是没有3的
getName();          //-> 4    window上的getName,console.log(5)的那个函数提升后,在console.log(4)的那里被重新赋值
Foo().getName();    //-> 1    在Foo函数中,getName是全局的getName,覆盖后输出 1
getName();          //-> 1    window中getName()
new Foo.getName();  //-> 2    Foo后面不带括号而直接 '.',那么点的优先级会比new的高,所以把 Foo.getName 作为构造函数
new Foo().getName();//-> 3    new Foo()会先执行,此时是Foo的实例,会调用原型上的getName方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 3、箭头函数里面的this

箭头函数体内的this对象,就是定义该函数时所在的作用域指向的对象,而不是使用时所在的作用域指向的对象。作用域是指函数内部,这里的箭头函数,也就是c,所在的作用域其实是最外层的js环境,因为没有其他函数包裹;然后最外层的js环境指向的对象是winodw对象,所以这里的this指向的是window对象。

let a = {
  b: function() { 
    console.log(this) 
  },
  c: () => {
    console.log(this)
  }
}

a.b()   // a
a.c()   // window

let d = a.b
d()     // window
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 4、this判断下面输出为多少?

var name1 = 1;

function test() {
    let name1 = 'kin';
    let a = {
        name1: 'jack',
        fn: () => {
            var name1 = 'black'
            console.log(this.name1)
        }
    }
    return a;
}

test().fn() // ?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

答案: 输出1

因为fn处绑定的是箭头函数,箭头函数并不创建this,它只会从自己的作用域链的上一层继承this。这里它的上一层是test(),非严格模式下test中this值为window。

  • 如果在绑定fn的时候使用了function,那么答案会是 'jack'
  • 如果第一行的 var 改为了 let,那么答案会是 undefind, 因为let不会挂到window上

# 5、下面程序的输出结果是?

var length = 10;
function fn() {
	console.log(this.length);
}

var obj = {
  length: 5,
  method: function(fn) {
    fn();
    arguments[0]();
  }
};

obj.method(fn, 1);
1
2
3
4
5
6
7
8
9
10
11
12
13
14

output:

10
2
1
2

解析:分析下在method(fn,1)执行时,经历了什么:

首先两个参数fn和1会被放入arguments中,在arguments中第一个参数就是我们传入的函数;接下来fn执行,此时this没有绑定因此指向window,输出10。

然而到了arguments0这一句,相当于把arguments[0]中的第一个参数拿来执行, 效果如下:

arguments[0] ()  //执行,等同于下面的

arguments.0() //当然这句话是不合法的,但是这样我们可以更清楚知道,this是指向arguments实例本身
1
2
3

arguments.length就是它本身的长度(arguments是一个类数组,具有length属性),因此输出2。

# 6、判断输出结果,并且解释原因?

var a = 1;        
(function a () {            
    a = 2;            
    console.log(a);        
})();
// 输出结果
ƒ a () {
   a = 2;
   console.log(a);
}
1
2
3
4
5
6
7
8
9
10

立即调用的函数表达式(IIFE) 有一个 自己独立的 作用域,如果函数名称与内部变量名称冲突,就会永远执行函数本身;所以上面的结果输出是函数本身;

# 7、下面代码中 a 在什么情况下会打印 1

var a = ?;
if(a == 1 && a == 2 && a == 3){
 	console.log(1);
}
1
2
3
4

解法1:利用 toString

let a = {
  i: 1,
  toString () {
    return a.i++
  }
}

if(a == 1 && a == 2 && a == 3) {
  console.log(1);
}
1
2
3
4
5
6
7
8
9
10

解法2:利用 valueOf

let a = {
  i: 1,
  valueOf () {
    return a.i++
  }
}

if(a == 1 && a == 2 && a == 3) {
  console.log(1);
}
1
2
3
4
5
6
7
8
9
10

解法3:数组这个就有点妖了

var a = [1,2,3];
a.join = a.shift;
if(a == 1 && a == 2 && a == 3) {
  console.log(1);
}
1
2
3
4
5

解法4:ES6的symbol

let a = {
    [Symbol.toPrimitive]: (i => () => ++i) (0)
};
if(a == 1 && a == 2 && a == 3) {
  console.log(1);
}
1
2
3
4
5
6

解法5:Object.defineProperty

Object.defineProperty(window, 'a', {
    get: function() {
          return this.value = this.value ? (this.value += 1) : 1;
    }
});
if(a == 1 && a == 2 && a == 3) {
  console.log(1);
}
1
2
3
4
5
6
7
8
Last Updated: 3/22/2020, 3:45:51 PM