JS基础知识点总结-原型链、构造函数、执行上下文、作用域、闭包、this

时间:2022-10-26 14:49:32

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。

JS基础知识点总结-原型链、构造函数、执行上下文、作用域、闭包、this

   使用情况: 对象拥有 相同或相似的状态结构(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);

JS基础知识点总结-原型链、构造函数、执行上下文、作用域、闭包、this

  • 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。每一个执行的上下文都有一系列的属性(我们称为上下文状态),他们用来追踪关联代码的执行进度。

JS基础知识点总结-原型链、构造函数、执行上下文、作用域、闭包、this

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);
  })();
})();

JS基础知识点总结-原型链、构造函数、执行上下文、作用域、闭包、this

当一个上下文终止之后,其状态与自身将会被 销毁(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
);

JS基础知识点总结-原型链、构造函数、执行上下文、作用域、闭包、this

  • 在某个循环中创建多个函数时,上图会引发一个困惑。如果在创建的函数中使用循环变量(如”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(); // 还是全局对象