作用域
作用域是在定义的时候就创建了, 而不是运行的时候。看看下面这个例子:
let a=1
function aa(){
console.log(a) //输出1
}
function bb(){
let a=2
aa()
}
是不是非常违背常理啊,你看嘛,aa在bb里面调用的,aa函数里面没有a变量,那么就应该去调用它的作 用域里找,刚好找到a等于2。
思路是完美的,可是js的作者采用的静态作用域,不管你们怎么运行,你们 定义的时候作用域已经生成了。
那么什么是作用域?
变量和函数能被有效访问的区域或者集合。作用域决定了代码块之间的资源可访问性.
作用域又分为全局作用域和函数作用域,块级作用域。 全局作用域任何地方都可以访问到,如window,Math等全局对象。
函数作用域就是函数内部的变量和方法,函数外部是无法访问到的。 块级作用域指变量声明的代码段外是不可访问的,如let,const.
作用域链
作用域链表示一个作用域可以访问到变量的一个集合。函数作为一个对象有一个[[scope]]属性,就是表示这个集合的。再来理解几个概念词:
AO:活动变量(Active object,AO)
VO:变量对象(Variable object,VO)
执行上下文:代码运行的环境,分为全局上下文和函数上下文。
举例子来说明一下:(借用的例子)
function a() {
function b() {
var b = 234;
}
var a = 123;
b();
}
var gloab = 100;
a();
console.log(a)
第一步: a 函数定义
我们可以从上图中看到,a 函数在被定义时,a函数对象的属性[[scope]]作用域指向他的作用域链scope chain,
此时它的作用域链的第一项指向了GO(Global Object)全局对象,我们看到全局对象上此时有5个属性,分别是this、window、document、a、glob。
第二步: a 函数执行
当a函数被执行时,此时a函数对象的作用域[[scope]]的作用域链scope chain的第一项指向了AO(Activation Object)活动对象,AO对象里有4个属性,
分别是this、arguments、a、b。第二项指向了GO(Global Object),GO对象里依然有5个属性,分别是this、window、document、a、golb。
第三步: b 函数定义
当b函数被定义时,此时b函数对象的作用域[[scope]]的作用域链scope chain的第一项指向了AO(Activation Object)活动对象,AO对象里有4个属性,
分别是this、arguments、a、b。第二项指向了GO(Global Object),GO对象里依然有5个属性,分别是this、window、document、a、golb。
第四步: b 函数执行
当b函数被执行时,此时b函数对象的作用域[[scope]]的作用域链scope chain的第一项指向了AO(Activation Object)活动对象,AO对象里有3个属性,
分别是this、arguments、b。第一项指向了AO(Activation Object)活动对象,AO对象里有4个属性,分别是this、arguments、a、b。
第二项指向了GO(Global Object),GO对象里依然有5个属性,分别是this、window、document、a、golb。
** 以上就是上面代码执行完之后的结果。**
闭包
闭包的官方定义:闭包是指那些能够访问自由变量的函数。
我:一个作用域可以访问另一个作用域的变量,就产生闭包。
闭包=函数+函数能够访问的自由变量。
什么是自由变量?
自由变量是指在函数中使用的,但既不是函数参数也不是函数的局部变量的变量。
var a = 1;
function foo() {
console.log(a);
}
foo()
foo 函数可以访问变量 a,但是 a 既不是 foo 函数的局部变量,也不是 foo 函数的参数,所以 a 就是自由变量。
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f;
}
var foo = checkscope();
foo();
输出 "local scope"
因为变量查找的规则是通过作用域链的,作用域链是在函数定义的时候就已经确定了, 所以我们来看看定义f函数时候的[[scope]]属性:
[
AO:{
scope:"local scope",
f:function
},
global:{
scope :"local scope",
checkscope:function
}
]
f执行时候的[[scope]]属性:
[
AO:{
arguments:[],
this:window
},
AO:{
scope:"local scope",
f:function
},
global:{
scope :"local scope",
checkscope:function
}
]
根据先后顺序scope变量输出为"local scope"
经典面试题:
var data = [];
for (var i = 0; i < 3; i++) {
data[i] = function () {
console.log(i);
};
}
data[0]();
data[1]();
data[2]();
//答案是都是 3,让我们分析一下原因:
//当执行到 data[0] 函数之前,此时全局上下文的 VO 为:
globalContext = {
VO: {
data: [...],
i: 3
}
}
//当执行 data[0] 函数的时候,data[0] 函数的作用域链为:
data[0]Context = {
Scope: [AO, globalContext.VO]
}
//data[0]Context 的 AO 并没有 i 值,所以会从 globalContext.VO 中查找,i 为 3,所以打印的结果就是 3
原型及原型对象
javascript万物皆对象,每个对象都有一个__proto__属性,指向了创造它的构造函数的原型对象。
每个函数都有一个原型对象,prototype,当使用new 创造对象时继承这个对象。
function A(){}
var a=new A()
a.__proto__===A.prototype
下面就有问题了,谁创造了A这个构造函数呢,还有谁创造了A.prototype这个对象呢?
这时候我们就要知道js两个顶级函数,Function,Object
所有函数都是由Function创建的
A.__proto__===Function.prototype
刚说了所有函数都是由Function创建的,也包括自己。也就是说Function创造了自己:
Function.__proto__===Function.prototype
Object刚讲的是顶级函数,所以也是函数:
Object.__proto__===Function.prototype
所有的对象都是由Object构造函数创建的:
A.prototype.__proto__===Object.prototype
那么Object.prototype也是对象啊,是由谁创建的呢,记住万物皆空,何尝不是人生,到头来什么都会没有。
Object.prototype.__proto__===null
原型链
1.在访问对象的某个成员的时候会先在对象中找是否存在
2.如果当前对象中没有就在构造函数的原型对象中找
3.如果原型对象中没有找到就到原型对象的原型上找
4.直到Object的原型对象的原型是null为止