JS词法分析

JS是弱类型语言,所谓弱类型只是表明该语言在表达式运算中不强制校验运算元的数据类型,而并不表明该语言是否具有类型系统。一般而言,JS的变量可从作用域角度分为全局变量和局部变量。

任何程序设计语言都有作用域(scope)的概念,简单来说,作用域就是变量与函数的可访问范围,也就是说,作用域控制着变量与函数的可见性生命周期。换句话说,函数的变量有其作用域,也就是引用变量时在哪个范围内查找该变量。那么这个范围又在哪里呢?这个范围其实就是活动对象(AO)。在函数调用的瞬间,会产生一个活动对象,活动对象的属性上存储着该函数所能引用到的变量。

JS执行流程

作用域

在JS中函数嵌套是非常普遍的,在函数嵌套中,对变量是如何寻找的呢?

var a = 1;
function fn(){
    var b = 2;
    function func(){
        var c = 3;
        console.log(a,b,c);// 1 2 3
    }
    func();
}

JS中对变量的寻找是由内向外,首先在函数内部寻找,若寻找不到,则逐级向外寻找直到全局(window)区域。

声明变量var的作用是什么呢?

console.log(window.a, window.b);// undefined undefined
function fn(){
    a = 1;// 未添加var表示仅仅是一个赋值操作,从fn域内向外寻找变量a直到全局(window)。
    var b = 2;
}
fn();
console.log(window.a, window.b);// 1 undefined

var 是在函数运行的上下文(EC)中,声明一个变量。为无var则仅仅表示一个赋值操作,但不要狭隘地理解为“声明了一个全局变量”。

function fn(){
    var a;
    function func(){
        a = 1;
        b = 2;
    }
    func();
}
fn();

console.log(b);// 2
// 以window.prop引用全局变量,寻找不到时则作为window的某个属性不存在返回undefined。
console.log(window.a);// undefined
// 直接使用变量引用变量,若寻找不到则直接报错。
console.log(a);// Uncaught ReferenceError: a is not defined

一个极容易出错,又非常基础的JS面试题。

var v = 'global';
function fn(){
    console.log(v);// global
    console.log(l);// undefined
    var l = 'local';
}
fn();

JS代码自上而下执行,但是JS代码整体运行分为两个阶段:词法分析期运行期。在JS代码自上而下执行前,会有一个“词法分析过程”。

var v = 'global';
function fn(){
    console.log(v);// global
    console.log(l);// Uncaught ReferenceError: l is not defined
    l = 'local';
}
fn();

词法分析

JS代码运行前,有一个类似编译的过程即词法分析,词法分析主要有3个步骤:分析函数参数、分析变量声明、分析函数声明。

function fn(func){
    console.log(func);// func(){console.log(func);}
    function func(){
        console.log(func);// func(){console.log(func);}
    }
    func();
}
fn(1);

词法分析的具体步骤:在函数运行的瞬间,会生成一个活动对象(AO, Active Object)。

  1. 分析函数参数
  • 将函数的形参添加为AO属性,属性的默认值为undefined
  • 接收函数的实参,并覆盖原属性值。
  1. 分析变量声明/分析局部变量
  • 若AO中不存在与声明的变量所对应的属性,则添加AO属性为undefined
  • 若AO中已存在与声明的变量所对应的属性,则不做任何修改。
  1. 分析函数声明
  • 若AO中存在与函数名所对应的属性,则覆盖原属性为一个函数表达式。
function fn(func){
    console.log(func);// func(){console.log(func);}
    var func = 100;
    function func(){
        console.log(func);// Uncaught TypeError: func is not a function
    }
    // func();
    console.log(func);// 100
}
fn(1);

具体分析如下

function fn(func){
    console.log(func);
}
/*分析函数参数*/
// 将函数的形参添加为AO属性,属性的默认值为undefined。
fn();// undefined
// 接收函数的实参,并覆盖原属性值。
fn(1);// 1
function fn(func){
    console.log(func);// 1
    /*分析变量声明/分析局部变量*/
    // 若AO中已存在与声明的变量所对应的属性,则不做任何修改。
    var func = 100;// 执行过程:对变量进行赋值
    console.log(func);// 100
}
fn(1);
------------------------------------------------------------------------------------------------------------
AO = {}
分析函数参数
AO = {func:undefined}
AO = {func:1}
分析局部变量
AO = {func:1}
运行阶段赋值
AO = {func:100}
function fn(func){
    console.log(func);// func(){}
    /*分析变量声明/分析局部变量*/
    // 若AO中已存在与声明的变量所对应的属性,则不做任何修改。
    var func = 100;// 执行过程:对变量进行赋值
    console.log(func);// 100
    /*分析函数声明*/
    // 若AO中存在与函数名所对应的属性,则覆盖原属性为一个函数表达式。
    function func(){

    }
    console.log(func); // 100
}
fn(1);
------------------------------------------------------------------------------------------------------------
AO = {}
分析函数参数
AO = {func:undefined}
AO = {func:1}
分析局部变量
AO = {func:1}
运行阶段赋值
AO = {func:100}
分析函数声明
AO = {func:function(){}}

函数声明与函数表达式

JS被称为“披着C外衣的List语言”,List语言是一种强大的函数式语言。函数式语言中函数是一等公民,函数可作为赋值给变量、函数也可以作为参数来传递。

// 函数声明,全局内得到了一个fn变量且值为function。
function fn(){

}
// 函数表达式,仅仅是一个变量赋值的过程,值是谁呢?
// 是右侧函数表达式返回的结果
var fn = function(){

}

在词法分析时,函数声明和函数表达式有着本质的区别。函数声明在词法分析阶段就后发挥作用。而函数表达式只有到运行阶段才会发挥作用。

// jQuery源码分析:
// 声明一个内层表达式返回值是一个函数,然后立即调用函数。
// 内层函数没有起名字,称为匿名函数。
// 这种手法中匿名函数立即执行,好处是不污染全局变量。
// 称之为立即执行匿名表达式
(function(window,undefined){
 
})(window);
// 为什么传入window而不传入undefined呢?
// 传入window是为了速度
// jQuery为了加快内部查找局部变量的速度,而直接将window以参数形式传入。
// 这样的话,window就在jQuery内部的AO上,那查找速度就会快。
// 不传入undefined时候为了安全
// 因为在IE和FF的低版本中,undefined竟然可以重新赋值,如undefined=3。
// 声明undefined局部变量,名字是undefined。同时又不传参,值自然是undefined。
// 防止外界对undefined的污染。
function(){
  function(){
    function(){
      function(){
        // document将会沿着作用域链由内向外层层上找,直到最外层的全局对象window。
        document.getElementById('sidebar');
      }
     }
  }
}

函数声明与函数表达式有什么区别呢?
ECMAScript规范规定:函数声明必须带有标识符(identifier),也就是函数名称。而函数表达式则可省略。实际上,解析器在向执行环境中加载数据时,对函数声明和函数表达式并非一视同仁。解析器会率先读取函数声明,并使其执行任何代码之前可用(可访问)。至于函数表达式,则必须等到解析器执行到它所在代码行时,才会真正被解析执行。

console.log(sum(1,2));// 3
function sum(i, j){
  return i + j;
}

代码执行前,JS解析器通过名为函数声明提升(function declaration hoisting)的过程,读取并将函数声明添加到执行环境中。对代码求值时,JS引擎也能把函数声明提升到顶部。

console.log(sum(1,2)); // Uncaught TypeError: sum is not a function
var sum = function(i,j){
  return i + j;
}

将函数声明修改为等价的函数表达式,代码执行时出现错误。原因在于函数位于一个初始化语句中,而不是一个函数声明。换句话说,在执行到函数所在的语句之前,变量sum中不会保存有对函数的引用。而且,由于错误的出现,代码也就不会向下执行了。

如何判断是函数声明还是函数表达式呢?
ECMAScript是通过上下文来区分的,如果函数作为赋值表达式的一部分的话,那它就是一个函数表达式。如果函数被包含在一个函数体内,或位于程序最顶端的话,那它就是一个函数声明。

function fn(func){
  console.log(func);// 1
  var func = 100;
  console.log(func);// 100
  // 将函数表达式赋值给变量
  func = function(){
    console.log(func);// f (){console.log(func);}
  }
  func();
  console.log(func);// f (){console.log(func);}
}
fn(1);

作用域链

作用域链就是函数由内向外所产生的活动对象(AO)的链

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容

  • 目录 1.静态作用域与动态作用域 2.变量的作用域 3.JavaScript 中变量的作用域 4.JavaScri...
    一缕殇流化隐半边冰霜阅读 7,078评论 37 113
  • 继承 一、混入式继承 二、原型继承 利用原型中的成员可以被和其相关的对象共享这一特性,可以实现继承,这种实现继承的...
    magic_pill阅读 1,049评论 0 3
  • 什么是作用域 对变量值的存储、访问、修改带给程序状态的改变编程语言的基本功能之一是能够存储变量当中的值,在之后对这...
    JunChow520阅读 618评论 0 7
  • 1,javascript 基础知识 Array对象 Array对象属性 Arrray对象方法 Date对象 Dat...
    Yuann阅读 889评论 0 1
  • 今天在朋友推荐下看了这部电影,一步完完全全的剧情片。先说说总体的客观评价,豆瓣给出了8.7分的高分,但是我还是觉得...
    是卧猫先生阅读 1,088评论 2 0