• JavaScript高级程序设计读书笔记


    JavaScript简介

    JavaScript简史

    JavaScript 诞生于 1995 年。


    JavaScript实现

    一个完整的 JavaScript 实现由下列三个不同的部分组成:

    1. 核心(ECMAScript);
    2. 文档对象模型(DOM);
    3. 浏览器对象模型(BOM)。

    ECMAScript

    ECMA-262 标准没有参照 web 浏览器,它规定了这门语言的下列组成部分:语法、类型、语句、关键字、保留字、操作符、对象。


    文档对象模型(DOM)

    文档对象模型(DOM,Document Object Model)是针对 XML 但经过扩展用于 HTML 的应用程序编程接口(API)。


    浏览器对象模型(BOM)

    浏览器对象模型(BOM,Browser Object Model)从根本上将,BOM 只处理浏览器窗口和框架,但习惯上也把所有针对浏览器的 JavaScript 扩展算作 BOM 的一部分,下面就是一些这样的扩展:

    • 弹出新浏览器窗口的功能;
    • 移动、缩放和关闭浏览器窗口的功能;
    • 提供浏览器详细信息的 navigator 对象;
    • 提供浏览器所加载页面的详细信息的 location 对象;
    • 提供用户显示器分辨率详细信息的 screen 对象;
    • 对 cookies 的支持;
    • 像 XMLHttpRequest 和 IE 的 ActiveXObject 这样的自定义对象。

    小结

    JavaScript 是一种专门与网页交互而设计的脚本语言,由下列三个部分组成:

    1. ECMAScript,由 ECMA-262 定义,提供核心语言功能;
    2. 文档对象模型(DOM),提供访问和操作网页内容的方法和接口;
    3. 浏览器对象模型(BOM),提供与浏览器交互的方法和接口;

    在HTML中使用JavaScript

    script元素

    向 HTML 页面中插入 JavaScript 的主要方法就是使用 <script> 元素。

    使用 <script> 元素的方式有两种:直接在页面中嵌入 JavaScript 代码和包含外部 JavaScript 文件。

    注意:使用 <script> 引入外部 JavaScript 文件时,不要在标签内添加额外的 JS 代码,如果添加了将会被忽略。


    标签的位置

    按照传统的做法,所有 <script> 元素都应该放在页面中 <head> 元素中,这种做法的目的就是把所有外部文件(CSS 文件和 JavaScript 文件)的引用都放在相同的地方。

    可是这种做法有一种缺点就是,必须要等到全部 JS 代码都被下载、解析和执行完成以后,才能开始出现页面的内容(浏览器在遇到 <body> 标签时才开始出现内容)。对于需要很多 JS 代码的页面来说,这会导致页面出现明显的延迟,而在延迟期间页面是一片空白。所以,为了避免这个问题,我们应该把 JS 引用放在 <body> 结束标签前面,即:

    <body> 
        <!--  html代码  -->   
        <script type="text/javascript" src="index.js"></script>
    </body>
    

    延迟脚本

    <script> 标签的 defer 属性用途是表明脚本在执行时不会影响页面的构造。也就是说,脚本会被延迟到整个页面都解析完毕后再运行。因此,在 <script> 元素中设置 defer 属性,相当于告诉浏览器立即下载,但延迟执行。

    假设把 <script> 元素放在 <head> 元素之中,并加上 defer 属性,但其中包含的脚本将延迟到浏览器遇到 </html> 标签后再执行。HTML5 规范要求脚本按照它们出现的先后顺序执行,因此第一个延迟脚本会先于第二个延迟脚本执行,而这两个脚本会先于 DOMContentLoaded 事件执行。但在现实中,延迟脚本不一定会按顺序执行,也不一定会在 DOMContentLoaded 事件触发前执行,因此最好只包含一个延迟脚本。

    defer 属性只适用于外部脚本文件。


    异步脚本

    HTML5 为 <script> 元素定义了 async 属性。与 defer 属性类似,都用于改变处理脚本的行为,也只适用于外部脚本文件,并告诉浏览器立即下载文件。但不同的是,标记为 async 的脚本并不保证按照指定它们的先后顺序执行。

    例如,在 <head> 元素中放两个 <script> 元素。第二个脚本文件可能会在第一个脚本文件前执行。指定 async 属性的目的是不让页面等待两个脚本下载和执行,从而异步加载页面其他内容。因此,建议异步脚本不要在加载期间修改 DOM。

    异步脚本一定会在页面的 load 事件前执行,但可能会在 DOMContentLoaded 事件触发前或后执行。


    嵌入代码与外部文件

    使用外部文件的优点:

    1. 可维护性;
    2. 可缓存;
    3. 适应未来;

    文档模式

    两种文档模式:混杂模式(quirks mode)、标准模式(standards mode)。


    noscript元素

    这个元素用以在不支持 JS 的浏览器中显示替代的内容。

    包含 <noscript> 元素中的内容只有在下面的情况才会显示出来:

    1. 浏览器不支持脚本;
    2. 浏览器支持脚本,但脚本被禁用;

    基本概念

    语法

    区分大小写

    ECMAScript 中的一切(变量、函数名和操作符)都区分大小写。


    标识符

    标识符就是指变量、函数、属性的名字,或函数的参数。

    标识符按照以下规则组合:

    • 第一个字符必须是字母、下划线(_)或美元符号($);
    • 其他字符可以是字母、下划线、美元符号或数字;

    不能把关键字、保留字、true、false、null 用作标识符。


    注释

    // 单行注释
    
    /*
    这是多行注释
    这是多行注释
    */
    

    严格模式

    严格模式是为 JS 定义了一种不同的解析与执行模型。在严格模式下,一些不确定的行为将得到处理,对某些不安全的操作会抛出错误。

    要在整个脚本中启用严格模式,可以在顶部添加下列代码:

    "use strict";
    

    它是一个编译指示(pragma),用来告诉支持 JS 引擎切换到严格模式。

    可以指定函数在严格模式下执行:

    function doSomething(){
        "use strict";
        //函数体
    }
    

    语句

    ECMAScript 中语句以一个分号结尾。如果省略分号,则由解析器确定语句的结尾。


    关键字和保留字

    关键字可用于表示控制语句的开始或结束,或用于执行特定操作等。关键字不能用作标识符。

    ECMA-262 还描述了一组不能用作标识符的保留字


    变量

    ECMAScript 的变量是 松散类型 的,就是可以用来保存任何类型的数据。

    定义变量时要使用 var 操作符(var 是一个关键字),后跟变量名(即标识符)。

    如果省略 var 操作符可以定义全局变量,但这也不是我们所推荐的。因为在局部作用域中定义的全局变量很难维护,而且如果有意地忽略了 var 操作符,也会由于相应变量不会马上就有定义而导致不必要的混乱。给未经声明的变量赋值在严格模式下会抛出 ReferenceError 错误。


    数据类型

    简单数据类型(基本数据类型):undefined、null、boolean、number、string、symbol、bigInt。

    复杂数据类型:object。

    typeof操作符

    typeof 用来检测给定变量的数据类型,只适用于基本数据类型的类型判断。

    对一个值使用 typeof 操作符可能返回下列某个字符串:

    • “undefined” - 如果这个值未定义;
    • “boolean” - 如果这个值是布尔值;
    • “string” - 如果这个值是字符串;
    • “number” - 如果这个值是数值;
    • “object” - 如果这个值是对象或 null;
    • “function” - 如果这个值是函数;

    undefined类型

    undefined 类型只有一个值,即特殊的 undefined。在使用 var 声明变量但未对其加以初始化时,这个变量的值就是 undefined。

    对未初始化的变量执行 typeof 操作符会返回 undefined 值,而对未声明的变量执行 typeof 操作符同样也会返回 undefined 值。


    null类型

    null 类型也只有一个值,这个特殊的值是 null。

    使用 typeof 操作符检测 null 值时会返回 “object” :

    var car = null
    alert(typeof car);    // "object"
    

    boolean类型

    boolean 类型只有两个值:true、false

    注意:boolean 类型的值是区分大小写的,True、False(以及其他的混合大小写形式)都不是 boolean 的值,只是标识符。

    要将一个值转换为其对应的 boolean 值,可以调用转型函数 Boolean()。


    number类型

    八进制字面值的第一位必须是零(0),然后是八进制数字序列(0~7)。如果字面值中的数值超出了范围,那前导零将被忽略,后面的数值将被当作十进制数解析。

    十六进制字面值的前两位必须是0x,后面跟任何十六进制数字(09及AF),其他A~F可以大写,也可以小写。

    在进行算术计算时,所有以八进制、十六进制表示的数值最后将被转换成十进制数值。


    浮点数值

    浮点数值就是该数值中必须包含一个小数点,并且小数点后面必须至少有一位数字。


    数值范围

    ECMAScript 能够表示的最小数值存在 Number.MIN_VALUE 中,在大多数浏览器中,这个值是 5e-324。

    ECMAScript 能够表示的最大数值存在 Number.MAX_VALUE 中,在大多数浏览器中,这个值是 1.7976931348623157e+308。

    如果计算结果超出了 JavaScript 数值范围时,那么这个数值将被自动转换成 Infinity 值。如果这个数值为负数,将被转换成 -Infinity(负无穷),如果这个数值为正数,将被转换成 Infinity(正无穷)。


    NaN

    即非数值是一个特殊的数值,这个数值用于表示一个本来要返回数值的操作数未返回数值的情况(这样就不会抛出错误)。

    有两个特点。首先,任何涉及 NaN 的操作都会返回 NaN,这个特点在多步计算中有可能导致问题。其次,NaN 与任何值都不相等,包括 NaN 本身。

    alert(NaN == NaN);    // false
    

    针对这两个特点,ECMAScript 定义了 isNaN() 函数。这个函数接受一个参数,该参数可以是任何类型,而函数会帮我们确定这个参数是否“不是数值”。某些不是数值的值会直接转换为数值。

    alert(isNaN(NaN));    // true
    alert(isNaN(10));       // false(10是数值)
    alert(isNaN("10"));    // false(可以被转换成数值10)
    alert(isNaN("blue"));    // true(不能转换为数值)
    alert(isNaN(true));    // false(可以被转换成数值1) 
    

    数值转换

    有三个函数可以把非数值转换为数值:Number()、parseInt()、parseFloat()。

    Number() 函数可以用于任何数据类型,另外两个函数则专门用于把字符串转为数值。这三个函数对于同样的输入会有返回不同的结果。

    Number() 函数的转换规则:

    • 如果是 boolean 值,true、false 将分别转为 1 和 0。
    • 如果是数字值,只是简单的传入和返回。
    • 如果是 null 值,返回 0。
    • 如果是 undefined,返回 NaN。
    • 如果是字符串,遵循下列规则:
      1. 如果字符串中只包含数字(包括带正负号情况),则将其转换为十进制数值(前导的零会被忽略)。
      2. 如果字符串中包含有效的浮点格式,则将其转换为对应的浮点数值(前导的零会被忽略)。
      3. 如果字符串中包含有效的十六进制格式,则将其转换为相同大小的十进制数值。
      4. 如果字符串为空(不含任何字符),则将其转换为 0。
      5. 如果字符串中包含除上述格式以外的字符,则将其转换为 NaN。
    • 如果是对象,则调用对象的 valueOf() 方法,然后依照前面的规则转换返回的值。如果转换结果为 NaN,则调用对象的 toString() 方法,然后再依照前面的规则转换返回的字符串值。

    string类型

    string 类型用于表示由零或多个 16 位 Unicode 字符组成的字符序列,即字符串。

    字符字面量

    String 数据类型包含一些特殊的字符字面量,也叫转义序列。

    换行
    制表
     退格
    回车
    f 进纸
    \ 斜杠
    ' 单引号(')
    " 双引号(")


    字符串的特点

    ECMAScript 的字符串是不可变的,也就是说,字符串一旦创建,它们的值就不能改变。如果要改变某个变量保存的字符串,首先要摧毁原来的字符串,然后再用另一个包含新值得字符串填充该变量。


    转换为字符串

    把一个值转换为一个字符串有两种方式。第一种是使用几乎每个值都有得 toString() 方法。

    在不知道要转换的值是不是 null 或 undefined 的情况时,还可以使用转型函数 String(),这个函数能够将任何类型的值转换为字符串。

    String()函数遵循下列转换规则:

    • 如果值由 toString() 方法,则调用该方法(没有参数)并返回相应的结果。
    • 如果值是 null,则返回 "null"。
    • 如果值是 undefined,则返回 "undefined"。

    Object类型

    ECMAScript 中的对象其实就是一组数据和功能的集合。

    对象可以通过执行 new 操作符后跟要创建的对象类型的名称来创建。而创建 Object 类型的实例并为其添加属性和(或)方法,就可以创建自定义对象。

    Object 的每个实例都具有下列属性和方法:

    • constructor:保存着用于创建当前对象的函数。
    • hasOwnProperty(propertyName):用于检查给定的属性在当前对象实例中是否存在。
    • isPrototypeOf(object):用于检查传入的对象是否是当前对象的原型。
    • propertyIsEnumerable(propertyName):用于检查给定的属性是否能够使用 for-in 语句来枚举。
    • toLocaleString():返回对象的字符串表示,该字符串与执行环境的地区对应。
    • toString():返回对象的字符串表示。
    • valueOf():返回对象的字符串、数值或布尔值表示,通常与 toString() 方法的返回值相同。

    操作符

    操作符包括算术操作符(如加号和减号)、位操作符、关系操作符、相等操作符

    一元操作符

    只能操作一个值的操作符叫一元操作符。

    递增和递减操作符

    前置型、后置型。前置型应该位于要操作的变量之前,后置型应该位于要操作的变量之后。

    执行前置递增和递减操作时,变量的值都是在语句被求值以前改变的。

    后置的递增和递减操作是在包含它们的语句被求值之后才执行的。

    在应用于不同的值时,递增和递减操作符遵循下列规则:

    • 在应用于一个包含有效数字字符的字符串时,先将其转换为数字值,再执行加减 1 的操作。字符串变量变成数值变量。
    • 在应用于一个不包含有效数字字符的字符串时,将变量的值设置为 NaN。字符串变量变成数值变量。
    • 在应用于布尔值 false 时,先将其转换为 0 再执行加减 1 的操作。布尔值变量变成数值变量。
    • 在应用于布尔值 true 时,先将其转换为 1 再执行加减 1 的操作。布尔值变量变成数值变量。
    • 在应用于浮点数值时,执行加减 1 的操作。
    • 在应用于对象时,先调用对象的 value() 方法以取得一个可供操作的值。然后对该值应用前述规则。如果结果是 NaN,则在调用 toString() 方法后再应用前述规则。

    一元加和减操作符

    一元加操作符以一个加号(+)表示,放在数值前面,对数值不会产生任何影响。

    一元减操作符主要用于表示负数。


    位操作符

    位操作符不直接操作 64 位的值,而是先将 64 位的值转换成 32 位的整数,然后执行操作,最后再将结果转换回 64 位。

    对于有符号的整数,32 位中的前 31 位用于表示整数的值。第 32 位表示数值的符号:0表示正数,1表示负数。这个表示符号的位叫符号位

    负数以二进制码存储,使用的格式是二进制补码,计算一个数值的二进制补码,经过以下步骤:

    1. 求这个数值绝对值的二进制码;
    2. 求二进制反码,即将 0 替换为 1,1 替换为 0;
    3. 得到的二进制反码加 1。
      根据这 3 个步骤求 -18 的二进制码,首先求 18 的二进制码:
      0000  0000  0000  0000  0000  0000  0001  0010
      然后,求其二进制反码:
      1111  1111  1111  1111  1111  1111  1110  1101
      最后,二进制反码加1:
      1111  1111  1111  1111  1111  1111  1110  1101
                                     1
      -------------------------------------------------------------------------------
      1111  1111  1111  1111  1111  1111  1110  1110
      就求得-18的二进制表示。

    如果对非数值应用位操作符,会先使用 Number() 函数将该值转换为一个数值,然后再应用位操作,得到的结果是一个数值。


    按位非(NOT)

    按位非操作符由一个波浪线(~)表示。

    执行按位非得结果就是返回数值得反码。

    按位非操作的本质:操作数的负值减 1。

    按位非是在数值表示的最底层执行操作,因此速度更快。


    按位与(AND)

    按位与操作符由一个和号字符(&)表示,它由两个操作符数。

    按位与操作只在两个数值的对应位都是 1 时才返回 1,任何一位是 0,结果都是 0。


    按位或(OR)

    按位或操作符由一个竖线(|)表示,也有两个操作符。

    按位或操作在有一个位是 1 的情况下就返回 1,而只有在两个位都是 0 的情况下才返回 0。


    按位异或(XOR)

    按位异或操作符由一个插入符号(^)表示,也有两个操作符。

    按位异或操作在两个数值对应位上只有一个 1 时才返回 1,如果对应的两位都是 1 或 0,则返回 0。


    左移

    左移操作符由两个小于号(<<)表示,这个操作符会将数值的所有位向左移动指定的位数。

    在向左移位后,原数值的右侧就多出了空位,就用 0 来填充空位。

    左移不会影响操作数的符号位。


    有符号的右移

    有符号的右移操作符由两个大于号(>>)表示,这个操作符会将数值向右移动,但保留符号位(即正负号标记)。

    在右移过程中,原数值出现空位,用符号位的值来填充空位。


    无符号右移

    无符号右移操作符由 3 个大于号(>>>)表示,这个操作符会将数值的所有 32 位都向右移动。对正数来说,无符号右移的结果与有符号右移相同。

    对于负数来说,无符号右移是以 0 来填充空位。


    布尔操作符

    布尔操作符一共有 3 个:非(NOT)、与(AND)、或(OR)

    逻辑非

    逻辑非操作符由一个叹号(!)表示。逻辑非操作符首先会将它的操作数转换为一个布尔值,然后再对其求反。

    逻辑非操作符遵循以下规则:

    • 如果操作数是一个对象,返回 false;
    • 如果操作数是一个空字符串,返回 true;
    • 如果操作数是一个非空字符串,返回 false;
    • 如果操作数是数值 0,返回 true;
    • 如果操作数是任意非 0 数值(包括Infinity),返回 false;
    • 如果操作数是 null,返回 true;
    • 如果操作数是 NaN,返回 true;
    • 如果操作数是 undefined,返回 true。

    逻辑非操作符也可以用于将一个值转换为与其对应的布尔值,用同时使用两个逻辑非操作符(!!)。


    逻辑与

    逻辑与操作符由两个和号(&&)表示,有两个操作数。

    在有一个操作数不是布尔值的情况下,逻辑与操作不一定返回布尔值,此时遵循下列规则:

    • 如果第一个操作数是对象,则返回第二个操作数;
    • 如果第二个操作数是对象,则只有在第一个操作数的求值结果为 true 的情况下才会返回该对象;
    • 如果两个操作数都是对象,则返回第二个操作数;
    • 如果第一个操作数是 null,则返回 null;
    • 如果第一个操作数 NaN,则返回 NaN;
    • 如果一个操作数是 undefined,则返回 undefined。

    逻辑与操作属于短路操作,即第一个操作数能够决定结果,那么就不会再对第二个操作数求值。


    逻辑或

    逻辑或操作符由两个竖线符号(||)表示,也有两个操作数。

    遵循以下规则:

    • 如果第一个操作数是对象,则返回第一个操作数;
    • 如果第一个操作数的求值结果为 false,则返回第二个操作数;
    • 如果两个操作数都是对象,则返回第一个操作数;
    • 如果两个操作数都是 null,则返回 null;
    • 如果两个操作数都是 NaN,则返回 NaN;
    • 如果两个操作数都是 undefined,则返回 undefined。

    逻辑或操作符也是短路操作符,如果第一个操作数的求值结果为 true,就不会对第二个操作数求值。


    乘性操作符

    3 个乘性操作符:乘法、除法、求模

    乘法

    乘法操作符由一个星号(*)表示,用于计算两个数值的乘积。

    在处理特殊值情况下,遵循下列特殊规则:

    • 如果操作数都是数值,执行常规的乘法计算;
    • 如果有一个操作数是 NaN,则结果是 NaN;
    • 如果是 Infinity 与 0 相乘,则结果是 NaN;
    • 如果是 Infinity 与非 0 数值相乘,则结果是 Infinity 或 -Infinity,取决于有符号操作符的符号;
    • 如果是 Infinity 与 Infinity 相乘,则结果是 Infinity;
    • 如果有一个操作数不是数值,则在后台调用 Number() 将其转换为数值,然后再应用以上规则。

    除法

    除法操作符由一个斜线符号(/)表示,执行第二个操作数除第一个操作数的计算。

    在处理特殊值情况下,遵循下列特殊规则:

    • 如果操作数都是数值,执行常规的除法计算;
    • 如果有一个操作数是 NaN,则结果是 NaN;
    • 如果是 Infinity 被 Infinity 除,则结果是 NaN;
    • 如果是零被零除,则结果是 NaN;
    • 如果是非零的有限数被零除,则结果是 Infinity 或 -Infinity,取决于有符号操作数的符号;
    • 如果是 Infinity 被任何非零数值除,则结果是 Infinity 或 -Infinity,取决于有符号操作数的符号;
    • 如果有一个操作数不是数值,则在后台调用 Number() 将其转换为数值,然后再应用以上规则。

    求模

    求模(余数)操作符由一个百分号(%)表示。

    在处理特殊值情况下,遵循下列特殊规则:

    • 如果操作数都是数值,执行常规的除法计算,返回除得的余数;
    • 如果被除数是无穷大值而除数是有限大的数值,则结果是 NaN;
    • 如果被除数是有限大的数值而除数是零,则结果是 NaN;
    • 如果是 Infinity 被 Infinity 除,则结果是 NaN;
    • 如果被除数是有限大的数值而除数是无穷大的数值,则结果是被除数;
    • 如果被除数是零,则结果是零;
    • 如果有一个操作数不是数值,则在后台调用 Number() 将其转换为数值,然后再应用以上规则。

    加性操作符

    加法

    加法操作符(+)

    如果两个操作符都是数值,执行常规的加法计算,然后根据下列规则返回结果:

    • 如果有一个操作数是 NaN,则结果是 NaN;
    • 如果是 Infinity 加 Infinity,则结果是 Infinity;
    • 如果是 -Infinity 加 -Infinity,则结果是 -Infinity;
    • 如果是 Infinity 加 -Infinity,则结果是 NaN;
    • 如果是 +0 加 +0,则结果是 +0;
    • 如果是 -0 加 -0,则结果是 -0;
    • 如果是 +0 加 -0,则结果是 +0。

    如果有一个操作符是字符串,就要应用如下规则:

    • 如果两个操作数都是字符串,则将第二个操作数与第一个操作数拼接起来;
    • 如果只有一个操作数是字符串,则将另一个操作数转换为字符串,然后再将两个字符串拼接起来。

    如果有一个操作数是对象、数值、布尔值,则调用它们的 toString() 方法取得相应的字符串值,然后再应用关于字符串的规则。

    对于 undefined 和 null,则分别调用 String() 函数并取得字符串“undefined”和“null”。


    减法

    减法操作符(-)

    一样需要遵循如下特殊规则:

    • 如果两个操作符都是数值,则执行常规的算术减法操作并返回结果;
    • 如果有一个操作数是 NaN,则结果是 NaN;
    • 如果是 Infinity 减 Infinity,则结果是 NaN;
    • 如果是 -Infinity 减 -Infinity,则结果是 NaN;
    • 如果是 Infinity 减 -Infinity,则结果是 Infinity;
    • 如果是 -Infinity 减 Infinity,则结果是 -Infinity;
    • 如果是 +0 减 +0,则结果是 +0;
    • 如果是 -0 减 +0,则结果是 -0;
    • 如果是 -0 减 -0,则结果是 +0;
    • 如果有一个操作数是字符串、布尔值、null 或 undefined,则先在后台调用 Number() 函数将其转换为数值,然后再根据前面规则执行减法计算。如果转换结果是 NaN,则减法的结果就是 NaN;
    • 如果有一个操作数是对象,则调用对象的 valueOf() 方法以取得表示该对象的数值。如果得到的值是 NaN,则减法的结果就是 NaN。如果对象没有 valueOf() 方法,则调用其 toString() 方法并将得到的字符转换为数值。

    关系操作符

    小于(<)、大于(>)、小于等于(<=)和大于等于(>=)这几个关系操作符用于对两个值进行比较,这几个操作符都返回一个布尔值。

    当关系操作符的操作数使用了非数值时,要进行数据转换或完成某些操作。以下就是其规则:

    • 如果两个操作数都是数值,则执行数值比较;
    • 如果两个操作数都是字符串,则比较两个字符串对应的字符编码值;
    • 如果一个操作数是数值,则将另一个操作数转换为一个数值,然后执行数值比较;
    • 如果一个操作数是对象,则调用这个对象的 valueOf() 方法,用得到的结果按照前面规则比较。如果对象没有 valueOf() 方法,则调用 toString() 方法,并用得到的结果根据前面规则比较;
    • 如果一个操作数是布尔值,则先将其转换为数值,然后再比较。

    相等操作符

    ES 提供两组操作符:相等和不相等——先转换再比较,全等和不全等——仅比较不转换。

    相等和不相等

    相等操作符由两个等于号(==)表示,如果两个操作数相等,返回 true。

    不相等操作符由叹号后跟等于号(!=)表示,如果两个操作数不相等,返回 false。

    这两个操作符都会先转换操作数(通常称为强制转型),然后比较它们的相等性。

    在转换不同数据类型时,遵循下列规则:

    • 如果有一个操作数是布尔值,则在比较相等性之前先将其转换为数值,false 转换为 0,true 转换为 1;
    • 如果一个操作数是字符串,另一个操作数是数值,在比较相等性之前先将字符串转换为数值;
    • 如果一个操作数是对象,另一个操作数不是,则调用 valueOf() 方法,用得到的基本类型值按照前面规则比较;

    这两个操作符在比较时遵循下列规则:

    • null 和 undefined 是相等的;
    • 比较相等性之前,不能将 null 和 undefined 转换成其他任何值;
    • 如果有一个操作数是 NaN,则相等操作符返回 false,而不相等操作符返回 true。注意:即使两个操作数都是
    • 如果两个操作数都是对象,则比较它们是不是同一个对象。如果两个操作数都指向同一个对象,则相等操作符返回 true;否则返回 false。

    全等和不全等

    全等操作符由 3 个等于号(===)表示,它只在两个操作数未经转换就相等的情况下返回 true。

    不全等操作符由一个叹号后跟两个等于号(!==)表示,它在两个操作数未经转换就不相等的情况下返回 true。

    注意:null == undefined 返回 true,因为它们是类似的值;但 null === undefined 会返回 false。


    条件操作符

    条件操作符遵循与 Java 中的条件操作符相同的语法形式。


    赋值操作符

    简单的赋值操作符由等于号(=)表示,其作用就是把右侧的值赋给左侧的变量。

    如果在等于号(=)前面再添加乘性操作符、加性操作符或位操作符,就可以完成复合赋值操作。

    每个主要算术操作符都有对应的复合赋值操作符,这些操作符如下:

    • 乘/赋值(*=);
    • 除/赋值(/=);
    • 模/赋值(%=);
    • 加/赋值(+=);
    • 减/赋值(-=);
    • 左移/赋值(<<=);
    • 有符号右移/赋值(>>=);
    • 无符号右移/赋值(>>>=)。

    逗号操作符

    使用逗号操作符可以在一条语句中执行多个操作。

    逗号操作符多用于声明多个变量,还可以用于赋值。用于赋值时,逗号操作符总会返回表达式中的最后一项。


    语句

    语句通常使用一或多个关键字来完成给定任务。

    if语句

    语法如下:

    if(condition){
        statement1
    }else{
        statement2
    }
    

    其中的 condition(条件)可以是任意表达式。如果对 condition 求值的结果为 true,则执行 statement1(语句1),否则执行 statement2(语句2)。


    do-while语句

    do-while 语句是一种后测试循环语句,即只有在循环体中的代码执行之后,才会测试出口条件。换句话说,在对条件表达式求值之前,循环体内的代码至少会被执行一次。语法如下:

    do{
        statement
    }while(expression);
    

    while语句

    while 语句属于前测试循环语句,也就是说,在循环体内的代码被执行之前,就会对出口条件求值。因此,循环体内的代码有可能永远不会被执行。语法如下:

    while(expression){
        statement
    }
    

    for语句

    for 语句也是一种前测试循环语句,但它具有在执行循环之前初始化变量和定义循环后要执行的代码能力。语法如下:

    for(initialization;expression;post-loop-expression){
        statement
    }
    

    在 for 循环的变量初始化表达式中,也可以不使用 var 关键字,该变量的初始化可以在外部执行。

    for 语句中的初始化表达式、控制表达式和循环后表达式都是可选的,如果将三个表达式全部省略,就会创建一个无限循环。


    for-in语句

    for-in 语句是一种精准的迭代语句,可以用来枚举对象的属性。语法如下:

    for(property in expression){
        statement
    }
    

    如果表示要迭代的对象的变量值为 null 或 undefined,for-in 语句会抛出错误。ES5 更正了这一行为,对这种情况不再抛出错误,而只是不执行循环体。为了保证最大限度的兼容性,建议在使用 for-in 循环之前,先检测该对象的值不是 null 或 undefined。


    label语句

    使用 label 语句可以在代码中添加标签,以便将来使用。语法如下:

    label: statement
    

    示例:

    start:for(var i = 0; i < count; i++){
        alert(i);
    }
    

    break和continue语句

    break 和 continue 语句用于在循环中精确地控制代码的执行。

    break 语句会立即退出循环,强制继续执行循环后面的语句。continue 语句虽然也是立即退出循环,但退出循环后会从循环的顶部继续执行。


    with语句

    with 语句的作用是将代码的作用域设置到一个特定的对象中。语法如下:

    with(expression)   statement;
    

    定义 with 语句的目的主要是为了简化多次编写同一个对象的工作,实例:

    var qs = location.search.substring(1);
    var hostName = location.hostname;
    var url = location.href;
    

    以上代码可以使用 with 语句改写成如下:

    with(location){
        var qs = search.substring(1);
        var hostName = hostname;
        var url = href;
    }
    

    注意:在严格模式下不允许使用 with 语句,否则将视为语法错误。

    由于大量使用 with 语句会导致性能下降,同时也会给调试代码造成困难,因此在开发大型应用程序时,不建议使用 with 语句。


    switch语句

    switch 语句与 if 语句的关系最为密切。语法如下:

    switch(expression){
        case value:statement
            break;
        case value:statement
            break;
        case value:statement
            break;
        default:statement
    }
    

    含义为:如果表达式等于这个值(value),则执行后面的语句。而 break 关键字会导致代码执行流跳出 switch 语句。如果省略 break,就会导致执行完当前 case 后,继续执行下一个 case。最后的 default 关键字则用于在表达式不匹配前面任何一种情形时,执行机动代码(也相当于 else 语句)。

    switch 语句在比较值时使用的是全等操作符,因此不会发生类型转换。


    函数

    通过函数可以封装任意多条语句,而且可以在任何地方、任何时候调用执行。

    ES 中的函数使用 function 关键字来声明,后跟一组参数以及函数体。语法如下:

    function functionName(arg0,arg1,...,argN){
        statements
    }
    

    ES 中的函数在定义时不必指定是否返回值。实际上,任何函数在任何时候都可以通过 return 语句后跟要返回的值来实现返回值。示例如下:

    function sum(num1,num2){
        return num1 + num2;
    }
    

    这个 sum() 函数的作用是把两个值加起来返回一个结果。

    调用这个函数的代码如下:

    var result = sum(5,10);
    

    这个函数会在执行完 return 语句之后停止并立即退出,因此位于 return 之后的任何代码将不会被执行。

    另外,return 语句也可以不带有任何返回值。函数在停止执行后将返回 undefined 值,这种用法一般用在需要提前停止函数执行而又不需要返回值的情况下使用。

    严格模式对函数有一些限制,如下:

    • 不能把函数命名为 eval 或 arguments;
    • 不能把参数命名为 eval 或 arguments;
    • 不能出现两个命名参数同名的情况。
      如发生以上情况,将导致语法错误,无法执行。

    理解参数

    在函数体内可以通过 arguments 对象来访问这个参数数组,从而获取传递给函数的每一个参数。

    ES 函数一个重要特定:命名的参数只提供便利,但不是必需的。

    没有传递值的命名参数将自动被赋予 undefined 值。

    ES 中的所有参数传递的都是值,不可能通过引用传递参数。


    没有重载

    ES 函数不能像传统意义上那样实现重载。ES 函数没有签名,因为其参数是由包含零或多个值的数组来表示的。而没有函数签名,真正的重载是不可能做到的。

    如果在 ES 中定义了两个名字相同的函数,则该名字只属于后定义的函数。


    小结

    下面简要总结 ES 中的基本要素

    • ES 中的基本数据类型包括 Undefined、Null、Boolean、Numbe 和 String。
    • ES 没有为整数和浮点数值分别定义不同的数据类型,Number 类型可以用于表示所有数值。
    • ES 中有一种复杂的数据类型:Object 类型,该类型是所有对象的基础类型。
    • 严格模式为这门语言中容易出错的地方施加了限制。
    • ES 提供了很多基本操作符:算术操作符、布尔操作符、关系操作符、相等操作符及赋值操作符等等。
    • ES 中有很多流控制语句,如:if 语句、for 语句和 switch 语句等等。
    • 无须指定函数的返回值,因为 ES 函数都可以在任何时候返回任何值。
    • 未指定返回值的函数返回的是一个特殊的 undefined 值。
    • ES 中没有函数签名的概念,因为其函数参数是以一个包含零或多个值的数组的形式传递的。
    • 可以向 ES 函数传递任意数量的参数,并可以通过 arguments 对象来访问这些参数。
    • 由于不存在函数签名,ES 函数不能重载。

    变量、作用域和内存问题

    基本类型和引用类型的值

    ES变量可能包含两种不同数据类型的值:基本类型值和引用类型值

    基本类型值指的是简单的数据段,而引用类型值指那些可能由多个值构成的对象。

    引用类型的值是保存在内存中的对象。JavaScript不允许直接访问内存中的位置,也就是说不能直接操作对象的内存空间。在操作对象时,实际上是在操  作对象的引用而不是实际的对象。因此,引用类型的值是按引用访问的。


    动态的属性

    定义基本类型值和引用类型值的方式是类似的:创建一个变量并为该变量赋值。

    我们不能给基本类型的值添加属性,只能给引用类型值动态地添加属性。


    复制变量值

    除了保存的方式不同之外,在从一个变量向另一个变量复制基本类型值和引用类型值时,也存在不同。如果从一个变量向另一个变量复制基本类型的值,会在变量对象上创建一个新值,然后把该值复制到为新变量分配的位置上,例如:

    var num1 = 5;
    var num2 = num1;
    

    以上代码中,num1中保存的值为5.当使用num1的值来初始化num2时,num2中也保存了值5.但num2中的5与num1的5是完全独立的,该值只是num1中5的一个副本。此后,这两个变量可以参与任何操作而不会相互影响。

    当从一个变量向另一个变量复制引用类型的值时,同样也会将存储在变量对象中的值复制一份到为新变量分配的空间中。不同的是,这个值的副本实际上是一个指针,而这个指针指向存储在堆中的一个对象。复制操作结束后,两个变量实际上将引用同一个对象。因此,改变其中一个变量,就会音响另一个变量,例如:

    var obj1 = new Object();
    var obj2 = obj1;
    obj1.name = "zww"
    alert(obj2.name);    //"zww"
    

    以上代码中,变量obj1保存了一个对象的新实例。然后这个值被复制到了obj2中。obj1和obj2都指向同一个对象。这样,当为obj1添加name属性后,可以通过obj2来访问这个属性。


    传递参数

    ES中所有函数的参数都是按值传递的。也就是说,把函数外部的值复制给函数内部的参数,就和把值从一个变量复制到另一个变量一样。例子:

    function addTen(num){
        num += 10;
        return num;
    }
    var count = 20;
    var result = addTen(count);
    alert(count);    //20,没有变化
    alert(result);    //30
    

    以上代码中,函数addTen()有一个参数num,而参数实际上是函数的局部变量。在调用函数时,变量count作用参数被传递给函数,这个变量值为20。于是数值20被复制给参数num以便在addTen()中使用。在函数内部,参数num值被加上了10,这一变化不会影响函数外部的count变量。


    检测类型

    要检测一个变量是不是基本数据类型,typeof操作符是最佳的工具。

    typeof操作符是确定一个变量是字符串、数值、布尔值,还是undefined的最佳工具,如果变量值是null或对象,则会返回"object"。

    var s = "zww";
    var b = true;
    var i = 19;
    var u;
    var n = null;
    var o = new Object();
    alert(typeof s);    //string
    alert(typeof b);    //boolean
    alert(typeof i);    //number
    alert(typeof u);    //undefined
    alert(typeof n);    //object
    alert(typeof o);    //object
    

    因此,ES提供了instanceof操作符,语法如下:

    result = variable instanceof constructor
    

    如果变量是给定引用类型的实例,那么instanceof操作符就会返回true。


    执行环境及作用域

    执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。

    每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。

    当代码在一个环境中执行时,会创建变量对象的一个作用域链。作用域链的用途是保证对执行环境有权访问的所有变量和函数的有序访问。


    延长作用域链

    虽然执行环境的类型总共只有两种:全局和局部(函数),但还是有其他办法延长作用域链的。因为有些语句可以在作用域链的前端临时增加一个变量对象,该变量对象会在代码执行后被移除。在两种情况下会发生这种情况。具体的说就是当执行流进入下列任何一个语句时,作用域链就会得到加长:

    • try-catch语句的catch块;
    • with语句。

    这两个语句都会在作用域链的前端添加一个变量对象。对with语句来说,会将指定的对象添加到作用域链中。对catch来说,会创建一个新的变量对象,其中包含的是被抛出的错误对象的声明。


    没有块级作用域

    声明变量

    使用var声明的变量会自动被添加到最接近的环境中。在函数内部,最接近的环境就是函数的局部环境;在with语句中,最接近的环境是函数环境。如果初始化变量时没有使用var声明,该变量会自动被添加到全局环境。例如:

    function add(num1,num2){
        var sum = num1 + num2;
        return sum;
    }
    var result = add(10,20);    //30
    alert(sum);    //由于sum不是有效的变量,因此会导致错误
    

    以上代码中,函数add()定义了一个名为sum的局部变量,该变量包含加法的操作结果。虽然结果值从函数中返回了,但变量sum在函数外部是访问不到的。如果省略例子中var关键字,那么add()执行完毕后,sum也将可以访问到:

    function add(num1,num2){
        sum = num1 + num2;
        return sum;
    }
    var result = add(10,20);    //30
    alert(sum);    //30
    

    查询标识符

    当在某个环境中为了读取或写入而引用一个标识符时,必须通过搜索来确定该标识符实际代表什么。搜索过程从作用域链的前端开始,向上逐级查询与给定名字匹配的标识符。如果在局部环境中找到了该标识符,就停止搜索,变量就绪。如果在局部环境中没有找到,则继续沿作用域链向上搜索。搜索过程将一直追溯到全局环境的变量对象。如果在全局环境中没有找到标识符,则意味着该变量尚未声明。


    垃圾收集

    JavaScript具有自动垃圾收集机制,也就是说,执行环境会负责管理代码执行过程中使用的内存。

    标记清除

    JavaScript中最常用的垃圾收集方式是标记清除。当变量进入环境时,就将这个变量标记为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。而当变量离开环境时,则讲其标记为“离开环境”。


    引用计数

    另一种不太常见的垃圾收集策略叫引用计数。含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1。如果同一个值又被赋给另一个变量,则该值的引用次数加1。相反,如果包含对这个值引用的变量又取得另外一个值,则这个值的引用次数减1。当这个值的引用次数变为0时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间回收回来。这样,当垃圾收集器下次再运行时,它就会释放那些引用次数为零的值所占用的内存。


    性能问题

    垃圾收集器是周期性运行的。

    IE的垃圾收集器是根据内存分配量运行的,具体一点说就是256个变量、4096个对象(或数组)字面量和数组元素(slot)或者64KB的字符串,达到上述任何一个临界值,垃圾收集器就会运行。

    在IE中,调用window.CollectGarbage()方法会立即执行垃圾收集。


    管理内存

    JavaScript在进行内存管理及垃圾收集时,最主要的一个问题是,分配给web浏览器的可用内存数量通常要比分配给桌面应用程序的少。这样做的目的主要是出于安全方面考虑,目的是防止运行JavaScript的网页耗尽全部系统内存而导致系统崩溃。内存限制问题不仅会影响给变量分配内存,同时还会影响调用栈以及在一个线程中能够同时执行的语句数量。

    因此,确保占用最少的内存可以让页面获得更好的性能。而优化内存占用的最佳方式,就是为执行中的代码只保存必要的数据。一旦数据不再有用,最好通过将其值设置为null来释放其引用,这个做法叫做解除引用。这一做法适用于大多数全局变量和全局对象的属性。局部变量会在它们离开执行环境时自动被解除引用。


    小结

    JavaScript变量可以用来保存两种类型的值:基本类型值和引用类型值。

    基本类型值和引用类型值具有以下特点:

    • 基本类型值在内存中占据固定大小的空间,因此被保存在栈内存中;
    • 从一个变量向另一个变量复制基本类型的值,会创建这个值的一个副本;
    • 引用类型的值是对象,保存在堆内存中;
    • 包含引用类型值得变量实际上包含的并不是对象本身,而是一个指向该对象的指针;
    • 从一个变量向另一个变量复制引用类型的值,复制的其实是指针,因此两个变量最终都指向同一个对象;
    • 确定一个值是哪种基本类型可以使用typeof操作符,而确定一个值是哪种引用类型可以使用instanceof操作符。

    所有变量都存在于一个执行环境(也称作用域)当中,这个执行环境决定了变量的生命周期,以及哪一部分代码可以访问其中的变量。

    以下是关于执行环境的几点总结:

    • 执行环境有全局执行环境和函数执行环境之分;
    • 每次进入一个新执行环境,都会创建一个用于搜索变量和函数的作用域链;
    • 函数的局部环境不仅有权访问函数作用域中的变量,而且还有权访问其包含(父)环境,乃至全局环境;
    • 全局环境只能访问在全局环境中定义的变量和函数,而不能直接访问局部环境中的任何数据;
    • 变量的执行环境有助于确定应该何时释放内存。

    JavaScript是一门具有自动垃圾收集机制的编程语言,开发人员不必关心内存分配和回收问题,可以对JavaScript的垃圾收集例程做如下总结:

    • 离开作用域的值将被自动标记为可以回收,因此将在垃圾收集期间被删除;
    • “标记清除”是目前主流的垃圾收集算法,这种算法思想是给当前不使用的值加上标记,然后再回收其内存;
    • 另一个垃圾收集算法是“引用计数”,这种算法思想是跟踪记录所有值被引用的次数。JavaScript引擎目前都不再使用这种算法;
    • 当代码中存在循环引用现象时,“引用计数”算法就会导致问题;
    • 解除变量的引用不仅有助于消除循环引用现象,而且对垃圾收集也有好处。为了确保有效地回收内存,应该及时解除不再使用的全局对象、全局对象属性以及循环引用变量的引用。

    引用类型

    Object类型

    创建 Object 实例的两种方式:

    1. 使用new操作符后跟Object构造函数,例如:
    var person = new Object();
    person.name = "ZWW";
    person.age = 22;
    
    1. 使用对象字面量表示法,例如:
    var person = {
        name: "ZWW",
        age: 22
    };
    

    在对象字面量中,使用逗号来分隔不同的属性,并且在最后一个属性后面不能添加逗号。

    在使用对象字面量时,属性名也可以使用字符串;如果留空其花括号,则可以定义只包含默认属性和方法的对象。


    Array类型

    创建数组的两种方式:

    1. Array构造函数
    var arr1 = new Array()
    var arr2 = new Array(10)
    var arr3 = new Array('a', 'b')
    
    console.log(arr1);    // []
    console.log(arr2);    // [empty × 10]
    console.log(arr3);    // ["a", "b"]
    
    1. 数组字面量
    var arr4 = []
    var arr5 = [1, 2, 3]
    

    检测数组

    // 1. instanceof
    var arr = new Array()
    console.log(arr instanceof Array);    // true
    
    // 2. Array.isArray()
    console.log(Array.isArray(arr));    // true
    

    转换方法

    var arr = [1, 2, 3]
    console.log(arr.toString());    // 1,2,3
    console.log(arr.toLocaleString());    // 1,2,3
    
    console.log(arr.valueOf());    // [1, 2, 3]
    alert(arr.valueOf())    // 1,2,3
    

    注意:如果数组中某一项值为 null / undefined,那么该值在 join()、toString()、valueOf()、toLocaleString()方法返回的结果为空字符串。

    栈方法

    栈是一种后进先出的数据结构。

    ES 提供了 push()、pop() 方法来实现类似栈的行为。

    var arr = [1, 2, 3]
    var arr1 = arr.push(4)
    console.log(arr1);    // 4
    console.log(arr);    // [1, 2, 3, 4]
    
    var arr2 = arr.pop()
    console.log(arr2);    // 4
    console.log(arr);    // [1, 2, 3]
    

    注意:push()、pop() 方法返回的是被添加 / 删除的元素,要改变原数组。

    队列方法

    队列是先进先出的数据结构。

    ES 提供了 shift()、unshift() 方法。

    var arr = [1, 2, 3]
    var arr1 = arr.shift()
    console.log(arr1);    // 1
    console.log(arr);    // [2, 3]
    
    var arr2 = arr.unshift(9)
    console.log(arr2);    // 3
    console.log(arr);    // [9, 2, 3]
    

    注意:shift() 方法返回被删除的元素并改变原数组,unshift() 方法返回被添加后的数组长度并改变原数组。

    重排序方法

    reverse()、sort() 可以对数组进行重排序。

    var arr = [1, 2, 3]
    var arr1 = arr.reverse()
    console.log(arr1);    // [3, 2, 1]
    console.log(arr);    // [3, 2, 1]
    
    var arr = [1, 2, 3]
    var arr1 = arr.sort((a, b) => {
      return b - a
    })
    console.log(arr);    // [3, 2, 1]
    console.log(arr1);    // [3, 2, 1]
    

    reverse()、sort()返回的是排序后的数组,改变原数组。

    操作方法

    concat() 方法可以基于当前数组中所有项创建一个新数组。

    var arr = [1, 2, 3]
    var arr1 = arr.concat()
    var arr2 = arr.concat(4, 5)
    var arr3 = arr.concat(4, 5, [6])
    
    console.log(arr1);    // [1, 2, 3]
    console.log(arr2);    // [1, 2, 3, 4, 5]
    console.log(arr3);    // [1, 2, 3, 4, 5, 6]
    

    concat() 方法返回一个新数组,不改变原数组。

    slice() 方法基于当前数组中的一或多个项创建一个新数组。

    var arr = [1, 2, 3]
    var arr1 = arr.slice(1)
    var arr2 = arr.slice(1, 2)
    
    console.log(arr1);    // [2, 3]
    console.log(arr2);    // [2]
    

    slice() 方法返回一个新数组,不改变原数组。

    splice() 方法可以进行删除、插入、替换操作。

  • 相关阅读:
    Spring格式化注解
    Mysql 慢查询和慢查询日志分析
    jQuery中的end()方法
    webService学习笔记
    让Hibernate和触发器协同工作
    JavaScript中的setInterval用法
    jQuery中事情的动态绑定
    mongoDB如何处理多对多关系
    新生儿信息管理系统在线帮助
    MySQL数据库安装与配置详解
  • 原文地址:https://www.cnblogs.com/LqZww/p/13636989.html
Copyright © 2020-2023  润新知