# this指向、作用域、闭包
# 1、this指向
参考资料:MDN中this解析 (opens new window)
1、对于直接调用的函数来说,不管函数被放在了什么地方,this都是window
2、对于被别人调用的函数来说,被谁点出来的,this就是谁
3、call、apply时,this是第一个参数。bind要优与call/apply哦,call参数多,apply参数少
4、在构造函数中,类中(函数体中)出现的this.xxx=xxx中的this是当前类的一个实例
5、箭头函数没有自己的this,需要看其外层的是否有函数,如果有,外层函数的this就是内部箭头函数的this,如果没有,则this是window
# 1、默认绑定(函数直接调用)
非严格模式下,默认绑定指向全局(node
中式 global
)
//1、非严格模式
function myfunc() {
console.log(this)
}
myfunc(); //window
//2、严格模式
function fn() {
'use strict' //严格模式下,禁止this关键字指向全局对象
console.log(this)
}
fn() //undefined
//面试题1
var a = 1
function fn() {
var a = 2
console.log(this.a) // console what ?
}
fn() //1
//面试题2
let a = 1 //let定义自己的作用域,不挂载至window
function fn() {
var a = 2
console.log(this.a)
}
fn() //undefined
//面试题3
var b = 1
function outer () {
var b = 2
function inner () {
console.log(this.b)
}
inner()
}
outer() //1
//面试题4
const obj = {
a: 1,
fn: function() {
console.log(this.a)
}
}
obj.fn() //1
const f = obj.fn
f() //undefined
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# 2、隐式绑定(属性访问调用,函数被别人调用)
隐式绑定的 this
指的是调用堆栈的上一级(.
前面一个)
function fn () {
console.log(this.a)
}
const obj = {
a: 1
}
obj.fn = fn
obj.fn() //1
function fn () {
console.log(this.a)
}
const obj1 = {
a: 1,
fn
}
const obj2 = {
a: 2,
obj1
}
obj2.obj1.fn() //1
//面试题:隐式绑定失败场景
//1、函数赋值
const obj1 = {
a: 1,
fn: function() {
console.log(this.a)
}
}
const fn1 = obj1.fn // 将引用给了fn1,等同于写了 function fn1() { console.log(this.a) }
fn1() //undefined
//2、setTimeout
setTimeout(obj1.fn, 1000) //undefined
//3、函数作为参数传递
function run(fn) {
fn()
}
run(obj1.fn) //undefined,传进去的是一个引用
//4、一般匿名函数也是会指向全局的
var name = 'The Window';
var obj = {
name: 'My obj',
getName: function() {
return function() { // 这是一个匿名函数
console.log(this.name)
};
}
}
obj.getName()()
//5、IIFE
(function(){
var a = 1
console.log(this.a)
})(); //undefined
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# 3、显示绑定(apply\call\bind)
apply
- func.apply(thisArg, [argsArray]),thisArg为undefined或null时指向全局
- 返回调用有指定this值和参数的函数的结果
call
- function.call(thisArg, arg1, arg2, ...)
- 返回使用调用者提供的
this
值和参数调用该函数的返回值,若该方法没有返回值,则返回undefined
。
bind
function.bind(thisArg[, arg1[, arg2[, ...]]])
- thisArg:调用绑定函数时作为
this
参数传递给目标函数的值。 如果使用new
(opens new window)运算符构造绑定函数,则忽略该值。当使用bind
在setTimeout
中创建一个函数(作为回调提供)时,作为thisArg
传递的任何原始值都将转换为object
。如果bind
函数的参数列表为空,或者thisArg
是null
或undefined
,执行作用域的this
将被视为新函数的thisArg
。 - arg1, arg2, ...:当目标函数被调用时,被预置入绑定函数的参数列表中的参数。
- thisArg:调用绑定函数时作为
返回:返回一个原函数的拷贝,并拥有指定的 this 值和初始参数。
MDN的bind实现
// Yes, it does work with `new (funcA.bind(thisArg, args))`
if (!Function.prototype.bind) (function(){
var ArrayPrototypeSlice = Array.prototype.slice; // 为了 this
Function.prototype.bind = function(otherThis) {
// 调用者必须是函数,这里的 this 指向调用者:fn.bind(ctx, ...args) / fn
if (typeof this !== 'function') {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
}
var baseArgs= ArrayPrototypeSlice.call(arguments, 1), // 取余下的参数
baseArgsLength = baseArgs.length,
fToBind = this, // 调用者
fNOP = function() {}, // 寄生组合集成需要一个中间函数,避免两次构造
fBound = function() {
// const newFn = fn.bind(ctx, 1); newFn(2) -> arguments: [1, 2]
baseArgs.length = baseArgsLength; // reset to default base arguments
baseArgs.push.apply(baseArgs, arguments); // 参数收集
return fToBind.apply( // apply 显示绑定 this
// 判断是不是 new 调用的情况,这里也说明了后边要讲的优先级问题
fNOP.prototype.isPrototypeOf(this) ? this : otherThis, baseArgs
);
};
// 下边是为了实现原型继承
if (this.prototype) { // 函数的原型指向其构造函数,构造函数的原型指向函数
// Function.prototype doesn't have a prototype property
fNOP.prototype = this.prototype; // 就是让中间函数的构造函数指向调用者的构造
}
fBound.prototype = new fNOP(); // 继承中间函数,其实这里也继承了调用者了
return fBound; // new fn()
};
})();
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
28
29
30
31
32
33
34
function fn () {
console.log(this.a)
}
const obj = {
a: 100
}
fn.call(obj) //100
function fn() {
console.log(this)
}
// 为啥可以绑定基本类型 ?
// boxing(装箱) -> (1 ----> Number(1))
// bind 只看第一个 bind(堆栈的上下文,上一个,写的顺序来看就是第一个)
fn.bind(1).bind(2)() //1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
面试题资料:手写bind\apply\call (opens new window)
# 4、new创建对象
如果函数 constructor
里没有返回对象的话,this
指向的是 new
之后得到的实例
function foo(a) {
this.a = a
}
const f = new foo(2)
f.a // 2
function bar(a) {
this.a = a
return {
a: 100
}
}
const b = new bar(3)
b.a //100
2
3
4
5
6
7
8
9
10
11
12
13
14
# 5、箭头函数
编译期间确定的上下文,不会被改变,哪怕你 new
,指向的就是上一层的上下文,箭头函数没有自己的this,需要看其外层的是否有函数,如果有,外层函数的this就是内部箭头函数的this,如果没有,则this是window
var a = {
myfunc: function() {
setTimeout(function(){
console.log(this); // this是window
}, 0)
}
};
a.myfunc(); //window
var a = {
myfunc: function() {
var that = this;
setTimeout(function(){
console.log(that); // this是a
}, 0)
}
};
a.myfunc(); //a对象:{myfunc: ƒ}
// 箭头函数
var a = {
myfunc: function() {
setTimeout(() => {
console.log(this); // this是a
}, 0)
}
};
a.myfunc(); //a对象:{myfunc: ƒ}
function fn() {
return {
b: () => {
console.log(this)
}
}
}
fn().b() //window
fn().b.bind(1)() //window
fn.bind(2)().b.bind(3)() //Number(2)
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
28
29
30
31
32
33
34
35
36
37
38
39
# 6、优先级
「new 绑」 > 「显绑」 > 「隐绑」 > 「默认绑定」
// 隐式 vs 默认 -> 结论:隐式 > 默认
function fn() {
console.log(this)
}
const obj = {
fn
}
obj.fn() //obj对象:{fn: ƒ}
// 显式 vs 隐式 -> 结论:显式 > 隐式
obj.fn.bind(5)() //5
// new vs 显式 -> 结论:new > 显式
function foo (a) {
this.a = a
}
const obj1 = {}
var bar = foo.bind(obj1)
bar(2)
console.log(obj1.a) //2
// new
var baz = new bar(3)
console.log(obj1.a) //2
console.log(baz.a) //3
// 箭头函数没有 this,比较没有意义
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
# 2、作用域
# 1、数据存储
栈(FIFO先进先出)放置静态内存,编译期分配:全局变量
堆(FILO先进后出)放置动态内存,执行期分配:函数执行上下文
# 2、执行上下文
当函数执行时,会创建一个执行上下文的环境,分为创建和执行两个阶段:
- 创建阶段:函数被调用未执行任何代码时,创建一个拥有3个属性的对象
executionContext = {
scopeChain: {}, // 创建作用域链(scope chain)
variableObject: {}, // 初始化变量、函数、形参
this: {} // 指定this
}// 初始化 VO -> 建立作用域链 -> 确定 This 上下文
2
3
4
5
- 执行阶段:分配变量(提升)、函数的引用,赋值;执行代码。
function demo(num) {
var name = 'xiaowa';
var getData = function getData() {};
function c() {}
}
demo(100);
// 创建阶段大致这样,在这个阶段就出现了【变量提升(Hoisting)】
executionContext = {
scopeChain: { ... },
variableObject: {
arguments: { // 创建了参数对象
0: 100,
length: 1
},
num: 100, // 创建形参名称,赋值/或创建引用拷贝
c: pointer to function c() // 有内部函数声明的话,创建引用指向函数体
name: undefined, // 有内部声明变量a,初始化为undefined
getData: undefined // 有内部声明变量b,初始化为undefined
},
this: { ... }
}
// 代码执行阶段,在这个阶段主要是赋值并执行代码
executionContext = {
scopeChain: { ... },
variableObject: {
arguments: {
0: 100,
length: 1
},
num: 100,
c: pointer to function c(),
name: 'xiaowa', // 分配变量,赋值
getData: pointer to function getData() // 分配函数的引用,赋值
},
this: { ... }
}
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
28
29
30
31
32
33
34
35
36
37
38
# 3、执行上下文栈
- 浏览器中的js解释器是单线程的,相对于浏览器中同一时间只能做一个事情
- 代码中只有一个全局执行上下文和无数个函数执行上下文,这些组成了执行上下文栈(Execution Stack)
- 一个函数的执行上下文,在函数执行完毕后,会被移出执行上下文栈。
function c(){
console.log('ok');
}
function a(){
function b(){
c();
}
b();
}
a();
2
3
4
5
6
7
8
9
10
11
12
# 4、作用域与作用域链
- 作用域:全局作用域、函数作用域、ES6块级作用域。作用域的最大用途就是隔离变量或函数,并控制生命周期。作用域在函数执行上下文创建时定义好的,不是函数执行是定义
- 作用域链:当一个块或函数嵌套在另一个块或函数中时,发生了作用域嵌套。在当前函数中如果无法找到某个变量,就会往上一级嵌套的作用域中去寻找,直到找到该变量或抵达全局作用域,这样的链式关系称为作用域链(Scope Chain)
- 代码执行流没进入一个新上下文(全局、函数、块级作用域),就会创建一个作用域链,用于搜索变量和函数
- 函数或块的局部上下文可以访问自己作用域内的变量,也可以访问任何包含上下文及全局上下文中的变量。
- 全局上下文只能访问全局作用域的变量和函数,无法访问局部上下文的任何数据
function a () {
return function b() {
var myname = 'b';
console.log(myname); // b
}
}
function c() {
var myname = 'c';
b();
}
var b = a();
c(); //输出b
// 去掉函数b中的myname声明后
function a () {
return function b() {
//var myname = 'b';
console.log(myname); //Uncaught ReferenceError: myname is not defined
}
}
function c() {
var myname = 'c';
b();
}
var b = a();
c();
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
28
29
30
# 5、作用域链增强
某些语句会导致在作用域链前端临时添加一个上下文,这个上下文在代码执行后会被删除。
- try/catch语句中的catch块:会创建一个新的变量对象,包含要抛出的错误对象的申明
- with语句:向作用域链前端添加制定的对象
//范例1
var a = 15, b = 15;
with( { a: 10 } ){
var a = 30, b = 30;
console.log(a); // 30
console.log(b); // 30
}
console.log(a); // 15,原值
console.log(b); // 30,b被更新
//范例2
var a = 15, b = 15;
with( { a: 10 } ){
b = 30;
console.log(a); // 10,指定对象a的值
console.log(b); // 30
}
console.log(a); // 15
console.log(b); // 30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 3、闭包
高级程序设计:闭包指有权访问另一个函数作用域中的变量的函数,可以理解为那些引用了另一个函数作用域中变量的函数,通常是在嵌套函数中实现的。
使用场景:
- 封装私有变量(AMD的框架等都使用)
//普通定义类
function Person(){
this.attackVolume = 100
}
Person.prototype = {
attack(){
this.attackVolume += 1
}
}
var person = new Person()
console.log(person.attackVolume)
//工厂方法
function Person(){
var attackVolume = 100
return {
attack(){
this.attackVolume += 1
}
}
}
var person = new Person()
console.log(person.attackVolume)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- 存储变量
function getList(){
let localData = null
return {
getData(){
if(localData){
return Promise.resolve(localData)
}
return fetch('xxx').then(res =>{
localData = res.json()
})
}
}
}
const listDataManager = getList()
window.onscroll = () => {
text.innerHTML = listDataManager.getData() //可能获取缓存数据
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 4、面试题
# 1、this指向
function foo() {
console.log( this.a )
}
var a = 2;
(function(){
"use strict"
foo();
})();//2,类似函数直接执行,"use strict"为迷惑,放在foo函数中为undefined
function foo() {
"use strict"
console.log( this.a)
}
var a = 2;
(function(){
foo();
})();//Uncaught TypeError: Cannot read property 'a' of undefined
var name="the window"
var a={
name:"My Object",
getName: function(){
return this.name
}
}
a.getName() // My Object
(a.getName)() // My Object
(a.getName = a.getName)() //the window,函数赋值后,this指向全局
(a.getName, a.getName)() //the window,函数操作后,this指向全局
var x = 3
var obj3 = {
x: 1,
getX: function() {
var x = 5
return function() {
return this.x
}();
}
}
console.log(obj3.getX()) //3,立即执行函数this指向全局
function a(x){
this.x = x
return this
}
var x = a(5)
var y = a(6)
console.log(x.x) // undefined
console.log(y.x) // 6
/* 解析
window.x = 5;
window.x = window;
window.x = 6;
window.y = window;
console.log(x.x) // void 0 其实执行的是 Number(6).x
console.log(y.x) // 6
*/
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
//隐式绑定
function show () {
console.log('this:', this); //this指向obj
}
var obj = {
show: show
};
obj.show();//this: { show: [Function: show] }
//隐式绑定失败场景
function show () {
console.log('this:', this); //this指向obj
}
var obj = {
show: function () {
show(); //类似于函数直接调用
}
};
obj.show();//this: window
var obj = {
show: function () {
console.log('this:', this);
}
};
(0, obj.show)(); //this: window,逗号表达式返回函数,函数再执行,let f = (0, obj.show), f()
//隐式绑定失败场景
var obj = {
sub: {
show: function () {
console.log('this:', this);
}
}
};
obj.sub.show() //this: {show: ƒ},sub对象,sub.出来的
//优先级与显示绑定
var obj = {
show: function () {
console.log('this:', this);
}
};
var newobj = new obj.show(); //this为newobj,输出this: show {},类似new function(){console.log('this:', this); }
var obj = {
show: function () {
console.log('this:', this);
}
};
var newobj = new (obj.show.bind(obj))(); //new优先级强,this为newobj,输出this: show {}
//事件触发
var obj = {
show: function () {
console.log('this:', this);
}
};
var elem = document.getElementById('book-search-results');
elem.addEventListener('click', obj.show); //this为elem对象
elem.addEventListener('click', obj.show.bind(obj)); //this为obj,显示绑定
elem.addEventListener('click', function () { obj.show(); }); //this为obj,执行时为obj
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# 2、作用域链
var person = 1
function showPerson(){
var person = 2;
console.log(person);
}
showPerson(); //2
var person = 1
function showPerson(){
console.log(person);
var person = 2;
}
showPerson(); //undefined
var person = 1
function showPerson(){
console.log(person);
var person = 2;
function person(){} //函数声明也可提前
}
showPerson(); //ƒ person(){}
var person = 1
function showPerson(){
console.log(person);
function person(){}
var person = 2;
}
showPerson(); //ƒ person(){},函数提升在变量提升之前
for(var i = 0; i < 10; i++) {
console.log(i);
}//0~9
for(var i = 0; i < 10; i++) {
setTimeout(function(){
console.log(i);
}, 0);
}//10~10,注意for循环结束后,i还会++
for(var i = 0; i < 10; i++) {
(function(i){
setTimeout(function(){
console.log(i);
}, 0)
})(i);
}//0~9
for(let i = 0; i < 10; i++) {
console.log(i);
}//0~9
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# 3、面向对象
function Person() {
this.name = 1;
return {};
}
var person = new Person();
console.log('name:', person.name); //name: undefined
function Person() {
this.name = 1;
}
Person.prototype = {
show: function () {
console.log('name is:', this.name);
}
};
var person = new Person();
person.show(); //name is: 1,可执行原型链的方法,作用域链从当前开始
function Person() {
this.name = 1;
}
Person.prototype = {
name: 2,
show: function () {
console.log('name is:', this.name);
}
};
var person = new Person();
Person.prototype.show = function () { //覆盖原型链上的show方法
console.log('new show');
};
person.show(); //new show
function Person() {
this.name = 1;
}
Person.prototype = {
name: 2,
show: function () {
console.log('name is:', this.name);
}
};
var person = new Person();
var person2 = new Person();
person.show = function () { //这里是person
console.log('new show');
};
person2.show(); //name is: 1
person.show(); //new show
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# 4、综合题
function Person() {
this.name = 1;
}
Person.prototype = {
name: 2,
show: function () {
console.log('name is:', this.name);
}
};
Person.prototype.show(); //name is: 2
(new Person()).show(); //name is: 1
2
3
4
5
6
7
8
9
10
11