《JavaScript高级》

JavaScript 简介

  1. JavaScript 是一种专为与网页交互而设计的脚本语言,由下列三个不同的部分组成:
    (1) ECMAScript,由 ECMA-262 定义,提供核心语言功能;
    (2) 文档对象模型(DOM),提供访问和操作网页内容的方法和接口;
    (3) 浏览器对象模型(BOM),提供与浏览器交互的方法和接口。

在 HTML 中使用 JavaScript

  1. 把 JavaScript 插入到 HTML 页面中要使用 <script> 元素。使用这个元素可以把 JavaScript 嵌入到 HTML 页面中,让脚本与标记混合在一起;也可以包含外部的 JavaScript 文件。而我们需要注意的地方有:
    (1) 在包含外部 JavaScript 文件时,必须将 src 属性设置为指向相应文件的 URL。而这个文件既可以是与包含它的页面位于同一个服务器上的文件,也可以是其他任何域中的文件。
    (2) 所有 <script> 元素都会按照它们在页面中出现的先后顺序依次被解析。在不使用 defer 和 async 属性的情况下,只有在解析完前面 <script> 元素中的代码之后,才会开始解析后面 <script> 元素中的代码。
    (3) 由于浏览器会先解析完不使用 defer 属性的 <script> 元素中的代码,然后再解析后面的内容,所以一般应该把 <script> 元素放在页面最后,即主要内容后面, </body> 标签前面。
    (4) 使用 defer 属性可以让脚本在文档完全呈现之后再执行。延迟脚本总是按照指定它们的顺序执行。
    (5) 使用 async 属性可以表示当前脚本不必等待其他脚本,也不必阻塞文档呈现。不能保证异步脚本按照它们在页面中出现的顺序执行。
  2. 另外,使用 <noscript> 元素可以指定在不支持脚本的浏览器中显示的替代内容。但在启用了脚本的情况下,浏览器不会显示 <noscript> 元素中的任何内容。

    基本概念

  3. 严格模式: 为 JavaScript 定义了一种不同的解析与执行模型。在严格模式下,ECMAScript3 中的一些不确定的行为将得到处理,而且对某些不安全的操作会抛出错误。
    (1) 在顶部添加代码 “use strict”; 整个脚本中启用严格模式
    (2) 指定函数在严格模式下执行: function doSomething(){ "use strict"; ...函数体 }

  4. 5种简单数据类型: Undefined、Null、Boolean、Number、String;1种复杂数据类型:Object。ES6新增的一种原始数据类型: Symbol(符号、标记)。凡是属性名是 Symbol 类型的,就都是独一无二的,Symbol 值通过函数生成: let s = Symbol() || let s = Symbol("s1");Symbol没有包装类型,不能使用 new 操作符。
  5. typeof 操作符:检测给定变量的数据类型,是操作符不是函数。用法 typeof(value) 返回:
    (1) “undefined” : value 未定义;
    (2) “boolean”: value 是布尔值;
    (3) “string”: value 是字符串;
    (4) “number”: value 是数值;
    (5) “object”: value 是对象或 null;
    (6) “function”: value 是函数。
    (老版本浏览器会将正则表达式也返回”function”,新版会返回”object”)
  6. 位操作符:按内存中表示数值的位来操作数值,速度最快。
    (1) 按位非(~):执行按位非的结果就是返回数值的反码;
    (2) 按位与(&):将两个数值的每一位对齐,两个数值的对应位都是1时才返回1,任何一位是0,结果就是0;
    (3) 按位或(|):将两个数值的每一位对齐,两个数值的对应位有一个位是1的就返回1,只有在两个位都是0的情况下才返回0;
    (4) 按位异或(^):将两个数值得每一位对齐,两个数值对应位上只有一个1时才返回1,如果对应的两位都是1或都是0,则返回0;
    (5) 左移(<<): 将数值的所有位向左移动指定的位数,以0来填充右侧多出的空位。左移不会影响操作数的符号位;
    (6) 有符号的右移(>>):将数值向右移动,但保留符号位,以0来填充左侧多出的空位;
    (7) 无符号右移(>>>):将数值的所有32位都向右移动,正数的无符号右移和有符号右移结果相同,负数无符号右移后的结果非常大,因为负数以其绝对值得二进制补码形式表示。
  7. 关系操作符: >、 < 、 >= 、 <=对两个值进行比较
    (1) 如果两个操作数都是数值,则执行数值比较
    (2) 如果两个操作数都是字符串,则比较两个字符串对应的字符编码值
    (3) 如果一个操作数是数值,则将另一个操作数转换为一个数值,然后执行数值比较。
    (4) 如果一个操作数是对象,则调用这个对象的 valueOf() 方法,用得到的结果按照前面的规则执行比较。如果对象没有 valueOf 方法,则调用 toString() 方法,并且得到的结果根据前面的规则执行比较
    (5) 如果一个操作数是布尔值,则先将其转换为数值,然后再执行比较
  8. 相等操作符: == 和 != 先转换再比较; === 和 !== 仅比较而不转换
    (1) 如果有一个操作数是布尔值,则在比较相等性之前先将其转换为数值—- false 转换为 0,而 true 转换为 1.
    (2) 如果一个操作数是字符串,另一个操作数是数值,在比较相等性之前先将字符串转换为数值
    (3) 如果一个操作数是对象,另一个操作数不是,则调用对象的 valueOf() 方法,用得到的基本类型值按照前面的规则进行比较
    (4) null 和 undefined 是相等的。
    (5) 要比较相等性之前,不能将 null 和 undefined 转换成其他任何值
    (6) 如果有一个操作数是NaN,则相等操作符返回 false,而不相等操作符返回 true。重要提示:即使两个操作数都是NaN,相等操作符也返回 false;因为按照规则,NaN 不等于 NaN
    (7) 如果两个操作数都是对象,则比较它们是不是同一个对象。如果两个操作数都指向同一个对象,则相等操作符返回 true; 否则,返回 false
  9. 与其他语言不同,ECMScript 没有为整数和浮点数值分别定义不同的数据类型,Number 类型可用于表示所有数值。
  10. 严格模式为这门语言中容易出错的地方施加了限制
  11. 无需指定函数的返回值,因为任何 ECMScript 函数都可以在任何时候返回任何值;实际上,未指定返回值得函数返回的是一个特殊的 undefined 值。
  12. ECMScript 中也没有函数签名的概念,因为其函数参数是以一个包含零或多个值的数组的形式传递的;可以向 ECMScript 函数传递任意数量的参数,并且可以通过 arguments 对象来访问这些参数;由于不存在函数签名的特性,ECMScript 函数不能重载。

变量、作用域和内存问题

  1. 基本类型值在内存中占据固定大小的空间,因此被保存在栈内存中
  2. 从一个变量向另一个变量复制基本类型的值,会创建这个值的一个副本
  3. 引用类型的值是对象,保存在堆内存中
  4. 包含引用类型值的变量实际上包含的并不是对象本身,而是一个指向该对象的指针
  5. 从一个变量向另一个变量复制引用类型的值,复制的其实是指针,因此两个变量最终都指向一个对象
  6. 确定一个值是哪种基本类型可以用 typeof 操作符,而确定一个值是哪种引用类型可以使用 instanceof 操作符
  7. 执行环境有全局执行环境和函数执行环境之分
  8. 每次进入一个新执行环境,都会创建一个用于搜索变量和函数的作用域链
  9. 函数的局部环境不仅有权访问函数作用域中的变量,而且有权方位其包含(父)环境,乃至全局环境
  10. 全局环境只能访问在全局环境中定义的变量和函数,而不能直接访问局部环境中的任何数据
  11. 变量的执行环境有助于确定应该何时释放内存
  12. 离开作用域的值将被自动标记为可以回收,因此将在垃圾收集期间被删除
  13. “标记清除”是目前主流的垃圾收集算法,这种算法的思想是给当前不使用的值加上标记,然后再回收其内存
  14. 另一种垃圾收集算法是“引用计数”,这种算法的思想是跟踪记录所有值被引用的次数。JavaScript 引擎目前都不再使用这种算法;但在 IE 中访问非原生 JavaScript 对象(如 DOM 元素)时,这种算法仍然可能会导致问题
  15. 当代码中存在循环引用现象时,“引用计数”算法就会导致问题
  16. 解除变量的引用不仅有助于消除循环引用现象,而且对垃圾收集也有好处。为了确保有效地回收内存,应该及时解除不再使用的全局对象、全局对象属性以及循环引用变量的引用

引用类型

  1. Object 类型: 一个基础类型,其他所有类型都从 Object 继承了基本的行为
  2. Array 数组类型: 一组值的有序列表
    (1) 数组的 length 属性很有特点—-不是只读的。因此,通过设置这个属性,可以从数组的末尾移除项或向数组中添加新项
    (2) 检测数组: 对于一个网页或者一个全局作用域而言,使用 instanceof 操作符;如果网页中包含多个框架,存在两个以上不同的全局执行环境,从而存在两个以上不同版本的 Array 构造函数,ES5 新增了 Array.isArray() 方法来判断
    (3) 转换方法: toString() 方法返回由数组中每个值的字符串形式拼接而成的一个以逗号分隔的字符串; valueOf() 返回的还是数组;toLocaleString() 经常也会返回与 toString() 和 valueOf() 方法相同的值,但也不总是如此,它会调用每一项的 toLocalString() 方法; join() 方法只接收一个参数,即用作分隔符的字符串,然后返回包含所有数组项的字符串。如果数组中的某一项的值是 null 或者 undefined ,==,那么该值在上述方法返回的结果中已以空字符串表示
  3. 栈方法
    (1) push() 方法可以接收任意数量的参数,把它们逐个添加到数组末尾,并返回修改后的数组长度
    (2) pop() 方法从数组末尾移除最后一项,减少数组的 length 值,然后返回移除的项
  4. 队列方法
    (1) shift() 方法移除数组中的第一个项并返回该项,同时将数组长度减一
    (2) unshift() 方法在数组前端添加任意个项并返回新数组的长度
  5. 重排序方法
    (1) reverse() 方法会反转数组项的顺序
    (2) sort() 默认情况下,该方法按升序排列数组项—-即最小的值位于最前面,最大的值排在最后面。sort() 方法会调用每个数组项的 toString() 转型方法,然后比较得到的字符串,以确定如何排序。即使数组中的每一项都是数值,sort() 方法比较的也是字符串。

    1
    2
    3
    var arr = [0,1,5,10,15]
    arr.sort()
    console.log(arr) // 0,1,10,15,5

    进行字符串比较时,“10”位于“5”的前面。因此 sort() 方法可以接收一个比较函数作为参数,以便我们指定哪个值位于哪个值的前面。比较函数接收两个参数,如果第一个参数应该位于第二个之前则返回一个负数,如果两个参数相等则返回0,如果第一个参数应该位于第二个之后则返回一个正数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function compare(value1,value2){
    if(value1 < value2){
    return 1;
    } else if(value1 > value2){
    return -1;
    } else {
    return 0;
    }
    }

    // 数值类型或者其 valueOf() 方法会返回数值类型的对象类型,更简单的比较函数
    function compare(value1,value2){
    return value2 - value1; // 降序
    return value1 - value2; // 升序
    }

    (3) reverse() 和 sort() 方法的返回值是经过排序之后的数组

  6. 操作方法
    (1) concat() 方法会先创建当前数组的一个副本,然后将接受到的参数添加到这个副本的末尾,最后返回新构建的数组。原来的数组值保持不变
    (2) slice() 基于当前数组中的一或多个项创建一个新数组,可以接收一或两个参数,即要返回项的起始和结束位置。一个参数:返回从该参数指定位置开始到当前数组末尾的所有项;两个参数:返回起始和结束位置之间的项—-但不包括结束位置的项。参数是负数,则用数组长度加上改数来确定相应的位置。slice() 方法不会影响原始数组
    (3) splice() 主要用途是向数组的中部插入项。
    删除: 指定两个参数:要删除的第一项的位置和要删除的项数
    插入: 提供3个参数:起始位置、0(要删除的项数)和需要插入的项。插入多项可以再传入第四、五以至任意多个项
    替换: 指定3个参数:起始位置、要删除的项数和要插入任意数量的项
    splice() 方法始终返回一个数组,该数组包含从原始数组中删除的项
  7. 位置方法
    (1) indexOf() 和 lastIndexOf() 这两个方法都接收两个参数,要查找的项和(可选的)表示查找起点位置的索引。区别是indexOf()方法从数组的开头开始向后查找,lastIndexOf()从末尾开始向前查找。都返回要查找的项在数组中的位置,没找到的情况下返回 -1
  8. 迭代方法
    (1) every():对数组中的每一项运行给定函数,如果该函数对每一项都返回true,则返回true
    (2) filter():对数组中的每一项运行给定函数,返回该函数会返回true的项组成的数组
    (3) foreEach():对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组
    (4) map():对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组
    (5) some():对数组中的每一项运行给定函数,如果该函数对任一项返回true,则返回true
    以上方法都不会修改数组中的包含的值
  9. 缩小方法
    (1) reduce():从数组的第一项开始,逐个遍历到最后。迭代数组的所有项,然后构建一个最终返回的值
    (2) reduceRight(): 从数组的最后一项开始,逐个遍历到最第一项。迭代数组的所有项,然后构建一个最终返回的值
    这两个方法都接收两个参数:一个在每一项上调用的函数和(可选的)作为缩小基础的初始值。传入函数接收4个参数:前一个值、当前值、项的索引和数组对象。这个函数返回的任何值都会作为第一个参数自动传给下一项
  10. RegExp 实例方法
    (1) exec(): 接收一个参数,即要应用模式的字符串,然后返回包含第一个匹配信息的数组;或者在没有匹配项的情况下返回 null。返回的数组包含两个额外的属性:index和input。其中,index 表示匹配项在字符创中的位置,而 input表示应用正则表达式的字符串
    (2) test(): 接收一个字符串参数。在模式与该参数匹配的情况下返回 true;否则返回 false
  11. 函数声明与函数表达式
    解析器在向执行环境中加载数据时,对函数声明和函数表达式并非一视同仁。解析器会率先读取函数声明,并使其在执行任何代码之前可用(可以访问);至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解释执行。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 可以正常运行
    console.log(sum(10,10))
    function sum(num1,num2){
    return num1 + num2
    }

    // 不能运行
    console.log(sum(10,10))
    var sum = function(num1,num2){
    return num1 + num2
    }
  12. 作为值的函数
    不仅可以像传递参数一样把一个函数传递给另一个函数,而且可以将一个函数作为另一个函数的结果返回。根据某个对象属性对对象数组进行排序

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function createdComparisonFunction(propertyName){
    return function(object1,object2){
    let value1 = object1[propertyName];
    let value2 = object2[propertyName];
    if(value1 < value2){
    return -1;
    } else if(value1 > value2){
    return 1;
    } else {
    return 0;
    }
    }
    }
    let data = [{age:10},{age:5},{age:20}];
    data.sort(createdComparisonFunction("age"));
  13. 函数内部属性
    (1) arguments:一个类数组对象,包含着传入函数中的所有参数。这个对象有一个名叫 callee 的属性,该属性是一个指针,指向拥有这个 arguments 对象的函数

    1
    2
    3
    4
    5
    6
    7
    8
    function factorial(num){
    if(num <= 1){
    return 1;
    } else {
    return num * factorial(num - 1) // 与函数名耦合
    return num * arguments.callee(num - 1) // 消除耦合
    }
    }

    (2) this: 引用的是函数据以执行的环境对象—-或者也可以说是 this 值(当在网页的全局作用域中调用函数时,this 对象引用的就是 window)
    (3) caller: 这个属性中保存着调用当前函数的函数的引用,如果是在全局作用域中调用当前函数,它的值为 null

  14. 函数属性和方法
    (1) length: 该属性表示函数希望接收的命名参数的个数
    (2) prototype: 保存他们所有实例方法
    每个函数都包含两个非继承而来的方法: apply() 和 call()。用途都是在特定的作用域中调用函数,实际上等于设置函数体内 this 对象的值,apply() 方法接收两个参数:一个是在其中运行函数的作用域,另一个是参数数组。其中,第二个参数可以是 Array 实例,也可以是 arguments 对象。和 call() 方法作用相同,但 call() 方法接收的第二个参数必须逐个列举出出来,不能传 arguments

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function sum(num1,num2){
    return num1 + num2;
    }
    function callSum1(num1,num2){
    return sum.apply(this, arguments); // 传入 arguments
    return sum.call(this, arguments); // call() 不能这样写
    }
    function callSum2(num1,num2){
    return sum.apply(this,[num1, num2]); // 传入数组
    return sum.call(this,num1, num2); // call() 只能这样写
    }

    传递参数并非 apply() 和 call() 真正的用武之地;它们真正强大的地方是能够扩充函数赖以运行的作用域

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    window.color = "red";
    var o = { color: "blue" };
    function sayColor(){
    console.log(this.color);
    }
    sayColor(); // red
    sayColor.call(this); // red
    sayColor.call(window); // red
    sayColor.call(o); // blue
    // 好处:对象不需要与方法有任何耦合关系

    var objectSayColor = sayColor.bind(0); // bind() 方法会创建一个函数的实例,其 this 值会被绑定到传个 bind() 函数的值
    objectSayColor(); // blue
  15. 基本包装类型 Boolean、Number和String
    (1) 每个包装类型都映射到同名的基本类型
    (2) 在读取模式下访问基本类型值时,就会创建对应的基本包装类型的一个对象,从而方便了数据操作
    (3) 操作基本类型值的语句一经执行完毕,就会立即销毁新创建的包装对象

  16. String 类型
    (1) charAt(): 接收一个参数,即基于0的字符位置。以单字符字符串的形式返回给定位置那个字符
    (2) charCodeAt(): 接收一个参数,即基于0的字符位置。以单字符字符串的形式返回给定位置那个字符编码
    (3) concat(): 用于将一个或多个字符串拼接起来,返回拼接得到的新字符串。原始值不改变
    (4) slice()、substr()、substring(): 返回被操作字符串的一个子字符串,接收一或两个参数。第一个参数指定子字符串的开始位置,第二个参数(在指定的情况下)表示子字符串到哪里结束。不会修改字符串本身的值,具体的不同MDN
    (5) indexOf()、lastIndexOf(): 从一个字符串中搜索给定的子字符串,然后返回子字符串的位置(如果没有找到改字字符串,则返回-1)。区别是前者从字符串的开头向后搜索,后者从字符串的末尾向前搜索
    (6) trim(): 创建一个字符串的副本,删除前置及后缀的所有空格,然后返回结果。不改变原始字符串
    (7) toLowerCase()、toLocaleLowerCase()、toUpperCase()、toLocaleUpperCase(): 大小写转换方法
    (8) match(): 模式匹配方法,本质上与调用 regExp 的 exec() 方法相同
    (9) search(): 参数与 match() 参数相同:由字符串或 RegExp 对象指定的一个正则表达式。该方法返回字符串中第一个匹配项的索引;如果没有找到匹配项,则返回-1。而且该方法始终是从字符串开头向后查找
    (10) replace(): 接收两个参数:第一个参数可以是一个 RegExp 对象或者一个字符串(不会被转换成正则表达式),第二个参数可以一个字符串或者一个函数
    (11) split(): 基于指定的分割符将一个字符串分割成多个子字符串,并且将结果放在一个数组中。分隔符可以是字符串,也可以是一个 RegExp 对象。该方法还可以接受可选的第二个参数,用于指定数组的大小。
    (12) localeCompare(): 比较两个字符串,如果字符串在字母表中应该排在字符串参数之前,则返回一个负数(大多是-1);如果字符串等于字符串参数,则返回0;若果字符串在字母表中应该排在字符串之后,则返回一个正数(大多是1)
    (13) fromCharCode(): 接收一或多个字符编码,让后将它们转换成一个字符串
  17. Global 对象
    (1) encodeURI(): 主要用于整个 URI 进行编码,不会对本身属于 URI 的特殊字符进行编码,例如冒号、正斜杠、问号和井字号;对应的解码方法是 decodeURI() 方法
    (2) encodeURIComponent(): 主要用于对 URI 中的某一段进行编码,会对它发现的任何非标准字符进行编码;对应的解码方法是 dencodeURIComponent() 方法
    (3) eval(): 像是一个完整的 ECMAScript 解析器,只接受一个参数,即要执行的 ECMAScript(或 JavaScript)字符串
  18. window 对象
    (1) 在全局作用域中声明的所有变量和函数,都成为了 window 对象的属性
    (2) Web浏览器实现了承担 Global 对象的 window 对象
  19. Math 对象
    (1) 包含的属性大都是数学计算中可能会用到的一些特殊值
    (2) min()和max()方法:用于确定一组数值中的最小值和最大值。都可以接收任意多个数值参数
    (3) ceil(): 向上舍入,它总是将数值向上舍入为最接近的整数
    (4) floor(): 向下舍入,它总是将数值向下舍入为最接近的整数
    (5) round(): 标准舍入,它总是将数值四舍五入为最接近的整数
    (6) random(): 返回大于等于0小于1的一个随机数