Giter Site home page Giter Site logo

blog's People

Contributors

liwens avatar

Watchers

 avatar

blog's Issues

JavaScript深入之词法作用域.md

作用域

作用域是一套查找变量位置和查找范围的规则,他的作用是协助引擎找到需要的变量

如下面代码

var value = 1;
function foo() {
  console.log(value)
}
foo() //1

执行foo时,foo的作用域块中,找不到变量value, 这时候根据作用域定义的规则,向上查找,foo的上级的全局作用域,于是在这里找到了value并返回。

同时作用域规定查找变量终点是全局作用域。不管能不能找到,都要给引擎一个交代

作用域和引擎如何配合查找变量

作用域会协助引擎查找需要的变量,那么引擎怎样查询的变量呢
引擎有两种查询形式,LHS 和 RHS

L 表示左侧, R表示右侧

引用《你不知道的JavaScript》的话

当变量出现在赋值操作的左侧进行LHS查询,出现在右侧进行RHS查询

LHS查找表示为赋值操作寻找一个容器,而RHS查找只想知道容器里面装了什么

通俗点讲 赋值操作 就是 “=”号

var a = 1;

我们需要对变量a进行赋值操作,显而易见的变量a也在 “=”号左边, 对 a 就是LHS查询,赋值操作之外的查询,就是RHS查询,比如

//对value是LHS查询,对a,b是RHS查询
var value = a + b;
//这里对value是RHS查询
console.log(value)

为什么要区分LHS和RHS查询?

因为在变量还没有声明的情况下,两种查找返回的结果不一样,

LHS查询从内部作用域向上找直到全局作用域都没找到变量的情况下,引擎会帮你在全局作用域创建一个变量,作为赋值的容器(严格模式下除外,而es6 module模块化自动开启严格模式)

RHS查询在找不到的情况下会,引擎会抛出ReferenceError错误

这就是上面说的`不管能不能找到,都要给引擎一个交代

function foo() {
  console.log(a)
}
foo(); //Uncaught ReferenceError: a is not defined

function foo2() {
  console.log(a = 1)
}
foo2(); // 1

所以这就是foo2函数在不声明变量a的情况下,也能正常输入 1 的原因了

词法作用域

词法作用域(lexical scoping),也叫静态作用域。 顾名思义静态作用域是相对静止的。 函数的作用域在定义时候就决定了。

我们来看下面代码

var value = 1;
function bar() {
  var value = 2;
  function foo() {
      console.log(value);
  }
  foo();
}
bar(); //2

我们看到,foo会输出一个2,因为它定义在了bar的内部,所以foo的作用域是 foo => bar => 全局作用域。向上查找的过程中,引擎在bar作用域块发现了变量value,于是返回给了引擎,查找结束。

我们再来看下面一段代码

var value = 1;

function foo() {
    console.log(value);
}
function bar() {
    var value = 2;
    //在bar内部调用
    foo();
}

bar(); // 1

我们看到,就算foo是在bar内部调用的,但foo会输出一个1而不是2,原因就是 JavaScript采用词法作用域(静态作用域),函数的作用域在定义时候就决定了。 foo定义在了全局作用域中,他的作用域是 foo => 全局作用域。

动态作用域

在编程语言界中,作用域有两种工作模型, 刚刚介绍的词法作用域(静态作用域) 是最普遍的。 相对的另外一种就是动态作用域了, 如果JavaScript是采用动态作用域的,那么上一个例子输出的就是2,

动态作用域应用的语言比较少,有Bash脚本,Perl。 这里不作过多讲解

参考:你不知道的JavaScript(上卷)

JavaScript深入之执行上下文与变量对象

前言

首先,我们看下面代码, 我们提前执行了foobar.可以看到输出结果不一样, 用函数声明创建的foo能正常输出,用函数表达式创建的bar报错了

foo();// 1
bar();// Uncaught TypeError: bar is not a function

//函数声明 创建
function foo() {
	console.log(1)
}

//函数表达式 创建
var bar = function() {
	console.log(2)
}

如果有人问为什么时,你肯定能张口就来: “函数声明创建的函数会提升到作用域顶部,可以先使用后定义” 这其实没有什么错误,便于大家理解。但如果要准确的解答这个问题,就要深入到本章节的内容了

执行上下文

当 JavaScript 代码执行一段可执行代码(executable code)时,会创建对应的执行上下文(execution context)。

对于每个执行上下文,都有三个重要属性:

  • 变量对象(Variable object,VO)
  • 作用域链(Scope chain)
  • this

今天重点讲讲创建变量对象的过程。

变量对象

变量对象是与 执行上下文 相关的数据作用域,对象里面储存了我们保存的变量和函数声明。

而执行上下文 分为两种

  • 全局上下文的变量对象
  • 函数上下文的变量对象,由于和全局的稍有不同,它有另一个名称,称之为活动对象(activation object, AO)以示区分
var a = 1;
function foo(i) {
  var b = 2;
}
foo(111)

在上述代码中,执行栈中有两个上下文,全局上下文和函数foo上下文,我们用数组的模拟一下

stach = [
  globalContext,
  fooContext
]

对于全局上下文来说,VO(变量对象)大概是这样的

globalContext.VO = {
  a:undefined,
  foo: reference to function foo(){},
}

而对于函数 foo来说,AO(活动对象)是这样的

fooContext.AO {
  arguments: {
    0:111,
    length: 1
  },
  a: undefined,
  i: undefined,
  b: undefined,
}

执行过程

执行上下文的代码会分成两个阶段进行处理,分析和执行,我们也可以叫做

1.进入执行上下文

2.代码执行

进入执行上下文

当进入执行上下文时,这时候还没有执行代码,JS引擎会找出需要提升的变量和函数,并且给他们提前在内存中开辟好空间,函数的话会将整个函数存入内存中,变量只声明并且赋值为 undefined,所以在第二个阶段,也就是代码执行阶段,我们可以直接提前使用。
举个例子:

function foo(a) {
  var b = 2;
  function c() {}
  var d = function() {};

  b = 3;

}

foo(1);

在第一阶段进入执行上下文后,这时候的 AO 是:

AO = {
    arguments: {
        0: 1,
        length: 1
    },
    a: 1,
    b: undefined,
    c: reference to function c(){},
    d: undefined
}

在第二阶段 代码执行 ,这时候的AO是

AO = {
    arguments: {
        0: 1,
        length: 1
    },
    a: 1,
    b: 3,
    c: reference to function c(){},
    d: reference to FunctionExpression "d"
}

这就是一开始例子说的函数表达式会报错,而函数声明正常执行的原因了。

另外如果变量名称跟已经声明的形式参数或函数相同,则函数声明的权重高。
请看下面代码,

console.log(foo);//会打印函数

function foo(){
    console.log("foo");
}

var foo = 1;
console.log(foo);//1

可以看到,提前使用的时候,函数声明的权重高,所以第一个console打印了函数。但这样不会觉得很乱吗?

遵循先定义再使用原则

在我们的潜在意识中,代码从上到下按顺序执行更直观,也不容易出错的。变量提升反而会把人绕晕。所以谷歌建议的代码规范中也倡导使用函数表达式创建函数,而在ES6中,使用let,const等定义变量或者函数,也不会提升, 会存在 暂时性死区

函数的表现

函数提升
bar(); // 2
function bar() {
	console.log(2)
}

函数表达式
foo(); // 不管是var还是let定义,都会报错
var foo = function() {
	console.log(1)
};

变量的表现

//es5, 变量提升
console.log(foo)//undefined
var foo = 1;

//es6 暂时性死区
console.log(bar)//Uncaught ReferenceError: bar is not defined
let bar = 2;

所以es6的暂时性死区其实就是提前调用变量或函数,在进入上下文时,不会在AO中提前开辟内存。而是直接抛出ReferenceError错误

遵循先定义再使用原则能一定程度降低项目出错的可能,增加项目可维护性

补充

arguments

如果你有留意上面的示例代码,会发现有一个叫arguments的对象。引用《JavaScript权威指南》

调用函数时,会为其创建一个Arguments对象,并自动初始化局部变量arguments,指代该Arguments对象。所有作为参数传入的值都会成为Arguments对象的数组元素。

JavaScirpt深入之从原型到原型链.md

前言

原型链有几个概念

构造函数,原型,prototype,实例。

首先来说一下构造函数

构造函数

person 是一个普通的函数

function person() {}

当一个普通函数被new操作符执行的时候,那么他就是构造函数

function Person() {}
var person = new Person();
person.name = 'liwens';
console.log(person.name); // liwens

另外要注意的是,函数名Person是以大写P字母开头的。在《JavaScript 高级程序设计第三版》P145 页中提到

按照惯例,构造函数始终都应该是以一个大写字母开头,而非构造函数应该以一个小写字母开头

原型 和 prototype 的关系

原型 是什么? 答: 原型 其实是一个对象,一般以 Xxx.prototype 表示

prototype 是什么? 答: 通俗的讲 prototype 是 构造函数 和 原型 之间的链接 (专业点讲叫 委托)。

每一个对象( 函数也是对象 )在创建的时候都会通过prototype关联一个对象( null 除外 ),这个对象就是原型。

让我们用一张图表示构造函数和实例原型之间的关系:
原型链1

实例

实例就是通过 new操作符调用 构造函数 生成的对象,如下面代码的 person1 就是 实例

function Person() {}
var person1 = new Person();

实例和原型的的关系

每一个实例都会自带一个属性 __proto__,这个属性会指向实例的原型

为了证明这一点,我们可以在火狐或者谷歌中输入

function Person() {}
var person = new Person();
console.log(person.__proto__ === Person.prototype); // true

于是我们更新下关系图

原型链2

同一个构造函数生成的实例,都指向同一个原型

function Person() {

}
Person.prototype = {
  //这里需要注意的是,我们说过原型的一个对象。我们这种写法实际上是把原型指向了另外一个对象。所以需要把constructor属性指回构造函数
  constructor: Person,
  name: 'liwens';
}
var person1 = new Person();
var person2 = new Person();
console.log(person1.name) // liwens
console.log(person2.name) // liwens

constructor

上面讲到构造函数通过 prototype 指向原型,实例通过 __proto__ 指向 原型

而原型是通过 constructor 属性指回构造函数的, 这样构造函数和原型都相互关联了

为了验证这一点,我们可以尝试:

function Person() {

}
console.log(Person === Person.prototype.constructor); // true

我们再更新原型图

原型链3

综上我们已经得出:

function Person() {

}

var person = new Person();
//实例通过 __proto__ 指向 原型
console.log(person.__proto__ == Person.prototype) // true
//原型通过 constructor 指向构造函数
console.log(Person.prototype.constructor == Person) // true
// 顺便学习一个ES5的方法,可以获得对象的原型
console.log(Object.getPrototypeOf(person) === Person.prototype) // true

实例与原型 从下往上,找到为止。

读取一个实例的属性时,首先会在实例中找,如果找不到,就会根据 实例 的 __proto__ 属性去到原型中找,找不到就去原型的原型找,一直找到最顶层为止

function Person() {}

Person.prototype.name = 'liwens';

var person = new Person();

person.name = 'Daisy';
console.log(person.name); // Daisy

delete person.name;
console.log(person.name); // liwens

在这个例子中,我们给实例对象 person 添加了 name 属性,当我们打印 person.name 的时候,结果自然为 Daisy。

但是当我们删除了 person 的 name 属性时,读取 person.name,从 person 对象中找不到 name 属性就会从 person 的原型也就是 person.proto ,也就是 Person.prototype 中查找,幸运的是我们找到了 name 属性,结果为 Kevin。

但是万一还没有找到呢?原型的原型又是什么呢?

原型的原型

其实原型也是实例,他也有__proto__ 属性,我们可以输出看一下原型的原型是什么

function Person() {

}
console.log(Person.prototype)

原型链2

可以看到,原型对象就是通过 Object 构造函数生成的,在 JavaScript 中, Object 是一切对象的源头
,所以我们再更新下关系图:

原型链2

原型链

那 Object.prototype 的原型呢?

null,我们可以打印:
console.log(Object.prototype.__proto__ === null) // true
然而 null 究竟代表了什么呢?

引用阮一峰老师的 《undefined 与 null 的区别》 就是:

null 表示“没有对象”,即该处不应该有值。
所以 Object.prototype.proto 的值为 null 跟 Object.prototype 没有原型,其实表达了一个意思。

所以查找属性的时候查到 Object.prototype 就可以停止查找了。

最后一张关系图也可以更新为:

原型链2

顺便还要说一下,图中由相互关联的原型组成的链状结构就是原型链,也就是蓝色的这条线。

es6 class 写法

我们通篇介绍的原型链都是 es5 的写法,这种写法跟传统的面向对象语言(比如 C++ 和 Java)差异很大,很容易让新学习这门语言的程序员感到困惑。

所以 ES6 提供了更接近传统语言的写法

//ES5 写法
function Person() {
  this.name = liwens;
}
Person.prototype.getName = function() {
  return this.name;
};

//ES6 写法
class Person {
  constructor() {
    this.name = liwens;
  }
  getName() {
    return this.name;
  }
}

可以看到 ES6 class 写法更加清晰,也有封装的感觉。但 class 写法只是语法糖,也是基于 ES5 写法封装的。 我们还是很有必要学习原型链的原理的

更多内容可以阅读阮一峰老师 ECMAScript6 入门 - Class 的基本语法

补充

最后补充几点大家可能不会注意的地方

constructor

首先是 constructor 属性,我们看个例子:

function Person() {}
var person = new Person();
console.log(person.constructor === Person); // true

当获取 person.constructor 的值时,person 中并没有 constructor 属性,这属性是原型才有的,之所以还能取得。是因为

person.constructor === Person.prototype.constructor

**_**proto_****

其次是 _proto_ ,绝大部分浏览器都支持这个非标准的方法访问原型,然而它并不存在于 Person.prototype 中,实际上,它是来自于 Object.prototype ,与其说是一个属性,不如说是一个 getter/setter,当使用 obj._proto_ 时,可以理解成返回了 Object.getPrototypeOf(obj)

真的是继承吗?

最后是关于继承,前面我们讲到“每一个对象都会从原型‘继承’属性”,实际上,继承是一个十分具有迷惑性的说法,引用《你不知道的 JavaScript》中的话,就是:

继承意味着复制操作,然而 JavaScript 默认并不会复制对象的属性,相反,JavaScript 只是在两个对象之间创建一个关联,这样,一个对象就可以通过委托访问另一个对象的属性和函数,所以与其叫继承,委托的说法反而更准确些。

总结

原型是一个对象,一般以 Xxx.prototype 表示。构造函数通过 prototype 属性指向原型,而原型通过 constructor 属性指回构造函数,实例是通过 new 操作符调用构造函数生成的对象,每一个实例都带有_proto_属性 ,这个属性指向原型。

当我们想访问一个实例的属性时,首先会在实例本身找,找到就停止搜索并返回找到的值,找不到就通过_proto_访问原型找,如果还找不到就会去原型的原型找。一直到 Object, 在 JavaScript 中,一切对象的源头都是 Object

本文 参考自 冴羽的博客,通过 读 + 实践 + 自己理解写一遍。深刻理解其中原理。避免看过就忘

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.