JS基础知识点总结-原型链、构造函数、执行上下文、作用域、闭包、this
Prototype
1.Object
Object是一个属性的集合,并且都拥有一个单独的原型对象[prototype object]. 这个原型对象[prototype object]可以是一个object或者null值。 一个Object的prototype是一个内部的[[prototype]]属性的引用。 我们会使用__<内部属性名>__ 下划线来代替双括号,例如__proto__ __proto__是一个Object的属性
2. 原型链 [原型链是一个由对象组成的有限对象链由于实现继承和共享属性]
原型对象[Prototype]也是普通的对象,并且也有可能有自己的原型,如果一个原型对象的原型不为null的话,我们就称之为原型链
var a = {
x: 10,
calculate: function (z) {
return this.x + this.y + z
}
};
var b = {
y: 20,
__proto__: a
};
var c = {
y: 30,
__proto__: a
};
如果一个对象的prototype没有显示的声明过或定义过,那么__prototype__的默认值就是object.prototype, 而object.prototype也会有一个__prototype__, 这个就是原型链的终点了,被设置为null。
使用情况: 对象拥有 相同或相似的状态结构(same or similar state structure) (即相同的属性集合)与 不同的状态值(different state values) 此时,我们通过构造函数的来创建和使用protorype,实现继承和共享属性
3. 构造函数 [创建对象并且自动为创建的新对象设置了原型对象(prototype object) 。原型对象存放于 ConstructorFunction.prototype 属性中]
// 构造函数
function Foo(y) {
// 构造函数将会以特定模式创建对象:被创建的对象都会有"y"属性
this.y = y;
}
// "Foo.prototype"存放了新建对象的原型引用
// 所以我们可以将之用于定义继承和共享属性或方法
// 所以,和上例一样,我们有了如下代码:
// 继承属性"x"
Foo.prototype.x = 10;
// 继承方法"calculate"
Foo.prototype.calculate = function (z) {
return this.x + this.y + z;
};
// 使用foo模式创建 "b" and "c"
var b = new Foo(20);
var c = new Foo(30);
- Foo为构造函数,是windowd内对象
- new 根据Foo的prototype属性来创建对象时会实例化构造函数,创建内存, 同时创建constructor属性,指向构造函数,__proto__指向Object.prototype
- Foo 构造函数拥有prototype显示属性(即hasOwnProperpy属性返回true)和__proto__隐式属性, __proto__指向Function.prototype
- Function.prototype是继承object.prototype属性并是Foo的构造函数
- 每一个object都有一个prototype
b.__proto__ === Foo.prototype, // true
c.__proto__ === Foo.prototype, // true
b.constructor === Foo, // true
c.constructor === Foo, // true
Foo.prototype.constructor === Foo // true
b.calculate === b.__proto__.calculate, // true
b.__proto__.calculate === Foo.prototype.calculate // true
4. 执行上下文栈
每一种代码的执行都需要依赖自身的上下文.函数的每一次调用,都会进入函数执行中的上下文,并且来计算函数中变量等的值
5. 执行上下文
一个执行的上下文可以抽象的理解为object。每一个执行的上下文都有一系列的属性(我们称为上下文状态),他们用来追踪关联代码的执行进度。
6. 变量对象[活动对象]
变量对象(variable object) 是与执行上下文相关的 数据作用域(scope of data) 。
它是与上下文关联的特殊对象,用于存储被定义在上下文中的 变量(variables) 和 函数声明.
变量对象(Variable Object)是一个抽象的概念,不同的上下文中,它表示使用不同的object。ECMAScript和其他语言相比(比如C/C++),仅有函数能够创建新的作用域。在函数内部定义的变量与内部函数,在外部非直接可见并且不污染全局对象
7. 活动对象
当函数被调用者激活,这个特殊的活动对象(activation object) 就被创建了。它包含普通参数(formal parameters) 与特殊参数(arguments)对象(具有索引属性的参数映射表)。活动对象在函数上下文中作为变量对象使用。即:函数的变量对象保持不变,但除去存储变量与函数声明之外,还包含以及特殊对象arguments 。在ECMAScript中,我们会用到内部函数[inner functions],在这些内部函数中,我们可能会引用它的父函数变量,或者全局的变量。我们把这些变量对象成为上下文作用域对象[scope object of the context]. 类似于上面讨论的原型链[prototype chain],我们在这里称为作用域链[scope chain]。
8. 作用域链
作用域链是一个 对象列表(list of objects) ,用以检索上下文代码中出现的 标识符(identifiers).作用域链的原理和原型链很类似,如果这个变量在自己的作用域中没有,那么它会寻找父级的,直到最顶层。
标示符[Identifiers]可以理解为变量名称、函数声明和普通参数。例如,当一个函数在自身函数体内需要引用一个变量,但是这个变量并没有在函数内部声明(或者也不是某个参数名),那么这个变量就可以称为自由变量[free variable]。那么我们搜寻这些自由变量就需要用到作用域链。
一个作用域链包括父级变量对象(variable object)(作用域链的顶部)、函数自身变量VO和活动对象(activation object)
当查找标识符的时候,会从作用域链的活动对象部分开始查找,然后(如果标识符没有在活动对象中找到)查找作用域链的顶部,循环往复,就像作用域链那样
var x = 10;
(function foo() {
var y = 20;
(function bar() {
var z = 30;
// "x"和"y"是自由变量
// 会在作用域链的下一个对象中找到(函数”bar”的互动对象之后)
console.log(x + y + z);
})();
})();
当一个上下文终止之后,其状态与自身将会被 销毁(destroyed) ,同时内部函数将会从外部函数中返回。此外,这个返回的函数之后可能会在其他的上下文中被激活,那么如果一个之前被终止的含有一些自由变量的上下文又被激活将会成为闭包
9. 闭包(Closures) [闭包是一系列代码块(在ECMAScript中是函数),并且静态保存所有父级的作用域。通过这些保存的作用域来搜寻到函数中的自由变量。]
函数可以作为参数被传递给其他函数使用,两个概念上的问题 [funarg problem]
向上查找的函数参数问题: 当一个函数从其他函数返回到外部的时候,这个问题将会出现。要能够在外部上下文结束时,进入外部上下文的变量,内部函数 在创建的时候(at creation moment) 需要将之存储进 [[Scope]]属性 的 父元素的作用域中。然后当函数被激活时,上下文的作用域链表现为激活对象与[[Scope]]属性的组合 作用域链 = 活动对象 + [[Scope]]
function foo() {
var x = 10;
return function bar() {
console.log(x);
};
}
// "foo"返回的也是一个function
// 并且这个返回的function可以随意使用内部的变量x
var returnedFunction = foo();
// 全局变量 "x"
var x = 20;
// 支持返回的function
returnedFunction(); // 结果是10而不是20
这种形式的作用域称为静态作用域[static/lexical scope]。上面的x变量就是在函数bar的[[Scope]]中搜寻到的。理论上来说,也会有动态作用域[dynamic scope], 也就是上述的x被解释为20,而不是10. 但是EMCAScript不使用动态作用域。
自上而下[”downward funarg problem”]: 在这种情况下,父级的上下会存在即: 保留父级,但是在判断一个变量值的时多义性。也就是,这个变量究竟应该使用哪个作用域。是在函数创建时的作用域呢,还是在执行时的作用域呢?为了避免这种多义性,可以采用闭包,也就是使用静态作用域。
// 全局变量 "x"
var x = 10;
// 全局function
function foo() {
console.log(x);
}
(function (funArg) {
// 局部变量 "x"
var x = 20;
// 这不会有歧义
// 因为我们使用"foo"函数的[[Scope]]里保存的全局变量"x",
// 并不是caller作用域的"x"
funArg(); // 10, 而不是20
})(foo); // 将foo作为一个"funarg"传递下去
- 几个函数可能含有相同的父级作用域(这是一个很普遍的情况,例如有好几个内部或者全局的函数)。在这种情况下,在[[Scope]]中存在的变量是会共享的。一个闭包中变量的变化,也会影响另一个闭包的
function baz() {
var x = 1;
return {
foo: function foo() { return ++x; },
bar: function bar() { return --x; }
};
}
var closures = baz();
console.log(
closures.foo(), // 2
closures.bar() // 1
);
- 在某个循环中创建多个函数时,上图会引发一个困惑。如果在创建的函数中使用循环变量(如”k”),那么所有的函数都使用同样的循环变量,导致一些程序员经常会得不到预期值。现在清楚为什么会产生如此问题了——因为所有函数共享同一个[[Scope]],其中循环变量为最后一次复赋值。
var data = [];
for (var k = 0; k < 3; k++) {
data[k] = function () {
alert(k);
};
}
data[0](); // 3, but not 0
data[1](); // 3, but not 1
data[2](); // 3, but not 2
var data = [];
for (var k = 0; k < 3; k++) {
data[k] = (function (x) {
return function () {
alert(x);
};
})(k); // 将k当做参数传递进去
}
// 结果正确
data[0](); // 0
data[1](); // 1
data[2](); // 2
10. This指针 [this适合执行的上下文环境息息相关的一个特殊对象。因此,它也可以称为上下文对象context object]
当你在代码中使用了this,这个 this的值就直接从执行的上下文中获取了,而不会从作用域链中搜寻。this的值只取决中进入上下文时的情况。在函数上下文[function context]中,this会可能会根据每次的函数调用而成为不同的值.this会由每一次caller提供,caller是通过 调用表达式[call expression] 产生的也就是这个函数如何被激活调用的)
// "foo"函数里的alert没有改变
// 但每次激活调用的时候this是不同的
function foo() {
alert(this);
}
// caller 激活 "foo"这个callee,
// 并且提供"this"给这个 callee
foo(); // 全局对象
foo.prototype.constructor(); // foo.prototype
var bar = {
baz: foo
};
bar.baz(); // bar
(bar.baz)(); // also bar
(bar.baz = bar.baz)(); // 这是一个全局对象
(bar.baz, bar.baz)(); // 也是全局对象
(false || bar.baz)(); // 也是全局对象
var otherFoo = bar.baz;
otherFoo(); // 还是全局对象