• 程序语言 68 0 1 发布

    编程语言可以从不同的角度进行分类, 本文试着对其分类做了一些系统性的总结,希望大家一起来讨论。

    按执行方式进行分类

    根据程序的执行方式(执行形式),可以将编程语言分为解释型语言和编译型语言两大类。

    解释型语言

    解释语言在程序执行时逐行逐行转换程序源代码。例如:

    Basic Javascript Perl Phyton Ruby 编译型语言

    编译型语言与解释语言相反,所有源代码都被翻译成“机器语言”,并在执行之前转换为称为可执行文件的形式。例如:

    Cobol Fortran C++ Pascal Java C# VB.net 判断基准

    虽然Java,C#,VB.net和其他.net语言不能将程序编译成机器语言,但是在JAVA虚拟机之上运行的字节代码或在.net框架运行时之上运行的IL等中间代码有必要事先进行转换,并且源代码不会分发到执行环境,因此这里将它归类为编译语言。
    另一方面,诸如Javascript之类的脚本语言,虽然大多数引擎会在执行时内部转换为中间代码,但中间代码依存于各个解释器,并没有像Java字节码一样标准化,源代码必须分发到运行环境,因此这里不把它归类为编译语言。

    未完

热门总结

  • C/C++ 198 0 1 发布

    这是一份核心 C++ 语言构造的参考手册。

    基本概念

    注释
    ASCII 码表
    名字与标识符
    类型 - 基础类型
    对象 - 作用域 - 生存期
    定义与 ODR
    名字查找
      有限定 无限定
    “如同”规则
    未定义行为
    内存模型与数据竞争
    翻译阶段
    main() 函数
    模块(C++20)

    C++ 关键词

    预处理器

    #if - #ifdef - #else - #endif
    #define - # - ## - #include
    #error - #pragma - #line

    表达式

    值类别
    求值顺序与定序
    常量表达式
    运算符
      赋值 算术
      自增与自减
      逻辑 比较
      成员访问与间接
      调用、逗号、三元
      sizeof - alignof(C++11)
      new - delete - typeid
    运算符重载
    默认比较(C++20)
    运算符优先级
    转换
      隐式 显式 用户定义
      static_cast - dynamic_cast
      const_cast - reinterpret_cast
    字面量
      布尔 整数 浮点
      字符 字符串
      nullptr(C++11)
      用户定义(C++11)

    声明

    命名空间声明
    命名空间别名
    左值与右值引用
    指针 - 数组
    结构化绑定(C++17)
    枚举与枚举项
    存储期与连接
    语言连接
    inline 说明符
    内联汇编
    const/volatile - constexpr(C++11)
    decltype(C++11) - auto(C++11)
    alignas(C++11)
    typedef - 类型别名(C++11)
    详述类型说明符
    属性(C++11)
    static_assert(C++11)

    初始化

    默认初始化
    值初始化(C++03)
    复制初始化
    直接初始化
    聚合初始化
    列表初始化(C++11)
    引用初始化
    静态非局部初始化
      - 常量
    动态非局部初始化
     有序 - 无序
    复制消除

    函数

    函数声明
    默认实参
    变长实参
    lambda 表达式(C++11)
    实参依赖查找
    重载决议
    运算符重载
    重载集的地址
    协程(C++20)

    语句

    if - switch
    for - 范围 for(C++11)
    while - do-while
    continue - break - goto - return
    synchronized 与 atomic(TM TS)

    类类型 - 联合类型
    注入类名
    数据成员 - 成员函数
    静态成员 - 嵌套类
    派生类 - using 声明
    空基类优化
    虚函数 - 抽象类
    override(C++11) - final(C++11)
    成员访问 - friend
    位域 - this 指针
    构造函数与成员初始化器列表
    默认构造函数 - 析构函数
    复制构造函数 - 复制赋值
    移动构造函数(C++11) - 移动赋值(C++11)
    转换构造函数 - explicit 说明符

    模板

    模板形参与实参
    类模板 - 函数模板
    类成员模板
    变量模板(C++14)
    模板实参推导
    显式特化
    类模板实参推导(C++17)
    部分特化
    形参包(C++11) - sizeof...(C++11)
    折叠表达式(C++17)
    待决名 - SFINAE
    制约与概念 (C++20)

    异常

    throw 表达式
    try-catch 块
    函数 try 块
    noexcept 说明符(C++11)
    noexcept 运算符(C++11)
    动态异常说明(C++17 前)

    杂项

    C++ 的历史
    扩充命名空间 std
    字母缩写

    惯用手法

    资源获取即初始化
    三/五/零 法则
    指向实现的指针

  • PHP 157 0 1 发布

    函数参考 ¶

    Tip

    参见 扩展库列表/归类.

    影响 PHP 行为的扩展APC — Alternative PHP Cache (可选 PHP 缓存)APCu — APC User CacheAPD — Advanced PHP debuggerbcompiler — PHP 字节码编译器BLENC — Blenc - BLowfish ENCoder for PHP source scriptsComponere错误处理 — 错误处理和日志记录FFI — Foreign Function Interfacehtscanner — htaccess-like support for all SAPIsinclued — Inclusion hierarchy viewerMemtrackOPcache输出控制 — 输出缓冲控制PHP 选项/信息 — PHP 选项和信息phpdbg — Interactive PHP Debuggerrunkitscream — Break the silence operatoruopz — User Operations for ZendWeakref — Weak ReferencesWinCache — Windows Cache for PHPXhprof — 层次式性能分析器音频格式操作ID3 — ID3 TagsKTagliboggvorbis — OGG/VorbisOpenAL — OpenAL Audio Bindings身份认证服务KADM5 — Kerberos VRadius针对命令行的扩展Ncurses — Ncurses Terminal Screen ControlNewtReadline — GNU Readline压缩与归档扩展Bzip2LZFPharRar — Rar ArchivingZipZlib — Zlib Compression信用卡处理MCVE — MCVE (Monetra) Payment加密扩展Crack — CracklibCSPRNGHash — 哈希信息摘要框架McryptMhashOpenSSL密码散列算法Sodium数据库扩展数据库抽象层针对各数据库系统对应的扩展日期与时间相关扩展Calendar日期/时间 — 日期和时间HRTime — High resolution timing文件系统相关扩展Direct IO目录Fileinfo — 文件信息文件系统InotifyMimetypePhdfsProctitlexattrxdiff国际化与字符编码支持Enchant — Enchant spelling libraryFriBiDiGender — Determine gender of firstnamesGettexticonvintl — Internationalization Functions多字节字符串PspellRecode — GNU Recode图像生成和处理CairoExif — 可交换图像信息GD — 图像处理和 GDGmagickImageMagick — 图像处理(ImageMagick)邮件相关扩展Cyrus — Cyrus IMAP administrationIMAP — IMAP, POP3 和 NNTPMailMailparsevpopmail数学扩展BC Math — BCMath 任意精度数学GMP — GNU Multiple PrecisionLapackMath — Mathematical FunctionsStatisticsTrader — Technical Analysis for Traders非文本内容的 MIME 输出FDF — Forms Data FormatGnuPG — GNU Privacy Guardharu — Haru PDFMing — Ming (flash)PDFwkhtmltoxPS — PostScript document creationRPM Reader — RPM Header ReadingXLSWriter进程控制扩展EioEvExpectLibeventPCNTL — 进程控制POSIX程序执行 — 系统程序执行parallelpthreadsphtSemaphore — Semaphore, Shared Memory and IPCShared MemorySync其它基本扩展GeoIP — Geo IP 定位FANN — FANN (快速人工神经网络)JSON — JavaScript对象符号(JSON)Judy — Judy ArraysLuaLuaSandboxMisc. — 杂项函数ParsekitSeasLogSPL — PHP标准库 (SPL)SPL Types — SPL Type HandlingStreamsSwooleTidyTokenizerURLsV8js — V8 Javascript Engine IntegrationYaml — YAML 数据序列化Yaf — Yet Another FrameworkYaconfTaintData Structures其它服务chdb — Constant hash databasecURL — Client URL 库EventFAM — File Alteration MonitorFTPGearmanGopher — Net GopherGupnpHyperwave APILDAP — Lightweight Directory Access ProtocolMemcacheMemcachedmqseries网络RRD — RRDtoolSAM — Simple Asynchronous MessagingSNMPSocketsSSH2 — Secure Shell2Stomp — Stomp ClientSVM — 支持向量机SVN — SubversionTCP — TCP WrappersVarnishYAZYP/NIS0MQ消息系统 — ZMQZooKeeper搜索引擎扩展mnoGoSearchSolr — Apache SolrSphinx — Sphinx 客户端Swish — Swish Indexing针对服务器的扩展ApacheFastCGI 进程管理器IIS — IIS AdministrationNSAPISession 扩展Msession — Mohawk Software Session Handler FunctionsSessions — Session HandlingSession PgSQL — PostgreSQL Session Save Handler文本处理BBCode — Bulletin Board CodeCommonMarkParle — Parsing and lexingPCRE — 正则表达式(兼容 Perl)POSIX Regex — Regular Expression (POSIX Extended)ssdeep — ssdeep Fuzzy Hashing字符串变量与类型相关扩展数组类/对象 — 类/对象的信息ClasskitCtype — 字符类型检测Filter — Data Filtering函数处理Quickhash反射Variable handlingWeb 服务OAuthSCASOAPYar — Yet Another RPC FrameworkXML-RPCWindows 专用扩展COM — COM and .Net (Windows)win32pswin32serviceXML 操作DOM — Document Object ModellibxmlSDO — Service Data ObjectsSDO-DAS-Relational — SDO Relational Data Access ServiceSDO DAS XML — SDO XML Data Access ServiceSimpleXMLWDDXXMLDiff — XML diff and mergeXML 解析器XMLReaderXMLWriterXSL图形用户界面(GUI) 扩展UI

  • 概述

    作用域(Scope),即有效范围,决定了标识符(包括变量、常量、函数名等)在程序中可以被使用的区域。

    作用域存在着嵌套关系,对某段代码来说,可能存在多个同名的标识符,其作用域都覆盖了这段代码,这时,被适用的将是作用域范围最小(即离代码最近)的那个标识符。

    作用域有两大类别:

    静态作用域 
    指标识符的作用范围是由标识符的声明位置和程序的结构决定的,也就是说某段代码所参照的标识符是在哪定义的,通过对程序代码进行词法解析即可确定,标识符的作用域是静态不变的。动态作用域 
    指标识符的作用范围是由程序运行时函数的调用情况决定的,也就是说某段代码所参照的标识符是在哪定义的,需在程序运行时根据执行栈才能确定,标识符的作用域可能会是动态变化的。

    以下面的代码为例,我们来简单的看一下两者的区别和各自的特点。

    var i = 1, f1 = function() { i = i+1; console.log(i); }, f2 = function() { var i = 2; f1(); f1(); }, f3 = function() { f1(); f1(); }; f2(); f3();

    静态作用域模式下,f1() 代码里参照的 i 始终是全局变量 i ,其输出如下:

    f2() 第一次调用的 f1()  将全局变量 i 由1变为 2 , 并输出2第二次调用的 f1()  将全局变量 i 由2变为 3 ,并输出 3f3()第一次调用的 f1()  将全局变量 i 由3变为 4,并输出 4第二次调用的 f1()  将全局变量 i 由4变为 5,并输出 5

    而动态作用域模式下,f1() 代码里参照的 i 取决于函数的调用(执行栈):

    被 f2() 调用的时候  f2() 里也有同名的 i 变量,由于排在全局变量 i 的前面,这时 f1() 操作的是 f2() 里的局部变量 i。被 f3() 调用的时候  f3() 里没有局部变量 i,因此直接操作的是全局变量 i。

    其输出如下:

    f2() 第一次调用的 f1()  将 f2() 的局部变量 i 由2变为 3,并输出 3第二次调用的 f1()  将 f2() 的局部变量 i 由3变为 4,并输出 4f3()第一次调用的 f1()  将全局变量 i 由 1 变为 2,并输出 2第二次调用的 f1()  将全局变量 i 由 2 变为 3,并输出 3

    采用动态作用域模式的语言很少,大部分语言采用的都是静态作用域模式,JavaScript 采用的也是静态作用域模式,因此这里我们只针对静态作用域来进行展开。

    作用域的类型

    不限于JavaScript,这里将各个语言有所实现的静态作用域作一个总的类型分类

    全局作用域 (global scope) 
    全局作用域里的变量称为全局变量,所有程序都能访问。 
    大部分语言都支持全局作用域,既有象 Basic 一样的只有全局作用域的语言,也存在象 Python 这样不让程序简单的就能修改全局变量的语言。 
     JavaScript 支持全局作用域。文件作用域 (file scope) 
    文件作用域与全局作用域类似,但变量只能是同一个源文件模块里的程序才能访问。 
    支持文件作用域的语言比较少,有C/C++等。  
    JavaScript 不存在文件作用域。函数作用域 (function scope) 
    函数作用域是一个局部作用域,变量在函数内声明,只在函数内部可见。
    大部分语言都支持函数作用域。  
    JavaScript 支持函数作用域。代码块作用域 (block scope) 
    代码块作用域也是一个局部作用域,变量在代码块 (如:{}) 内声明,只在代码块内部可见。 
    支持代码块作用域的有 C/C++、C#、Java。  
    JavaScript 从 ES6 开始支持代码块作用域。静态局部作用域 (static local scope) 
    静态局部作用域也是函数内部的局部作用域,其特殊性是即使函数执行结束后变量也不会被释放,每次函数代码的执行访问的都是同一个变量。 
    支持静态局部作用域的语言比较少,基本上都是一些历史比较悠久的语言,如 C/C++、Fortran 等。  
    JavaScript 不存在静态局部作用域。闭包作用域(closure scope) 
    闭包是一种让函数的代码能够访问函数声明(函数对象被创建)时的作用域内(上下文环境)的变量机制。
    闭包在函数式语言中非常普遍。  
    JavaScript 支持闭包作用域。 全局作用域

    在 JavaScript 中,全局作用域是最外围的一个执行上下文,可以在代码的任何地方访问到。在浏览器中,我们的全局作用域就是 window。因此在浏览器中,所有的全局变量和函数都是作为 window 对象的属性和方法创建的。

    局部作用域

    局部作用域和全局作用域正好相反,局部作用域一般只在某个特定的代码片段内可访问到,JavaScript 中的局部作用域分为函数作用域和代码块作用域两类,其中代码块作用域在 ECMAScript6 之前不被支持。

    函数作用域

    函数作用域的变量不管声明在那个位置,在整个函数内部都可访问。函数内用var声明的变量都具有函数作用域。

    function hi() { for (var i = 0; i < 10; i++) { var value = "hi " + i ; } console.log(i); // 输出:10 console.log(value);//输出 : hi 9 } hi();

    如上例所示,变量 i 和 value 虽然是声明在循环语句块里,但因为是函数作用域,所以即使出了循环语句块,后面的代码仍可访问。

    代码块作用域

    代码块作用域的变量只在声明变量的代码块内部可见。ECMAScript6 之后,函数内用 let 声明的变量和 const 声明的常量都属于代码块作用域。

    function hi() { for (let i = 0; i < 10; i++) { let value = "hi " + i ; } try { console.log(i); // Uncaught ReferenceError: i is not defined } catch (e){ console.log(e.toString()); } try { console.log(value);// Uncaught ReferenceError: value is not defined } catch (e){ console.log(e.toString()); } } hi(); 闭包作用域

    闭包并没有一个明确标准化的定义,一个常见的定义是把闭包当成一个由函数和其创建时的执行上下文组合而成的实体。 这个定义本身没有问题,但把闭包理解成函数执行时的作用域环境好像更接近闭包的本质,因此知典对 JavaScript 中的闭包重新做了一个定义:

    闭包是将函数定义时的局部作用域环境保存起来后生成的一个实体。闭包实现了一个作用域,函数始终是运行在它们被定义的闭包作用域里,而不是它们被调用的作用域里。闭包可以嵌套,全局作用域→闭包(0..n)作用域→函数作用域→代码块(0..n)作用域就整个的形成了一个代码执行时的作用域链。

    以下面的代码为例:

    var x = 1; function Counter() { var n = 0; var f = function () { n = n + x; return n; }; return f; }

    函数Counter()在内部定义了本地变量 n,并返回一个函数对象 f。 函数对象 f 创建时的局部作用域环境(包含变量 n)被保存起来,成为被返回的函数对象内部关联的闭包。 调用Counter(),获得返回的函数对象:

    var a = Counter();

    Counter()执行后的环境状态如下:

    ┌────────────────┐ 全局环境 ┌────────────────────────────────┐ x => 1 ←─ Counter()执行时的局部环境(a的闭包) ←─ a(); a => function n => 0 ←─ a(); └────────────────────────────────┘ └────────────────┘

    连续4次调用保存在变量a里的返回函数:

    console.log("a()=" + a()); //输出: a()=1 console.log("a()=" + a()); //输出: a()=2 console.log("a()=" + a()); //输出: a()=3 console.log("a()=" + a()); //输出: a()=4

    如上例所示,a() 每次执行时访问的都是同一个闭包环境里的同一个变量 n。

    下面的代码,我们调用两次Counter():

    var a = Counter(); var b = Counter();

    Counter()两次执行后的环境状态如下:

    ┌──────────────────┐ ┌───────────────────────────────────────┐ 全局环境 ←─ Counter()第1次执行时的局部环境(a的闭包) ←─ a(); x => 1 n => 0 ←─ a(); a => function └───────────────────────────────────────┘ b => function ┌───────────────────────────────────────┐ ←─ Counter()第2次执行时的局部环境(b的闭包) ←─ b(); n => 0 ←─ b(); └───────────────────────────────────────┘ └──────────────────┘


    然后执行以下代码:

    console.log("a()=" + a()); // 输出:a()=1 console.log("a()=" + a()); // 输出:a()=2 console.log("b()=" + b()); // 输出:b()=1 console.log("a()=" + a()); // 输出:a()=3 console.log("b()=" + b()); // 输出:b()=2

    如上例所示,Counter() 每次执行所返回的函数对象 f,都是一个不同的函数对象,关联着不同的闭包环境。

    作用域链

    JavaScript 中的作用域链有两种: 一种是函数创建时保存在函数对象属性中的、静态存在的作用域链,还有一种是程序执行时,与执行上下文相关联的、动态存在的作用域链,下面对这两种作用域链分别进行说明。

    函数的作用域链

    如闭包说明中的截图所示,函数对象有一个内部属性 [[Scope]],包含了函数被创建后的作用域对象的集合,这个集合被称为函数的作用域链,它决定了哪些数据能被函数对象执行时的代码访问。

    闭包说明的示例代码中所创建的函数对象 a 和 b,各自的作用域链如下图所示:

    函数的作用域链自函数对象创建起直至函数对象被释放(不再被访问)为止,即使函数代码不在执行状态始终一直存在,因此我们说函数的作用域链是一个静态的作用域链。

    执行上下文的作用链

    (这里以函数的执行为例进行说明,与函数的执行相比,全局代码执行时的作用域链更为简单,没有函数作用域和闭包作用域。)

    执行函数时会创建一个称为“执行上下文(execution context)”的内部对象,执行上下文定义了函数执行时的环境。

    每个执行上下文都有自己的作用域链,当执行上下文被创建时,它的作用域链初始化为当前运行函数的 [[Scope]] 属性所包含的作用域对象,这些作用域对象的引用被复制到执行上下文的作用域链中。

    另外,执行上下文会为函数创建一个函数作用域对象,JavaScript 里称之为活动对象(activation object),该对象包含了函数的所有局部变量、命名参数、参数集合以及 this,然后此对象会被推入作用域链的前端。新的作用域链如下图所示:

    有些语句可以延长作用域链,即在作用域链的前端临时增加一个新的代码块作用域对象,该作用域对象会在代码执行后被移除。

    ES6 之后支持代码块作用域,如果代码块里存在 let 定义的变量,即会出现作用域延长的现象。ES5 之前也有两种特殊情况下会发生这种现象:

    try-catch 语句中的 catch 块with 语句

    函数执行结束后,函数的执行上下文对象被释放,其关联的作用域链也会一起被释放。因此我们说执行上下文的作用域链是一个动态的作用域链。

    以下面的代码为例:

    var x = 1; function Counter() { var n = 0; var f = function () { console.log("start"); for (let i = 1;i<3;i++) { debugger; console.log(i); } n = n + x; return n; var lvar; }; return f; } var a = Counter(); a();

    其执行过程中的作用域链变化如下:

    进入函数  作用域链的顶端是 local (函数作用域)   


    进入代码块  一个新的 block (块作用域)被压至作用域链的顶端   


    执行代码块内的代码  


    退出代码块  block (块作用域)被弹出, 作用域链顶端恢复为 local (函数作用域)    


  • JavaScript 145 0 1 发布

    JavaScript中变量的访问方式取决于变量的数据类型:

    基本类型
    按值访问对象类型
    按引用访问基本类型

    对象类型
  • JavaScript是一门非常简练并且功能强大的脚本语言,其机制和原理具有非常与众不同的特点。

     

    一切都是对象

    JavaScript中,数值、字符串、数组、函数等一切都属于对象(Object)。

    与其他语言一样,JavaScript仍然支持如下所示的原始数据类型及相应的字面量定义方式:

    数值型  如:3、-2、3.14 字符串型  如:“Hi,world!”、“china” 布尔型  如:true、false

    但与一般语言不同的是,基于一切都是对象的思想,JavaScript语言引擎会在程序执行时对以上原始数据类型自动创建相应的包装对象,从而可以象对象一样调用其对象方法。

    数值型 –> Number 字符串型–> String 布尔型–> Boolean

    例如,String对象都拥有返回字符串长度的length属性,如果访问“Hi,world!”.length、 将获得返回值9。

    而且,JavaScript的对象模型非常简单,JavaScript中的对象就是个键/值对的集合。

    这里的「键」不限于字符串类型,也可以是数值或其他对象。

    事实上,JavaScript中的数组(Array),本质上也是一个键/值对的集合,自然索引也是作为属性名(键)存在的。

    对象的属性 对象基于原型

    Java或C++等基于类的面向对象语言都包含有两个基本概念:类(Class)和实例(Instance)。类是一个抽象的模板,比如Employee类,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同。

    而JavaScript的面向对象的基本概念是原型(Prototype)。对象不分类和实例,但对象可以将其他的对象作为自己的原型,然后根据需要定义独自的属性。

    运行期,JavaScript语言引擎会为所有的对象维护一个原型链,原型链的顶端是Object对象。

    当对象被访问属性时,语言引擎会先查找对象本身,对象本身不存在该属性时,然后按照原型链从下到上依次查找。这就是JavaScript语言中的继承机制。

    ECMAScript6虽然也引入了类(Class),但这并不意味着JavaScript在原型机制之外导入了一个类似于C++「类」的新机制。

    ECMAScript6中的类仅仅是提供了一个更简单明了的语法方法用于定义构造函数,本质上就是构造函数,这也是其常常被说成是语法糖的缘由。

    对象的原型 完全动态语言

    JavaScript是一个完全动态的语言,这里包含以下三个意思:

    JavaScript是一个动态类型语言 JavaScript是一个弱类型语言 JavaScript是一个动态编程语言

    其中,动态类型语言和动态编程语言是两个不同的概念,本身并没有关联性。

    动态类型语言

    动态类型语言(Dynamically Typed Language)指的是在运行期而不是在编译期对类型进行检查的一类语言。

    备注:  作为解释语言的JavaScript等语言,其编译期可以理解成将脚本加载并转换为内部可执行代码这样一个过程。

    属于动态类型语言的还有PHP、Ruby、Python等。

    与动态类型语言相对的就是静态类型语言(Statically Typed Language),C#、Java、TypeScript等就属于这类语言。

    弱类型语言

    弱类型语言具有以下特点:

    变量没有类型,可以被赋予不同类型的值 计算时不同类型的值会自动进行转化

    同属动态类型语言的PHP、Ruby、Python中,PHP、Ruby也属于弱类型语言,而Python因为变量在第一次被赋值后,其类型便已被确定下来,另外计算时必须强制指定类型转化,因此通常被认为是强类型语言。

    动态编程语言

    动态编程语言(Dynamic Programming Language)指的是能够在运行中动态改变其程序结构,包括增加新的函数、类型等的一类语言。 JavaScript里可以在函数里嵌套函数,如下例所示,嵌套函数f2就是在外面的f1()函数被调用时才创建的,这也是JavaScript闭包实现的基础。

    function f1(x) { function f2() { return x; } return f2; } f1(3); 部分支持函数编程

    JavaScript部分具有函数语言的特点,函数语言本身可能比较复杂,但对于JavaScript来说,您只要掌握以下3点就可以了:

    所有的函数都有返回值 函数没有副作用 函数也可以当作值进行处理 函数都有返回值

    所有的函数都返回值,如果没有明确的代码实现,函数的返回值就是undefined。

    函数没有副作用

    函数没有副作用,包含以下两点:

    函数不应改变外部的变量值 函数不应改变引用传递的参数的值 函数也是值

    函数也是对象,一种可以被执行的特殊对象。 函数能够当作值进行处理,因此関数的参数可以是另外一个函数,函数的返回值也可以是一个函数。

    闭包

    闭包是函数式语言中一个非常重要和强大的功能。JavaScript在函数对象被创建时会把当时的执行环境保存起来,形成一个闭包作用域,函数每次执行时都能够访问这个作用域中的变量。因此,闭包通常用来创建内部变量,使得这些变量不能被外部随意修改,同时又可以通过指定的函数接口来操作。

    代码的执行 作用域和闭包
  • 本文着重于对JavaScript代码的执行机制进行剖析和说明。 

    代码类型

    在JavaScript中,可执行的JavaScript代码分三种类型: 

    函数体代码(Function Code) 
    即用户自定义函数中的函数体JavaScript代码。 全局代码(Global Code) 
    即全局的、不在任何函数里面的代码,包括写在文件以及直接嵌入在HTML页面中的JavaScript代码等。  动态执行代码(Eval Code) 
    即使用eval()函数动态执行的JavaScript代码。 

    不同类型的代码其执行机制也有所不同。

    线程模型 JavaScript引擎线程

    JavaScript语言规范没有包含任何线程机制,客户端的JavaScript也没有明确定义线程机制,但浏览器端的JavaScript引擎基本上还是严格按照”单线程”模型去执行JavaScript代码的。
    究其原因,应该还是为了简单吧,因为JavaScript的主要用途是与用户交互以及操作DOM,如果采用多线程,将会带来很复杂的同步问题。

    JavaScript引擎是基于事件驱动的,引擎维护着一个事件队列,JavaScript引擎线程所作的就是不断的从事件队列中读取事件,然后处理事件,这个过程是循环不断的,所以整个的运行机制又称为事件循环(Event Loop)。

    虽然HTML5提出的Web Worker允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM,所以可以说,Web Worker并没有改变JavaScript单线程的本质。

    浏览器的其他线程

    JavaScript引擎是单线程的,但浏览器本身是多线程的,JavaScript引擎线程只是浏览器里的一个线程,除此之外,浏览器通常至少还有以下四类线程:

    GUI渲染线程 
    在JavaScript中,GUI渲染操作也是异步的,DOM操作的代码会在GUI渲染线程的事件队列中生成一个任务,GUI渲染处理由GUI渲染线程而不是JavaScript引擎线程执行。
    但需要注意 GUI渲染线程与JavaScript引擎线程是互斥的,当JavaScript引擎线程执行时GUI渲染线程会被挂起,而GUI渲染线程执行时,JavaScript引擎线程肯定不在执行状况。 用户交互线程 
    当一个用户入力事件(鼠标点击,键盘入力等)被触发时该线程会把事件添加到JavaScript引擎线程的事件队列的队尾,等待JavaScript引擎线程的处理。 网络通信线程 
    网络通信线程负责网络通信,并且在服务器回复之后会把事件添加到JavaScript引擎线程的事件队列的队尾,等待JavaScript引擎线程的处理。 定时器线程 
    定时触发(setTimeout 和 setInterval)是由浏览器的定时器线程执行定时计数,然后在定时时间结束时把定时处理函数的执行代码插入到 JavaScript引擎线程的事件队列的队尾,等待JavaScript引擎线程的处理。 
    所以用这两个函数的时候,实际的执行时间是大于指定时间的,并不保证能准确定时。 执行上下文

    不仅仅是JavaScript,解释性语言都存在执行上下文(execution context,也称执行环境,运行上下文)这样一个概念。

    执行上下文定义了执行中的代码有权访问的其他数据,决定了它们各自的行为。

    分类

    执行上下文大致可以分为两类:

    全局上下文(global context) 
    最外围的一个执行上下文,全局上下文取决于执行环境,在浏览器中则是window。 局部上下文(函数执行上下文) 
    每个函数都有自己的执行上下文,当执行进入一个函数时,函数的执行上下文就会被推入一个执行上下文栈的顶部并获取执行权。
    当这个函数执行完毕,它的执行上下文又从这个栈的顶部被删除,并把执行权并还给之前执行上下文。这就是JavaScript程序中的执行流。

    而由eval()函数动态执行的代码运行在调用者的执行上下文之中,不会产生新的执行上下文。

    与作用域的关系

    执行上下文与作用域很容易被混淆成同一个东西,事实上两者的概念是完全不同的。

    以函数为例,函数的执行上下文是完全与函数代码运行相关联的动态存在,相关代码运行结束了,与之相关联的执行上下文也就被释放了,而作用域更多的是一个静态的概念,如闭包作用域就与代码是否正在执行没有关系。

    执行上下文与作用域的关联是:执行上下文会为执行中的代码维护一个作用域链,里面包含了代码可以访问的各个名字对象,当代码中出现访问某个标识符(变量名,函数名等),JavaScript引擎会根据这个作用域链顺序进行查找,如果存在则直接返回该对象,如果整个链里都不存在则会产生异常。

    构成

    执行上下文只是一个抽象概念,在具体JavaScritp引擎实现中,它会被表示为一个至少包含以下三个属性的内部对象:

    变量对象(Variable Object) 
    环境中定义的所有变量和函数(函数声明,函数形参)都保存在这个对象中。 作用域链 
    一个由变量对象组成单向链表,用于变量或其他标识符查找,本质上,它是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。 
    详细说明请参考执行上下文的作用域链 this  this被赋予函数所属的Object,具体来说: 当函数被作为某个对象的方法被调用时,this代表该对象。 全局函数和匿名函数里的this代表全局对象的window,但如果是strict模式,this则是undefined。 构造函数中的this代表构造函数所创建的对象。 apply()和call()方法在参数里明确指示函数执行时的this对象。 流程

    在JavaScript中,程序代码是在执行上下文环境里被执行的,这包括两个阶段:

    为代码创建执行上下文 包括 创建变量对象 创建arguments对象,初始化参数名称和值 扫描代码中的函数声明,将该函数对象放入变量对象 扫描代码中的变量声明,将该变量对象放入变量对象,这个阶段变量的赋值语句并不执行,所以所有变量的值都是undefined 初始化作用域链 判断this对象 执行代码  在当前上下文上解释执行代码

    从以上记述可以看到, 函数执行之前,函数的代码首先会被全部扫描,内部声明的函数,变量不分位置,全部事先登记到执行上下文的变量对象里。这为JavaScript语言带来了一个提升(Hoisting)的概念,即后面定义的名字,前面的代码也可访问。

    示例代码如下:

    (function() { log(); //正常输出hello,因为下面定义的log()函数的作用域被提升到顶端 v(); //异常,因为下面定义的v变量的作用域虽被提升到顶端但值为undefined function log() { console.log('hello'); } var v = log; }()); 异步处理

    JavaScritp的异步处理是通过回调函数实现的,即通过事件队列,在主线程执行完当前的任务,主线程空闲后轮询事件队列,并将事件队列中的任务(回调函数)取出来执行。

    异步处理大致有以下几大类型,不同的异步处理由不同的浏览器内核模块调度执行,调度会将相关回调添加到事件队列中。

    DOM事件回调 定时触发回调 网络通信回调 Promise回调

    其中,Promise的优先级最高,排在其他所有类型的异步处理之前,而Promise以外的异步处理之间并没有优先级差别。