1. 什么是作用域链?
作用域,即变量(变量作用域又称上下文)和函数生效(能被访问)的区域或集合
换句话说,作用域决定了代码区块中变量和其他资源的可见性
1. 作用域分类
一般将作用域分成:
- 全局作用域
- 函数作用域
- 块级作用域
全局作用域
任何不在函数中或是大括号中声明的变量,都是在全局作用域下,全局作用域下声明的变量可以在程序的任意位置访问
// 全局变量
var func= 'Hello World!';
function fun() {
console.log(func);
}
// 打印 'Hello World!'
fun();
函数作用域
函数作用域也叫局部作用域,如果一个变量是在函数内部声明的它就在一个函数作用域下面。这些变量只能在函数内部访问,不能在函数以外去访问
function greet() {
var greeting = 'Hello World!';
console.log(greeting);
}
// 打印 'Hello World!'
greet();
// 报错: Uncaught ReferenceError: greeting is not defined
console.log(greeting);
在函数内部声明的变量或函数,在函数外部是无法访问的,这说明在函数内部定义的变量或者方法只是函数作用域
块级作用域
ES6引入了let和const关键字,和var关键字不同,在大括号中使用let和const声明的变量存在于块级作用域中。在大括号之外不能访问这些变量
{
// 块级作用域中的变量
let greeting = 'Hello World!';
var lang = 'English';
console.log(greeting); // Prints 'Hello World!'
}
// 变量 'English'
console.log(lang);
// 报错:Uncaught ReferenceError: greeting is not defined
console.log(greeting);
2.词法作用域
词法作用域,又叫静态作用域,变量被创建时就确定好了,而非执行阶段确定的。也就是说我们写好代码时它的作用域就确定了,JavaScript 遵循的就是词法作用域
var a = 1;
function foo(){
console.log(a)
}
function bar(){
var a = 2;
foo();
}
bar()
由于js遵循词法作用域,相同层级的 foo 和 bar 就没有办法访问到彼此块作用域中的变量,所以输出1
3. 作用域链
当在Javascript中使用一个变量的时候,首先Javascript引擎会尝试在当前作用域下去寻找该变量,如果没找到,再到它的上层作用域寻找,以此类推直到找到该变量或是已经到了全局作用域
如果在全局作用域里仍然找不到该变量,它就会在全局范围内隐式声明该变量(非严格模式下)或是直接报错
用《你不知道的Javascript(上)》中的一张图解释:
把作用域比喻成一个建筑,这份建筑代表程序中的嵌套作用域链,第一层代表当前的执行作用域,顶层代表全局作用域 变量的引用会顺着当前楼层进行查找,如果找不到,则会往上一层找,一旦到达顶层,查找的过程都会停止
var x = 10;
function foo() {
var y = 15;
function bar() {
var z = 20;
console.log(x + y + z);
};
bar()
};
foo();
此时作用域链有三级,第一级为bar AO,第二级为foo AO,然后Global Object(VO)
scope -> bar.AO -> foo.AO -> Global Object
2. this是什么?绑定规则是什么?优先级是怎样的?
1.this的定义
this 关键字是函数运行时自动生成的一个内部对象,只能在函数内部使用,总指向调用它的对象
同时,this在函数执行过程中,this一旦被确定了,就不可以再更改
在绝大多数情况下,函数的调用方式决定了 this 的值(运行时绑定)
2.绑定规则
根据不同的使用场合,this有不同的值,主要分为下面几种情况:
- 默认绑定
- 隐式绑定
- new绑定
- 显示绑定
默认绑定
全局环境中定义person函数,内部使用this关键字
var name = 'Jenny';
function person() {
return this.name;
}
console.log(person()); //Jenny
注:严格模式下,不能将全局对象用于默认绑定,this会绑定到undefined,只有函数运行在非严格模式下,默认绑定才能绑定到全局对象
隐式绑定
函数还可以作为某个对象的方法调用,这时this就指这个上级对象
function test() {
console.log(this.x);
}
var obj = {};
obj.x = 1;
obj.m = test;
obj.m(); // 1
new绑定
通过构建函数new关键字生成一个实例对象,此时this指向这个实例对象
function test() {
this.x = 1;
}
var obj = new test();
obj.x // 1
之所以能过输出1,是因为new关键字改变了this的指向
new过程遇到return一个对象,此时this指向为返回的对象
function fn()
{
this.user = 'xxx';
return {};
}
var a = new fn();
console.log(a.user); //undefined
显示修改
apply()、call()、bind()是函数的一个方法,作用是改变函数的调用对象。它的第一个参数就表示改变后的调用这个函数的对象。因此,这时this指的就是这第一个参数
var x = 0;
function test() {
console.log(this.x);
}
var obj = {};
obj.x = 1;
obj.m = test;
obj.m.apply(obj) // 1
3.优先级
new绑定优先级 > 显示绑定优先级 > 隐式绑定优先级 > 默认绑定优先级
隐式绑定 VS 显式绑定
function foo() {
console.log( this.a );
}
var obj1 = {
x: 1,
foo: foo
};
var obj2 = {
x: 3,
foo: foo
};
obj1.foo(); // 1
obj2.foo(); // 3
obj1.foo.call( obj2 ); // 3
obj2.foo.call( obj1 ); // 1
new绑定 VS 隐式绑定
function foo(o) {
this.x = o;
}
var obj1 = {
foo: foo
};
var obj2 = {};
obj1.foo( 2 );
console.log( obj1.x ); // 2
obj1.foo.call( obj2, 3 );
console.log( obj2.x ); // 3
var bar = new obj1.foo( 4 );
console.log( obj1.x ); // 2
console.log( bar.x ); // 4
new绑定的优先级>隐式绑定