JavaScript代码的执行机制
0 171

本文着重于对JavaScript代码的执行机制进行剖析和说明。 

代码类型

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

  1. 函数体代码(Function Code) 
    即用户自定义函数中的函数体JavaScript代码。
  2. 全局代码(Global Code) 
    即全局的、不在任何函数里面的代码,包括写在文件以及直接嵌入在HTML页面中的JavaScript代码等。 
  3. 动态执行代码(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引擎线程只是浏览器里的一个线程,除此之外,浏览器通常至少还有以下四类线程:

  1. GUI渲染线程 
    在JavaScript中,GUI渲染操作也是异步的,DOM操作的代码会在GUI渲染线程的事件队列中生成一个任务,GUI渲染处理由GUI渲染线程而不是JavaScript引擎线程执行。
    但需要注意 GUI渲染线程与JavaScript引擎线程是互斥的,当JavaScript引擎线程执行时GUI渲染线程会被挂起,而GUI渲染线程执行时,JavaScript引擎线程肯定不在执行状况。
  2. 用户交互线程 
    当一个用户入力事件(鼠标点击,键盘入力等)被触发时该线程会把事件添加到JavaScript引擎线程的事件队列的队尾,等待JavaScript引擎线程的处理。
  3. 网络通信线程 
    网络通信线程负责网络通信,并且在服务器回复之后会把事件添加到JavaScript引擎线程的事件队列的队尾,等待JavaScript引擎线程的处理。
  4. 定时器线程 
    定时触发(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中,程序代码是在执行上下文环境里被执行的,这包括两个阶段:

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

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

示例代码如下:

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

异步处理

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

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

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

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

0 171
() 全部评论
所有回复 (0)

推荐总结

  • 概述

    作用域(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是一门非常简练并且功能强大的脚本语言,其机制和原理具有非常与众不同的特点。

     

    一切都是对象

    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中的属性有两种类型:数据属性和访问器属性。

    数据属性

    数据属性可以看成是直接封装了一个内部变量,内部变量中存放了该属性的值。 当对某个对象尚未存在的属性进行赋值时,该属性将会作为数据属性被自动创建。

    var o = {}; o.prop1 = "value1";

    上面的代码中,对象o的属性prop1即会在被赋值时自动创建。

    数据属性也可通过Object.defineProperty方法和value定义来创建。

    var o = {}; Object.defineProperty(o,"prop1",{ value : "value1", writable : true } 访问器属性

    访问器属性类似于C#,Ruby,Delphi等语言中的属性,内部可以不用直接关联一个数据变量,而是为属性的读取和更新分别提供了一个相应的getter方法和setter方法。 访问器属性必须通过Object.defineProperty或其他类似的方法事先进行定义。

    var o = {}; Object.defineProperty(o,"prop1",{ get: function(){ if (this._prop1 === undefined) { this._prop1 = 1; } return this._prop1; }, set : function(s){ if (typeof s == "string"){ this._prop1 = parseFloat(s); } else if ( typeof s == "number") { this._prop1 = s; } else { throw new Error("invalie parameter!"); } } }

    有了get方法,我们就可以在属性第一次被访问时才去进行初期化处理,而有了set方法,我们就可以追加对赋值进行类型转化等很多数据属性没法实现的程序逻辑。

    属性的特性

    ES5开始,JavaScript为属性提供了三个特性用于描述其各种特征。特性是内部值,不能直接访问。属性的特性会有一些默认值,要修改特性的默认值,必须使用Object.defineProperty方法。

    下面依次对这些特性进行说明

    configurable

    configurable特性定义是否可以通过delete操作符来删除属性,默认值是true。

    var o = {}; o.p1="value1"; console.log(o.p1); // value1 delete o.p1; console.log(o.p1); // undefine

    如以上代码所示,属性被通过delete操作符删除之后再去访问就已变成未定义了。 然后,我们可以把属性的Configurable特性设置为false来防止属性删除。

    var o = {}; o.p1="value1"; console.log(o.p1); // value1 Object.defineProperty(o, "p1", { configurable: false }) delete o.p1; console.log(o.p1); // value1 enumerable

    enumerable特性定义是否能够通过for…in语句来枚举出属性,默认是true

    writable

    writable特性定义表示属性值是否可以修改,默认为true。

    属性的继承

    属性可以通过对象的原型链进行继承。

    var objA = new Object(); objA.prop1 = 10; function Func1() {} Func1.prototype = objA; var objB = new Func1(); function Func2() {} Func2.prototype = objB; var objC = new Func2(); console.log(objC.prop1); // 10

    上面的代码中,objC本身没有prop1属性,因此访问objC.prop1时,JavaScript将会按照objC—>objB—>objA的原型链进行顺序查找,最后从objA中取出该属性值。

    objC.prop1 = 20;

    这时如重新将objC.prop1进行赋值,并不会影响到objB和objA,而是objC自身会被自动创建一个同名的数据属性。

    console.log(objC.prop1); //20 console.log(objB.prop1); //10 console.log(objA.prop1); //10 属性的键值

    JavaScript里对象的属性是以键/值对的形式存在的,这里的「键」不限于字符串类型,也可以是数值或其他对象

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

    //数值也可以是属性名(键) var a = [0,9,2]; console.log(2 in a); // 输出:true console.log(3 in a); // 输出:false console.log(a.1); // 语法错 //任意对象也可以是属性名(键) var d1 = new Date("2012/01/01"), d2 = new Date("2013/03/01"), o = { }; o[d1] = "2012/01/01"; o[d2] = "2013/03/01"; console.log(o[d1]); // 2012/01/01 console.log(o[d2]); // 2013/03/01 //下面代码,d1,d2在JavaScript语法里被当作变量名的字符串处理, console.log(o.d1); // undefined console.log(o.d2); // undefined

    字符串以外的值虽然也可以作为属性的键值,但因为JavasSript语法只允许字符串为变量名,所以不能以a.1或o.d1这样的方式,而只能以a[1]或o[d1]的方式访问对象的属性。

    对象限制

    ES5中提供了一系列限制对象被修改的方法,按限制程度由低到高,依次有禁止扩展,密封,冻结三种模式。当然,即使是冻结模式,访问器属性的set方法仍然可正常动作,表现出来就是该属性值仍可修改。

    禁止扩展对象

    通过Object.preventExtensions()方法可以禁止将对象进行扩展,禁止扩展后的对象无法:

    添加新的属性

    但可以:

    删除已有的属性 改变已有属性的特性 修改已有数据属性的值(如果该属性可写) 修改已有访问器属性的值(如果有set方法) 密封对象

    通过Object.seal方法可以将对象进行密封,密封后的对象无法:

    添加新的属性 删除已有的属性 改变已有属性的特性

    但可以

    修改已有数据属性的值(如果该属性可写) 修改已有访问器属性的值(如果有set方法) 冻结对象

    通过Object.freeze方法可以将对象进行冻结,冻结后的对象无法:

    添加新的属性 删除已有的属性 改变已有属性的特性 修改已有数据属性的值(即使该属性可写)

    但可以

    修改已有访问器属性的值(如果有set方法) 相关方法汇总

    属性的相关方法都是以Object的静态方法或原型方法的形式提供的,下面简单的做一下汇总:

    属性定义相关 Object.create() 创建对象的同时定义该对象的属性 Object.defineProperty() 定义一个属性 Object.defineProperties() 定义一组属性 Object.getOwnPropertyDescriptor() 获取属性定义信息 属性访问相关 Object.keys() 获取对象的所有属性名,仅限于可枚举的自身属性 Object.getOwnPropertyNames() 获取对象的所有属性名,包括可枚举和不可枚举,仅限自身属性 Object.prototype.hasOwnProperty() 判断对象自身是否拥有某个属性 对象限制相关 Object.preventExtensions() 限制对象扩展 Object.isExtensible() 判断对象是否可以扩展 Object.seal() 密封对象 Object.isSealed() 判断对象是否被密封 Object.freeze() 冻结对象 Object.isFrozen() 判断对象是否被冻结
  • JavaScript是一个基于原型的面向对象的语言。 本文着重于对原型的实现机制进行剖析和说明。

     

    原型链的实现

    JavaScript里所有的对象都有一个名为__proto__的属性,这个属性里面存放的就是对象所参照的原型对象的引用。

    __proto__中的对象连在一起就构成了一个原型链,链的顶端就是Object.prototype对象,Object.prototype的__proto__属性值则是null
    __proto__属性被包含在ECMAScript6之中,但之前基本上已被大多数浏览器厂商所支持。
    通过Object.getPrototypeOf()可以获得指定对象的原型对象,这也是被推荐的使用方法。但__proto__属性是可读写的,这也意味着程序可以通过该属性动态的改变对象的原型对象。

    原型的自动设置

    当通过构造函数创建新对象时,JavaScript会自动将构造函数的prototype属性值设置到新对象的__proto__属性里。

    作为示例,我们首先声明一个类(构造函数)Person

    var Person = function(name) { this.name = name; }; Person.prototype.getName = function() { return this.name; };

    然后我们创建一个Person的对象。

    var tom = new Person("Tom");

    上面创建Person对象的代码与下面的程序逻辑是等价的,事实上JavaScript也是这样执行的。

    var tom = new Object(); tom.__proto__ = Person.prototype; tom = Person.call(tom,"Tom"); 属性的继承

    当访问对象的属性时,JavaScript会通过遍历原型链进行查找,直到找到给定名称的属性为止。

    如果查找进行到原型链的顶部-Object.prototype仍然没有找到指定名称的属性时,就会返回undefined。

    由于这个查值过程是一个遍历过程,所以当属性的值越靠近顶层,查找性能就会越低,而当值靠近底层时,查找性能就会高很多,所以在编写复杂的应用时,一定要提防原型链过长而带来的性能问题。

    而设值对象属性则不会遍历原型链,而是直接将属性添加到该对象自身,并不影响到原型链中的对象。

    //接上面的例子 //下面这条语句直接取的是tom对象的属性值 console.log(tom.name); // 输出:Tom //下面这条语句执行的是tom.__proto__(引用的是Person.prototype)的getName方法 tom.getName(); // 输出:Tom
  • JavaScript 175 0 1 发布

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

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

    对象类型

热门总结

  • HTML5 17213 0 1 发布

    本目录收录的是电子邮件相关的应用软件。


    定义

    电子邮件(Email)又称电子邮箱,简称电邮,是指一种由一寄件人将数字信息发送给一个人或多个人的信息交换方式,目的是达成发信人和收信人之间的信息交互。

    电子邮件系统是以存储与转发的模型为基础,邮件服务器接受、转发、提交及存储邮件。寄信人、收信人及他们的电脑都不用同时在线。寄信人和收信人只需在寄信或收信时简短的连接到邮件服务器即可。

    范围

    本目录收录的软件仅限于以电子邮件为主要功能(或之一)的应用软件。

    列表

    本目录收录了以下软件:

     RainLoop

    简单的、 现代的 & 快速、 基于 WEB 的电子邮件客户端。

    以下是 RainLoop 各相关链接及授权信息的介绍:

    官网(HP)
    https://www.rainloop.net/源代码(Source)
    https://github.com/RainLoop/rainloop-webmail授权(License)
    AGPL 3.0范例(Example)
    https://mail.rainloop.net/#/mailbox/INBOX

  • HTML5 16791 0 1 发布

    本目录收录的是即时通信相关的应用软件。

     定义

    即时通信(Instant Messaging,IM)是一种通过网络进行实时通信的系统,允许两人或多人使用网络即时的传递文字消息、文件、语音与视频交流。

    即时通信不同于电子邮件在于它的交谈是即时(实时)的。大部分的即时通信服务提供了状态信息的特性──显示联系人名单,联系人是否在在线与能否与联系人交谈等等。

    范围

    本目录收录的软件仅限于以即时通信为主要功能(或之一)的应用软件。

    列表

    本目录收录了以下软件:

    CandyConverse.jsKaiwaRocket.Chat

    Candy

    Candy是一个支持XMPP协议的多用户即时聊天客户端软件。

    以下是 Candy 各相关链接及授权信息的介绍:

    官网(HP)
    http://candy-chat.github.io/candy源代码(Source)
    https://github.com/candy-chat/candy授权(License)
    MIT范例(Example)

    Converse.js

    Converse.js是一个支持XMPP/JABB的多用户即时聊天客户端软件。

    以下是 Converse.js各相关链接及授权信息的介绍:

    官网(HP)
    http://conversejs.org源代码(Source)
    https://github.com/jcbrand/converse.js授权(License)
    MPL范例(Example)
    https://conversejs.org/demo/anonymous.html

    Kaiwa

    Kaiwa 是一个支持XMPP的即时聊天客户端软件。

    以下是 Kaiwa 各相关链接及授权信息的介绍:

    官网(HP)
    http://getkaiwa.com源代码(Source)
    https://github.com/digicoop/kaiwa授权(License)
    MPL范例(Example)

    Rocket.Chat

    WebChat平台。

    以下是 Rocket.Chat 各相关链接及授权信息的介绍:

    官网(HP)
    https://rocket.chat/源代码(Source)
    https://github.com/RocketChat/Rocket.Chat授权(License)
    MIT范例(Example)
    https://demo.rocket.chat/home

  • HTML5 16452 0 1 发布

    本目录收录的是流程图相关的应用软件。


    定义

    流程图(Flowchart Diagram)是表示算法、工作流或流程的一种框图表示,它以不同类型的框代表不同种类的步骤,每两个步骤之间则以箭头连接。

    流程图大致可以分为以下四种类型:

    文件流程图数据流程图系统流程图程序流程图范围

    本目录收录的软件仅限于以流程图实现为主要功能(或之一)的应用软件。

    列表

    本目录收录了以下软件:

    Diagramo

    Diagramo是一个流程图模型编辑工具

    以下是Diagramo各相关链接及授权信息的介绍:

    官网(HP)
    http://diagramo.com源代码(Source)
    https://github.com/ssshow16/diagramo授权(License)
    GPL范例(Example)
    http://diagramo.com/editor/editor.php

  • HTML5 13633 0 1 发布

    ERD

    本目录收录的是ERD模型相关的应用软件。

    定义

    ERD(Entity-relationship Diagram,实体关系图)是概念数据模型的高层描述所使用的数据模型或模式图。

    ERD由实体和实体之间的关系定义而成,实体(Entity)表示一个离散对象,可以被(粗略地)认为是名词,如人、交易等。关系(Relationship)描述了两个或更多实体相互如何关联,联系可以被(粗略地)认为是动词。

    范围

    本目录收录的软件仅限于以ERD模型实现为主要功能(或之一)的应用软件。

    列表

    本目录收录了以下软件:

    WWWSqlDesigner

    WWWSqlDesigner是一个ER图形工具,允许用户创建数据库设计,可以保存/加载并导出到SQL脚本。 支持各种数据库和语言,能够导入现有的数据库设计。

    以下是WWWSqlDesigner各相关链接及授权信息的介绍:

    官网(HP)
    https://github.com/ondras/wwwsqldesigner源代码(Source)
    https://github.com/ondras/wwwsqldesigner授权(License)
    BSD范例(Example)
    http://ondras.zarovi.cz/sql/demo/?keyword=default

  • PHP 9758 0 1 发布

    Bootstrap3是一个高度可定制的基于Bootstrap的DokuWiki模板,具有响应性,适用于所有设备(移动设备,平板电脑,台式机等)。

    功能和特点HTML5和CSS3基于Bootstrap 3.xGlyphicons 和 FontAwesome图标AnchorJS支持可高度定制丰富的HTML和DokuWiki钩子侧边栏支持(左侧和右侧)主题切换器插件统合Bootstrap Wrapper PluginDiagram PluginDiscussion PluginEdittable PluginExplain PluginInlinetoc PluginLinkback PluginMove PluginOverlay PluginPublish PluginRack PluginTagging PluginTags PluginTranslation PluginUser Home-Page PluginWrap Plugin - TabsTplInc Plugin设定主题项目名项目说明值类型缺省值可选值bootstrapThemeBootstrap主题multichoicedefaultdefault
    optional
    custom
    bootswatchbootswatchTheme从Bootswatch.com选择主题multichoiceyeticerulean
    cosmo
    cyborg
    darkly
    flatly
    journal
    lumen
    paper
    readable
    sandstone
    simplex
    solar
    slate
    spacelab
    superhero
    united
    yeticustomTheme插入自定义主题的URLstringnullshowThemeSwitcher在导航栏中显示Bootswatch.com主题切换器onoff0hideInThemeSwitcher在主题切换器中隐藏主题multicheckboxnullcerulean
    cosmo
    cyborg
    darkly
    flatly
    journal
    lumen
    paper
    readable
    sandstone
    simplex
    solar
    slate
    spacelab
    superhero
    united
    yetithemeByNamespace按名字空间指定主题onoff0侧边栏项目名项目说明值类型缺省值sidebarPositionDokuWiki Sidebar position (left or right)multichoiceleftleft
    rightrightSidebarThe Right Sidebar page name, empty field disables the right sidebar.
    The Right Sidebar is displayed only when the default DokuWiki sidebar is enabled and is on the left position (see the sidebarPosition configuration). If do you want only the DokuWiki sidebar on right position, set the sidebarPosition configuration with right valuestringrightsidebarleftSidebarGridLeft sidebar grid classes col-{xs,sm,md,lg}-x (see Bootstrap Grids documentation)stringcol-sm-3 col-md-2rightSidebarGridRight sidebar grid classes col-{xs,sm,md,lg}-x (see Bootstrap Grids documentation)stringcol-sm-3 col-md-2sidebarOnNavbarDisplay the sidebar contents inside the navbar (useful on mobile/tablet devices)onoff0sidebarShowPageTitleDisplay Sidebar page titleonoff1导航栏项目名项目说明值类型缺省值inverseNavbarInverse navbaronoff0fixedTopNavbarFix navbar to toponoff0showTranslationDisplay translation toolbar (require Translation Plugin)onoff0showToolsDisplay Tools in navbarmultichoicealwaysnever
    logged
    alwaysshowHomePageLinkDisplay Home-Page link in navbaronoff0homePageURLUse custom URL for home-page linksstringnullshowUserHomeLinkDisplay User Home-Page link in navbaronoff1hideLoginLinkHide the login button in navbar. This option is useful in “read-only” DokuWiki installations (eg. blog, personal website)onoff0showEditBtnDisplay edit button in navbarmultichoicenevernever
    logged
    alwaysindividualToolsSplit the Tools in individual menu in navbaronoff0showIndividualToolEnable/Disable individual tool in navbarmulticheckboxsite,pageuser
    site
    pageshowSearchFormDisplay Search form in navbarmultichoicealwaysnever
    logged
    alwaysshowAdminMenuDisplay Administration menuonoff0useLegacyNavbarUse legacy and deprecated navbar.html hook (consider in the future to use the :navbar hook)onoff0showNavbarDisplay navbar hookmultichoicealwayslogged
    alwaysnavbarLabelsShow/Hide individual labelmulticheckboxlogin,registerlogin
    register
    admin
    tools
    user
    site
    page
    themes
    expand
    profileshowAddNewPageEnable the Add New Page plugin in navbar (require Add New Page Plugin)multichoicenevernever
    logged
    alwaysnotifyExtensionsUpdateNotify extensions update (for Admin users)onoff0Semantic项目名项目说明值类型缺省值semanticEnable semantic dataonoff1schemaOrgTypeSchema.org type (Article, NewsArticle, TechArticle, BlogPosting, Recipe)multichoiceArticleArticle
    NewsArticle
    TechArticle
    BlogPosting
    RecipeshowSemanticPopupDisplay a popup with an extract of the page when the user hover on wikilink (require Semantic Plugin)onoff0布局项目名项目说明值类型Default ValuefluidContainerEnable the fluid container (full-width of page)onoff0fluidContainerBtnDisplay a button in navbar to expand containeronoff0pageOnPanelEnable the panel around the pageonoff1tableFullWidthEnable 100% full table width (Bootstrap default)onoff1tableStyleTable stylemulticheckboxstriped,condensed,responsivestriped
    bordered
    hover
    condensed
    responsiveshowLandingPageEnable the landing page (without a sidebar and the panel around the page)onoff0landingPagesLanding page name (insert a regex)regex(intro)showPageToolsEnable the DokuWiki-style Page Toolsmultichoicealwaysnever
    logged
    alwaysshowPageIdDisplay the DokuWiki page name (pageId) on toponoff1showBadgesShow badge buttons (DokuWiki, Donate, etc)onoff1showLoginOnFooterDisplay a “little” login link on footer. This option is useful when hideLoginLink is ononoff0showWikiInfoDisplay DokuWiki name, logo and tagline on footeronoff1文章目录 项目名项目说明值类型缺省值tocAffixAffix the TOC during page scrollingonoff1tocCollapseSubSectionsCollapse all sub-sections in TOC to save spaceonoff1tocCollapseOnScrollCollapse TOC during page scrollingonoff1tocCollapsedCollapse TOC on every pagesonoff0tocLayoutTOC layoutmultichoicedefaultdefault
    navbarg钩子HTML钩子

    所有文件必须位于模板目录(lib / tpl / bootstrap3 /)或conf /目录中。

    文件名插入到页面HTML中的位置meta.html <head>和</head>之间topheader.html紧接着<body>标签之后header.htmlAbove the upper blue bar, below the pagename and wiki titlenavbar.htmlDEPRECATED (see the note below) - Inside the navbar, use this to add additional links (e.g. <li><a href=“/foo”>Foo</a></li>)pageheader.htmlbreadcrumbs下面,页面实际内容的上方pagefooter.htmlAbove the lower blue bar, below the last changed Datefooter.html在页面的最后,位于</ body>标记之前sidebarheader.html边侧栏上方sidebarfooter.html边侧栏下方social.htmlBelow the header.html, use this to add a social buttons (eg. Google+, Twitter, LinkedIn, etc)rightsidebarheader.html右边侧栏上方rightsidebarfooter.html
    右边侧栏下方
    Dokuwiki钩子

    可以通过创建简单的DokuWiki“钩子”页面来自定义页面的各个部分。 bootstrap3模板会将这些钩子页面内容插入到页面的总体布局中。

    钩子页面名说明名字空间单位:sidebarThe sidebarYES:rightsidebarThe right-sidebarYES:navbarNavbar with sub-menusYES:pageheaderHeader of the Wiki articleYES:pagefooterFooter of the Wiki articleYES:footerFooter of the pageYES:cookie:bannerCookie-Law bannerNO:cookie:policyCookie-Law policyNO:helpHelp page for “Help Page Icon”YES:headerHeader of page below the navbarYES:topheaderTop Header of page (on top of navbar if fixedTopNavbar is off)YES

  • PHP 1375 0 1 发布
    CSS文件

    DokuWiki本体的CSS文件位于lib / styles目录中,不过DokuWiki本体仅定义了一些最基础的CSS,更多的CSS存在于模板和各个插件里面。
    所有CSS文件都是通过lib/exe /css.php程序获取的。该程序还处理缓存,模式替换,LESS预处理和优化,由tpl_metaheaders()函数调用。

    加载CSS的顺序如下: 在CSS中,如果为同一属性指定了不同的值,并且样式冲突,则稍后加载的样式将优先,并且属性值将被覆盖,因此首选样式是从后面开始。

    基本样式:lib /styles/*.css插件样式:lib / plugins / <插件名称> / *。css模板样式:在lib / tpl / <模板名称> /style.ini中定义用户样式:conf / user * .css

    如果要通过自定义CSS添加样式,则基本上应将其添加到用户样式(conf / user * .css)中。

    媒体类型

    样式表支持五种媒体类型:

    screen:用于显示器print:用于打印all:用于所有的媒体设备rtl:feed:外部链接Dokuwiki官方说明

  • HTML5 833 0 1 发布

    本目录收录的是电子表格相关的应用软件。

     定义

    电子表格(Spreadsheet),指的是类似于Micfosoft Excel的办公文档。它会显示由一系列行与列构成的网格,称为单元格,单元格之间可以合并成一个跨多行多列的大单元格。

    单元格内可以存放数值、计算式、或文本,单元格的边框和文本颜色字体通常可以个别设置。

    范围

    本目录收录的软件仅限于以电子表格为主要功能(或之一)的应用软件。

    列表

    本目录收录了以下软件:

    EtherCalc

    EtherCalc是一个实时多人协作的电子表格处理器,后台需NodeJS服务器

    以下是 EtherCalc各相关链接及授权信息的介绍:

    官网(HP)
    https://ethercalc.net/源代码(Source)
    https://github.com/audreyt/ethercalc授权(License)
    Apache 2范例(Example)

    https://ethercalc.net/

    截图

  • HTML5 456 0 1 发布

    本目录收录的是PDF相关的应用软件。

     定义

    PDF(Portable Document Format:便携式文档格式)是一种用独立于应用程序、硬件、操作系统的方式呈现文档的文件格式。

    每个PDF文件包含固定布局的平面文档的完整描述,包括文本、字形、图形及其他需要显示的信息,通常在任何设备和环境下都能获得同样的展现。

    范围

    本目录收录的软件仅限于以PDF为主要功能(或之一)的应用软件。

    列表

    本目录收录了以下软件:

    laddu-reader

    Laddu 是一个PDF阅读器,基于Mozilla的pdf.js。

    以下是 laddu-reader 各相关链接及授权信息的介绍:

    官网(HP)
    源代码(Source)
    https://github.com/iraycd/laddu-reader授权(License)
    MIT范例(Example)
    截图