系统化学习前端之JavaScript(01)

发布时间 2023-03-24 14:01:39作者: 深巷酒

前言

JavaScript 由三部分组成:ECMAScript,DOM,BOM。

ECMAScript:JavaScript 核心语法,本篇主要介绍 ECMAScript,即 JavaScript 核心语法。

DOM:文件对象模型,主要作用是通过 JavaScript 与 HTML 和 CSS 交互,可参考 DOM 相关

BOM:浏览器对象模型,主要作用是通过 JavaScript 与浏览器交互,可参考 BOM 相关

JavaScript 基础语法

JavaScript 基础语法主要包括数据类型,变量定义,运算符以及流程控制等。

JavaScript 的宿主环境

  1. node

    服务端 JavaScript,又称为 Node.js。

  2. 浏览器 JavaScript 引擎

    客户端 JavaScript,前端领域的 JavaScript,包含 ECMAScript,DOM,BOM,本篇及系列均为客户端 JavaScript。

JavaScript 的引入方式

  1. 内联式

    <script>
    	// js
    </script>
    
  2. 外链式

    <script src="./xxx.js"></script> 
    

JavaScript 注释

  1. 单行注释

    // 单行注释
    
  2. 多行注释

    /**
    * 这是一行注释
    * 这是另一行注释
    */
    

JavaScript 变量和常量

  1. 变量

    JavaScript 定义变量的关键字有两个:varlet

    • 声明变量

      var a;  // 变量名必须由数字、字母、下划线以及$组成,数字不能开头
      let b;
      

      注意:声明变量是必须的,调用未声明的变量会报错 ReferenceError

    • 初始化变量

      a = 1;
      b = '1';
      

      注意:初始化变量也叫做变量赋值,调用为初始化的变量会返回 undefined

    • 定义变量

      var a = 1;
      let b = '1';
      

      注意:定义变量是声明变量和初始化变量的合集,定义变量时省略 varlet 关键字,在非严格模式下,变量会成为全局变量。

    • 调用变量

      console.log('变量', a, b)
      
  2. 常量

    JavaScript 定义常量的关键字为 const

    • 定义常量

      const ISTURE = true; // 常量名尽量使用全大写
      

      注意:声明常量时必须赋值,因此不存在声明常量和初始化常量的行为,常量定义后无法修改,修改会报错 TypeError

    • 调用常量

      console.log('常量', ISTURE)
      

    常量赋值为引用类型时,引用类型数据存于堆空间,引用类型地址(常量名)存于栈空间,堆中数据改变,地址未变,因此,常量可变。

    const OBJ = {
        name: 'Json',
        age: 18
    }
    
    console.log('修改前', OBJ)
    
    OBJ.name = 'Alen'
    
    console.log('修改后', OBJ)
    

JavaScript 数据类型

  1. Number

    JavaScript 没有严格意义上的整型和浮点型的划分,整型和浮点型都是数字类型。

    var a = 1; // 十进制
    var b = 017; // 八进制
    var c = 0x1F; // 十六进制
    
    var d = 12.34; // 小数形式
    var f = 1.2E2; // 指数形式
    
    console.log(a, b, c, d, f)  // 1 15 31 12.34 120
    

    注意:根据输出结果,八进制和十六进制均自动转化为十进制输出了。

  2. String

    字符串类型可以使用 '' 或者 “” 包裹。

    var a = '1';
    var b = "js";
    
  3. Boolean

    布尔类型只有两个值:true 和 false。

    var a = true;
    var b = false;
    
  4. null

    “主动”空类型,可以为变量主动赋空,能够释放变量占用的内存空间。

  5. undefined

    “被动”空类型,声明变量未赋值调用无返回函数的返回值访问对象不存在的属性均为 undefined。

  6. symbol

    symbol 类型的值表示独一无二的值。

    var a = Symbol('1') // '1' 为标识字符串,可不传,Symbol() 生成的每个值都是唯一的
    
    console.log(a) // Symbol(1)
    
    var b = Symbol('1')
    
    console.log(a === b) // false
    
  7. array

    数组类型只能使用 [] 包裹,多个数组元素使用 , 隔开。

    var a = [1, '2', true] // 数组元素可以为任意数据类型
    
  8. function

    JavaScript 中是存在一种数据类型为函数类型的,因为在 JavaScript 中可以通过 函数表达式 来定义函数。

    const sum = function (x, y) {
    	return x + y
    }
    
    console.log(sum)
    
  9. object

    对象类型只能使用 {} 包裹,其属性以键值对的形式存在。

    var a = {
    	name: 'Json', // 属性值可以为任意类型数据,属性名比较特殊,后续详解。
    	age: 16
    }
    
  10. 基本类型

    JavaScript 基本类型数据有 6 种:Number,String,Boolean,null,undefined,symbol。

    基本类型数据传递是按值传递的。

    使用 typeof 可以识别基本类型数据。

    typeof 1 // 'number'
    
    typeof '1' // 'string'
    
    typeof true // 'boolean'
    
    typeof undefined // 'undefined'
    
    typeof null // 'object'
    
    typeof Symbol() // 'symbol'
    

    注意:typeof null 返回 object,可以区分 typeof undefined

  11. 引用类型

    JavaScript 引用类型数据有 3 种:array,function,object。

    引用类型数据传递是按引用传递的。

    使用typeof识别引用类型无法区分 Array 和 Object 类型。

    typeof [1,2,3] // 'object'
    
    typeof function() { console.log('123') } // 'function'
    
    typeof { name: 'Json', age: 19 } // 'object'
    

    使用 instanceofconstructor 识别引用类型数据,该方法需要预测引用类型数据。

    [1,2,3] instanceof Array // true
    
    (function a() { console.log('123') }) instanceof Function // true
    
    ({ name: 'Json', age: 19 }) instanceof Object // true
    
    [1,2,3].constructor === Array // true
    
    (function a() { console.log('123') }).constructor === Function // true
    
    ({ name: 'Json', age: 19 }).constructor === Object // true
    
  12. 封装识别数据类型的方法

    上述的几种识别数据类型的方法都具有局限性,我们可以借助 Object.prototype.toString.call() 封住一个适用所有类型的方法,而且不需要预测。

    function type(arg) {
    	return Object.prototype.toString.call(arg).slice(8, -1).toLowerCase()
    }
    

JavaScript 数据类型转换

JavaScript 基本数据类型可以通过操作符隐式转换,也可以通过转换函数显示转换。引用类型数据转换需要通过其原型方法来进行,后续对象中会讲解。

  1. 隐式转换

    所谓隐式转换是指在运算过程中,操作数数据类型自动发生转换。

    1 + 2 // 3
    
    1 + true // 2
    1 + false // 1
    
    1 + '2' // 12
    '2' - 1 // 1
    
    1 + null // 1
    
    1 + undeined // NaN,NaN 是一种返回值,表示结果不是一个数字。
    

    注意:隐式转换主要发生在算术运算的过程中,Number 类型只有与 String 类型进行 + 运算会进行字符串拼接,与其他类型进行任何运算都将转换 Number 类型进行运算。另:关系运算和逻辑运算也会发生隐式转换,关系运算中操作数均转换为 Number 类型进行对比。逻辑运算中操作转换 Boolean 对比。

  2. 显式转换

    所谓显示转换是指通过转换函数进行数据类型的转换。

    • Number()

      Number() 函数接收一个参数,输出转换后的 Number 类型,如参数存在不可转字符,则输出 NaN。

      Number('1') // 1
      Number('1a') // NaN
      
      Number(true) // 1
      Number(false) // 0
      
      Number(null) // 0
      
      Number(undefined) // NaN
      

      注意:通常数字的转化可以使用 +参数 的形式转换,算术运算表达式中的隐式转换均通过 Number() 转换的。

    • parseInt()

      parseInt() 函数接收一个参数,输出转换后的整型数字,如参数存在不可转字符,可作截取转换,但只能从头开始。

      parseInt(2) // 2
      parseInt(2.1) // 2
      
      parseInt('1a') // 1
      parseInt('1.1a') // 1
      parseInt('a1.1') // NaN
      
      parseInt(true) // NaN
      parseInt(false) // NaN
      parseInt(null) // NaN
      parseInt(undefined) // NaN
      

      注意:parseInt() 传入第二个参数表示转化进制数,如 parseInt('1010', 2) 返回 10,表示二进制 1010 转化 十进制 10

    • parseFloat()

      parseFloat() 函数接收一个参数,输出转换后的 Number ,如参数存在不可转字符,可作截取转换,但只能从头开始。

      parseFloat(2) // 2
      parseFloat(2.1) // 2.1
      
      parseFloat('1a') // 1
      parseFloat('1.1a') // 1.1
      parseFloat('a1.1') // NaN
      
      parseFloat(true) // NaN
      parseFloat(false) // NaN
      parseFloat(null) // NaN
      parseFloat(undefined) // NaN
      
    • String()

      String() 函数接收一个参数,输出转换后的 Strong 类型,输出结果形式为 '参数'。

      String(1) // '1'
      String(1.1) // '1.1'
      
      String(true) // 'true'
      String(false) // 'false'
      
      String(null) // 'null'
      
      String(undefined) // 'undefined'
      

      注意:通常字符串转化可以使用 '' + 参数 的形式转化。

    • Boolean()

      Boolean() 函数接收一个参数,输出转换后的 Boolean 类型。只有当参数为 0, '', undefined, null, NaN 时,输出值为 false,其余均为 true

      Boolean(0) // false
      Boolean('') // false
      Boolean(null) // false
      Boolean(undefined) // false
      Boolean(NaN) // false
      

JavaScript 运算符和表达式

表达式由操作数和运算符组成,操作数可以是任意类型数据,运算符主要包含:算术运算符、关系运算符、逻辑运算符等。

  1. 算术运算符

    运算符:+, -, *, /, %, ++, --

    % 模运算,运算结果为取余,如 5 % 2 结果为 1。

    ++, -- 自增自减运算,++num 表示自加 1 再取值运算;num++ 表示先取值运算再自加 1。

    var a = 5
    var b = 0
    
    b = ++a + 2
    console.log(b) // 8
    
    var c = 5
    var d = 0
    d = c++ + 2
    console.log(d) // 7
    
  2. 关系运算符

    运算符:>, <, >=, <=, ==, !=, ===, !==

    == 不完全相等,只判断操作数通过 Number() 函数转换后 Numer 类型的值是否相等。

    === 完全相等,同时判断操作数转换后的值和操作数本身的数据类型是否相等。

    '1' == true // true
    '1' === true // false
    
  3. 逻辑运算符

    运算符:&&, ||, !

    && 与运算,如 a && b,a 为真,则为 b,a 为假,则为 a。类比电路中的串联电路

    || 或运算,如 a || b,a 为真,则为 a,a 为假,则为 b。类比电路中的并联电路

  4. 位运算符

    运算符:&, |, ^

    & 按位与,任何数字与1按位与操作,结果为1,即为奇数,反之为偶。

    | 按位或,任何小数与 0 按位或操作,小数转换为整数(直接取整,无四舍五入)。

    ^ 按位异或,整数数字可以使用按位异或操作进行快速交换。

    var a = 10
    var b = 13
    
    a = a ^ b
    b = b ^ a
    a = a ^ b
    
    console.log(a, b) // 13, 10
    
  5. 赋值运算符

    运算符:=, +=, -=, *=, /=, %=

    = 简单赋值,运算符右操作数赋值给左操作数。

    +=, -=, *=, /=, %= 运算赋值,左右操作数先进行相应的运算,运算结果再赋值给左操作数。

  6. 三元运算符

    格式:a ? b : c。若 a 为真,则输出结果为 b,若 a 为假,则输出结果为 c。

  7. 运算优先级

    算术 > 比较 > 逻辑(与 > 或 > 非),() 解决一切优先级问题。

JavaScript 流程控制

  1. 顺序结构

    JavaScript 默认代码执行顺序由上向下顺序执行。

  2. 分支结构

    JavaScript 分支结构有 if...else if...else 结构和 switch...case...default 结构。

    var score = 89
    
    if(score >=0 && score < 60) {
      console.log('不及格')
    } else if(score >= 60 && score < 80) {
      console.log('及格')
    } else if(score >= 60 && score < 90) {
      console.log('良好')
    } else if(score >= 90 && score <= 100) {
      console.log('优秀')
    } else {
      console.log('谎报军情')
    }
    
    // 良好
    
    var comments = "良好"
    
    switch(comments) {
    case '不及格':
      console.log('分数在 0 - 60 之间')
    case '及格':
      console.log('分数在 60 - 80 之间')
    case '良好':
      console.log('分数在 80 - 90 之间')
      // break
    case '优秀':
      console.log('分数在 90 - 100 之间')
    case '谎报军情':
      console.log('分数不在 0 - 100 之间')
    default:
      console.log('努力,少年。')
    }
    
    /**
    * 分数在 80 - 90 之间
    * 分数在 90 - 100 之间
    * 分数不在 0 - 100 之间
    * 努力,少年。
    */
    

    if...else if...else 结构分支条件局限小,可以使用范围,结果只输出满足条件的分支。

    switch...case...default 结构分支条件只能为常量值,结果输出不唯一,默认输出满足条件及以下的分支,可以使用 break 跳过其他分支。

  3. 循环结构

    JavaScript 主要的循环结构有 while() {...} 结构和 for() {...} 结构。

    // 循环打印 1 - 10
    
    var n = 1;
    while(n <= 10) {
      console.log(n)
      n++
    }
    
    
    for(var i = 1; i <= 10; i++) {
      console.log(i)
    }
    

    一个循环必须的几个要素:循环初始值:1,循环终止值:10,循环过渡方法:num++,循环体:console操作。

    控制循环进度的关键字 breakcontinuebreak 表示执行到此,跳出循环; continue 表示执行到此,继续下一步循环。

    var n = 1;
    while(n <= 10) {
        if(n == 5) {
            n++
            continue // 执行到 5,加 1 后不输出了,继续从 6 开始循环
        }
        console.log(n)
        n++
    }
    
    for(var i = 1; i <= 10; i++) {
        console.log(i)
        if(i == 5) {
            break // 执行到 5,跳出循环,不再输出 5 - 10 之间的值了
        }
    }
    

JavaScript 对象

JavaScript 语言中”万物皆对象“。JavaScript 内置了 11 个对象,其中包括 2 个单体对象(Global,Math),2 个辅助对象(RegExp,Date),1 个错误对象(Error),3 个包装对象(Number,String,Boolean),3 个复杂对象(Array,Function,Object)。

Global

Global 为全局作用域对象,在浏览器由 window 代替,Nodejs 中使用 global。浏览器中定义的全局变量均挂载在 window 对象下。

var a = 'global'

console.log(a) // global
console.log(window.a) // global

注意:定义全局变量实际调用方式应为 window.variable 的形式,window可以省略。

Math

Math 是保存数学计算的常量和提供数学计算常用 API 的对象。

  1. 常量

    • Math.E

      自然常数 e。

    • Math.LN2

      自然常数 ln2,以 e 为底 2 的对数。

    • Math.LN10

      自然常数 ln10,以 e 为底 10 的对数。

    • Math.LOG2E

      自然常数 log2(e),以 2 为底 e 的对数。

    • Math.LOG10E

      自然常数 log10(e),以 10 为底 e 的对数。

    • Math.PI

      自然常数 π。

    • Math.SQRT1_2

      根号下1/2。

    • Math.SQRT2

      根号下2。

  2. 数学 API

    • Math.abs()

      绝对值函数,函数接收一个参数,参数经过 Number() 函数转换后,若结果为 Number 类型则取绝对值返回,反之返回 NaN。

    • Math.cbrt()

      立方根函数,函数接收一个参数,参数经过 Number() 函数转换后,若结果为 Number 类型则取立方根返回,反之返回 NaN。

    • Math.sqrt()

      平方根函数,函数接收一个参数,参数经过 Number() 函数转换后,若结果为 Number 类型则取平方根返回,反之返回 NaN。

    • Math.ceil()

      天花板函数,函数接收一个参数,参数经过 Number() 函数转换后,若结果为 Number 类型则向上取整返回,反之返回 NaN。

    • Math.floor()

      地板函数,函数接收一个参数,参数经过 Number() 函数转换后,若结果为 Number 类型则向下取整返回,反之返回 NaN。

    • Math.exp()

      幂指函数,函数接收一个参数 n,参数经过 Number() 函数转换后,若结果为 Number 类型则取 e 的 n 次幂返回,反之返回 NaN。

    • Math.pow()

      乘方函数,函数接收两个参数 x,y,参数经过 Number() 函数转换后,若结果均为 Number 类型则取 x 的 y 次方 返回,反之返回 NaN。

    • Math.random()

      随机函数,随机返回一个 0 - 1 之间的小数,可以取到 0 ,但取不到 1。

    • Math.round()

      四舍五入函数,函数接收一个参数,参数经过 Number() 函数转换后,若结果为 Number 类型则四舍五入取整,反之返回 NaN。

RegExp

RegExp 是定义正则表达式和提供正则表达式常用 API 的对象。

  1. 正则语法

    • 字符集

      一位字符位上的备选字符集合。

      [0-9] 表示匹配 0 -9 之间的一位数字。

      [a-z] 表示匹配 a - z 之间的一位字母。

      [A-Z] 表示匹配 A - Z 之间的一位字母。

      {a-zA-Z} 表示匹配 a - z 或 A - Z 之间的一位字母。

      [0-9A-Za-z] 表示匹配 0 - 9 或者 a - z 或者 A - Z 之间的一位字母或数字。

      [\u400-\u9fa5] 表示匹配一位汉字。

      注意:- 表示范围。

      [^1] 表示匹配 1 以外的任何一位字符。

      注意:^ 范围太大,不推荐使用。

      \d 表示匹配一位数字。

      \w 表示匹配一位数字、字母或 _ 。

      \s 表示匹配一位空白字符(空格、TAB)。

      注意:\D, \W, \S 表示取反,如 [\d\D] 表示数字或非数字,即任意一位字符。

      . 表示匹配任意一位字符。

    • 量词

      规定一位字符集出现的次数,跟在字符集之后。

      {n,m} 表示匹配至少 n 个,至多 m 个。

      {n,} 表示匹配至少 n 个。

      {n} 表示匹配只有 n 个。

      ? 表示匹配 0 - 1 个。

      + 表示匹配 1 - ∞ 个。

      * 表示匹配 0 - ∞ 个。

    • 选择与分组

      a|b 表示匹配 a 或 b 。

      (a|b) 表示匹配 a 或 b 的分组。

      注意:

      1. 一个正则表达式中有 n 个 (),就有 n 个分组。

      2. 分组引用:\数字 形式可以作为分组的引用,如 /(\d{1,2})(a|b)([A-Z])/ 有 3 个分组,\1 表示分组 (\d{1,2})\3 表示分组 ([A-Z])

      3. 分组捕获:默认分组引用是分组捕获而来的,?: 可以取消分组捕获,如 /(?:\d{1,2})(a|b)([A-Z])/,第一个分组取消捕获,则 \1 表示分组 (a|b)\2 表示分组 ([A-Z])

    • 特殊位置

      ^ 表示匹配开头,如 /^[a-z]/ 表示匹配以 a - z 之间的字母开头。

      $ 表示匹配结尾,如 /[a-z]/ 表示匹配以 a - z 之间的字母结尾。

      注意:^$ 合用时,表示完整匹配,如 /^(x|y)$/ 表示匹配 x 或 y,/^x|y$/ 表示匹配 x 开头 或 y 结尾。

      \b 表示匹配边界,边界指:开头,空格,标点,结尾。如 \b([a-zA-Z]+\b) 匹配字符串 'this is,a.regExp' 的结果为:thisisaregExp

      'this is,a.regExp'.match(/\b([a-zA-Z])+\b/g) // ['this', 'is', 'a', 'regExp']
      
  2. 正则表达式

    JavaScript 中定义正则表达式的方法有两种:字面量法 和 构造函数法。

    • 字面量法

      var reg = /^a[a-z]+z$/ig
      
    • 构造函数法

      var reg = new RegExp('^a[a-z]z$', 'ig')
      var regBound = new RegExp('\\b([a-zA-Z])+\\b', 'ig') // 注意:字符串输出 `\` 需要转义,此类字符为转义字符。
      

    注意:i 表示匹配过程中忽略大小写,g 表示开启贪婪匹配,正则默认是非贪婪匹配的,一旦匹配则返回,不再继续匹配。

  3. 正则表达式 API

    • reg.test()

      函数接收一个参数,用正则 reg 验证参数是否匹配,如果匹配,返回 true,反之返回 false。

    • reg.exec()

      函数接收一个参数,在参数中查找匹配 reg 的关键词,如果找到,返回关键词信息数组,反之返回 null。

    注意:其他与正则相关的 API 在 String 对象中会有体现,之所以没有放在这里,是因为调用方式不同。

Date

Date 是定义日期时间和提供操作日期时间 API 的对象。

  1. 日期时间

    定义日期时间的方法只有 构造函数法。

    var date = new Date() // 当前时间
    var date1 = new Date('2023/3/15 8:10:30') // < 10 可不前缀 0
    var date2 = new Date('2023-03-15 08:10:30')
    var date3 = new Date('2023/03/15') // Wed Mar 15 2023 08:00:00 GMT+0800
    var date4 = new Date('08:10:30')
    

    注意:

    a. new Date() 不传参生成当前日期时间对象,传参须按照上述格式,生成对应日期时间对象。

    b. new Date() 不传参,取当前时间,取的实际是当前系统的时间。没错,就是电脑或手机的时间,你不准,它也不准。

    c. new Date() 如果只传入日期,则取格林尼治时间 00:00:00,对应系统当前所在时区计算实际时间,北京,东 8 区,加 8 小时,为 08:00:00。

    d. new Date() 如果只传入时间,则无法生成日期对象,返回 Invalid Date

  2. 日期时间 API

    • date.getFullYear()

      读取 date 对象中的年。

    • date.setFullYear()

      接收一个参数,参数为字符串会用 Number() 转换,设置 date 对象中的年。

    • date.getMonth()

      读取 date 对象中的月,获取的月份是在 0 - 11 之间,因此计算实际月份需要 +1。

    • date.setMonth()

      接收一个参数,参数为字符串会用 Number() 转换,设置 date 对象中的月。

      注意:设置月份需要结合年和日,设置月超过 12,则年 + 1,设置月 - 12 为实际月。如果设置月,不存在当前日,则设置不生效,如当前 31 日,设置 24 月均不生效。

    • date.getDate()

      读取 date 对象中的日。

    • date.setDate()

      接收一个参数,参数为字符串会用 Number() 转换,设置 date 对象中的日。

      注意:设置日需要结合当前月确定,超出当前月的最大日,则月份 + 1,设置日 - 最大日为实际日。

    • date.getHours()

      读取 date 对象中的时。

    • date.setHours()

      接收一个参数,参数为字符串会用 Number() 转换,设置 date 对象中的时。

      注意:设置时超过 24 ,则日期会自动 +1,设置时 -24 为实际时。

    • date.getMinutes()

      读取 date 对象中的分。

    • date.setMinutes()

      接收一个参数,参数为字符串会用 Number() 转换,设置 date 对象中的分。

      注意:设置分超过 60,则时会自动 + 1,设置分 -60 为实际分。

    • date.getSeconds()

      读取 date 对象中的秒。

    • date.setSeconds()

      接收一个参数,参数为字符串会用 Number() 转换,设置 date 对象中的秒。

      注意:设置分超过 60,则时会自动 + 1,设置分 -60 为实际秒。

    • date.toJSON()

      当前系统时间转换为格林尼治时间输出,即不计算时区的时差。

    • date.getTime()

      当前系统时间转换为时间戳(13位,毫秒级)输出,时间戳:1970-01-01 00:00:00 至今的毫秒数,时间运算底层是通过时间戳计算的。

    • date.toLocaleString()

      格式化输出系统日期时间,如 yyyy/mm/dd 上午hh:mm:ss,12小时制。

      注意:

      a. toLocaleString() 在浏览器环境输出格式为 yyyy/mm/dd hh:mm:ss,24小时制;在 node 环境输出格式为 yyyy/mm/dd 上午hh:mm:ss,12小时制。

      b. 统一设置 24 小时制的方式:date.toLocaleString('chinese',{hour12: false})

    • data.toLocaleDateString

      格式化输出系统日期,如 yyyy/mm/dd

    • data.toLocaleTimeString

      格式化输出系统时间,如 下午hh:mm:ss

Error

Error 是定义错误和封装错误类型的对象。

  1. 错误类型及定义错误

    • Error

      泛错误,不区分错误类型。定义错误:new Error() 接受一个参数,参数为错误描述。

    • SyntaxError

      语法错误。定义错误:new SyntaxError() 接收一个参数,参数为错误描述。

    • ReferenceError

      引用错误,一般指变量或对象属性未找到。定义错误:new ReferenceError() 接收一个参数,参数为错误描述。

    • TypeError

      类型错误,一般指错误使用(), []或 错误使用对象方法。定义错误:new TypeError() 接收一个参数,参数为错误描述。

    • RangeError

      范围错误,一般指超出预设范围,如 toFixed() 参数范围为 0 - 20,超出则报错。定义错误:new RangeError() 接收一个参数,参数为错误描述。

    • URIError

      URI 错误,一般指 URI 拼接错误。定义错误:new URIError() 接收一个参数,参数为错误描述。

    • EvalError

      Eval 错误,指 eval() 函数错误,一般不被抛出。

    注意:一般定义抛出异常,使用 new Error() 即可。

  2. 抛出异常

    throw new Error('自定义异常描述。')
    

    注意:自定义抛出异常多用于表单校验。

  3. 捕获异常

    try {
        // 可能存在异常代码
    }
    catch(err) {
        // err 为错误对象,可以抛出异常
    }
    finally {
        // 是否抛出异常都会执行的代码,多用于资源回收
    }
    

Number

Number 是基本数据类型 Number 的包装对象。

Number 是定义 Number 类型数据和提供操纵 Number 类型数据 API 的对象。

  1. Number 类型数据

    Number 类型数据定义有两种方式:字面量法 和 构造函数法

    • 字面量法

      var num = 12.3
      
    • 构造函数法

      var num = new Number(12.3)
      
  2. Number 类型 API

    • num.toExponential()

      函数返回科学计数法的数字输出,接收一个参数,指定小数位数,参数范围为 0 - 20,四舍五入方式保留小数位数。

    • num.toFixed()

      函数返回四舍五入后的数字输出,接收一个参数,参数指定小数位数,参数范围为 0 - 20,四舍五入方式保留小数位数。

    • num.toLocaleString()

      函数返回数字的格式化字符串输出,默认返回千分位格式,如 1,000

      注意:

      a. num.toLocaleString('fi-FI') 输出空格格式千分位,如 1 000

      b. num.toLocaleString("zh-CN", {style:"currency", currency:"CNY"}) 输出人民币千分位格式,如 ¥1,000

      c. num.toLocaleString("en-US", {style:"currency", currency:"USD"}) 输出美元千分位格式,如 $1,000

    • num.toPrecision()

      函数返回指定长度的数字输出,接收一个参数,参数指定数字长度,参数范围 0 - 100,四舍五入方式保留数字位数。

    • num.toString()

      函数返回数字的字符串输出。

String

String 是基本数据类型 String 的包装对象。

String 是定义 String 类型数据,封装 String 类型数据属性和 String 类型数据 API 的对象。

  1. String 类型

    String 类型数据定义有两种方式:字面量法 和 构造函数法

    • 字面量法

      var str = 'javascript'
      
    • 构造函数法

      var str = new String('javascript')
      
  2. String 类型数据属性

    • str.length

      返回当前字符串长度。

  3. String 类型数据 API

    字符串是不可变的,任何方式处理字符串以后,原字符串不发生改变,只可能返回一个新的字符串。

    • str.charAt()

      返回指定位置的字符。

      不传参返回第一个字符;

      传入位置参数则返回当前位置的字符,位置参数范围为 0 - (str.length - 1),如果参数在范围之外,则返回 '' 空字符串。

    • str.at()

      返回指定位置的字符。

      不传参返回第一个字符;

      传入位置参数则返回当前位置的字符,位置参数范围为 (-str.lengh) - (str.length - 1),如果参数在范围之外,则返回 undefined 。参数为负,实际返回 (负值 + str.length) 位置的字符。

      注意:str[i] 也可以返回指定位置的字符,i 的范围为 0 - (str.length - 1),范围之外返回 undefined

    • str.toLowerCase()

      返回一个转换为全大写字母的字符串。

    • str.toUpperCase()

      返回一个转换为全小写字母的字符串。

    • str.charCodeAt()

      返回指定位置字符的 Unicode 编码。

      不传参返回第一个字符的 Unicode 编码;

      传入位置参数则返回当前位置字符的 Unicode 编码,位置参数范围为 0 - (str.length - 1),如果参数在范围之外,则返回 NaN

    • str.codePointAt()

      返回指定位置字符的 Unicode 编码。不传参返回第一个字符的 Unicode 编码,传入位置参数则返回当前位置字符的 Unicode 编码,位置参数范围为 0 - (str.length - 1),如果参数在范围之外,则返回 undefined

      注意:静态方法 String.fromCharCode() 接收一个 Unicode 编码作为参数,返回 Unicode 编码对应的字符。

    • str.concat()

      传入参数,参数会通过 String() 函数处理成字符串参数,后返回一个当前字符串与传入字符串参数拼接后的字符串,可传入多个参数,如 'hello'.concat('javascript', 3) 返回 'hellojavascript3'

      注意:字符串拼接可以直接使用 str1 + str2 的方式。

    • str.repeat()

      不传参返回 ''空字符串;

      传入参数 n 则返回一个当前字符串重复 n 次后的字符串,n 范围为 0 - ∞。如 'hello'.repeat(2) 返回 'hellohello'

    • str.padStart()

      可以接收两个参数 maxlength 和 b。

      传入一个参数 maxlength,maxlength 表示最大长度,如果 maxlength 小于等于当前字符串长度,则返回当前字符串,大于则返回一个长度为 maxlength,以空格开头补全的字符串,如 'hello.padStart(6)' 返回 ' hello'

      传入两个参数,参数 b 通过 String() 函数转换字符串,再以 b 字符串开头补全字符串,根据 maxlength 参数最大长度, b 字符串会发生截取或重复,如 'hello'.padStart(10, 'ab') 返回 ababahello

    • str.padEnd()

      同上,结尾补全。如 'hello'.padEnd(10, 'ab') 返回 helloababa

    • str.trim()

      去除当前字符串左右两边的空格。如 ' hel ll o '.trim() 返回 'hel ll o'

      注意:无法去除字符串内部的空格。

    • str.trimStart()str.trimLeft()

      去除当前字符串左边(即开头)的空格,如 ' hel ll o '.trimStart() 等同于 ' hel ll o '.trimLeft() 返回 'hel ll o '

    • str.trimEnd()str.trimRight()

      去除当前字符串右边(即结尾)的空格,如 ' hel ll o '.trimEnd() 等同于 ' hel ll o '.trimRight() 返回 ' hel ll o'

    • str.slice()

      可以接收两个参数 start 和 end。

      传入一个参数 start,start 表示开始位置,返回一个当前字符串从开始位置至结尾的所有字符组成的子字符串。start 取值范围为 -∞ - +∞,当 start 取值在 (-∞, -str.length] 时,返回当前字符串,如 'hello'.slice(-5) 返回 'hello';当 start 取值在 (-str.length, -1] 时,返回 str.length + start 至结尾的子字符串,如 'hello'.slice(-3) 返回 'llo';当 start 取值在 [0,str.length) 时,返回 start 至结尾的子字符串,如 'hello'.slice(2) 返回 'llo';当 start 取值在 [str.length, +∞) 时,返回 '' 空字符串,如 'hello'.slice(5) 返回 ''

      传入两个参数,end 表示结束位置,返回一个当前字符串从开始位置至结束位置的所有字符(不含结束位置字符)组成的子字符串。其中,end > start。

    • str.substring()

      同上,但不支持负数位置,即当 start 取值在 (-∞, 0) 时,返回当前字符串。

    • str.substr()

      可以接受两个参数 start 和 n。

      传入一个参数时同 str.slice() 一样。

      传入两个参数,n 表示返回子字符串长度,即 从 start 位置开始向后截取 n 个字符组成子字符串返回。

    • str.startsWith()

      接收一个参数,参数会通过 String() 函数转换字符串,判断当前字符串是否以参数字符串开头,是则返回 true,反之返回 false。如 'hello'.startsWith('he') 返回 true。

    • str.endsWith()

      接收一个参数,参数会通过 String() 函数转换字符串,判断当前字符串是否以参数字符串结尾,是则返回 true,反之返回 false。如 'hellotrue'.endsWith(true) 返回 true。

    • str.indexOf()

      接收一个参数,参数会通过 String() 函数转换字符串,先判断当前字符串是否包含参数字符串,不包含则返回 -1,包含的话,则会从当前字符串开头位置查找参数字符串第一个字符所在位置,一旦找到就返回其所在位置,不再继续查找。如 'hello'.indexOf('l') 返回 2

      注意:字符在字符串所在位置被称为字符串索引,字符串索引由 0 开始,自左向右递增;当字符串索引为负时,由 -1 开始,自右向左递增。

    • str.lastIndexOf()

      同上,只是查找顺序是从当前字符串结尾查找。如 'hello'.lastIndexOf('l') 返回 3

    • str.includes()

      接收一个参数,参数会通过 String() 函数转换字符串,判断当前字符串是否包含参数字符串,包含则返回 true,反之为 false。

    • str.search()

      接收一个参数,参数可以为基础数据类型,也可以为正则表达式。

      参数为基础数据类型时,等同于 str.indexOf()

      参数为正则表达式,从当前字符串中查找满足正则表达式的子字符串,找到则返回子字符串第一个字符所在位置,反之返回 -1 。

    • str.match()

      接收一个参数,参数可以为基础数据类型,也可以为正则表达式。

      参数为基础数据类型时,参数会通过 String() 函数转换字符串,判断当前字符串是否包含参数字符串,不包含返回 null,包含则返回由参数字符串,参数字符串第一个字符所在位置,以及其他信息组成的类数组。

      参数为正则表达式,从当前字符串中查找满足正则表达式的子字符串,找到则返回由参数字符串,参数字符串第一个字符所在位置,以及其他信息组成的类数组,反之返回 null 。

    • str.replace()

      接收两个参数 target 和 replace。target 表示需要被替换的内容,replace 表示替换的内容,replace 会通过 String() 函数转换字符串。

      target 参数可以为基础数据类型或正则表达式。

      target 为基础数据类型时,会通过 String() 函数转换字符串,在当前字符串中查找 target 子字符串,并替换为 replace 字符串,返回一个替换后的字符串,如 'hello'.replace('l',1) 返回 'he1lo'

      target 参数为正则表达式时,在当前字符串中查找满足正则表达式的子字符串,并替换为 replace 子字符串,返回一个替换后的字符串,如 'hello'.replace(/ll/, 'r') 返回 'hero'

      注意:replace 查找 target 子字符串属于非贪婪匹配,例子中 hello 替换 l 只替换了第一个。

    • str.replaceAll()

      str.replace() 的贪婪匹配模式,如 'hello'.replaceAll('l', 1) 返回 'he11o'

      注意:str.replace() 可以通过第二参数传入函数的方式实现 str.replaceAll()

       str.replace(/reg/, function (match) {
          // match 为 reg 匹配的每一个结果
          return R // R 为替换内容
       })
      
    • str.split()

      接收一个参数。

      不传参返回当前字符串组成的数组,如 'hello'.split() 返回 ['hello']

      传一个参数,参数会通过 String() 函数转换字符串,在当前字符串中查找参数字符串(贪婪匹配),未找到则返回当前字符串组成的数组,如果找到了,则以参数字符串分割当前字符串为多个子字符串,多个子字符串组成数组返回,如 'hello'.split('l') 返回 ['he', '', 'o']

      注意:'hello'.split('') 返回 ['h', 'e', 'l', 'l', 'o']

Boolean

Boolean 是定义 Boolean 数据类型和提供操作 Boolean 类型数据 API 的对象。

  1. Boolean 类型数据

    Boolean 类型数据定义有两种方式:字面量法 和 构造函数法。

    1. 字面量法

      var bool = true
      
    2. 构造函数法

      var bool = new Boolean(true)
      

Array

Array 是定义 Array 数据类型,封装 Array 类型属性以及 Array 类型 API 的对象。

  1. Array 类型

    Array 类型数据定义有两种方式:字面量法 和 构造函数法。

    1. 字面量法

      var arr = [1, true, '', null, undefined, [1,2]]
      
    2. 构造函数法

      var arr = new Array(1,'', false, null, undefined, [1,2])
      var arr1 = new Array(3) // [undefined, undefined,undefined]
      

    注意:

    a. 数组内的项可以称之为元素,数组的元素可以是任意类型数据。

    b. new Array() 如果传入参数为 1 个且为 Numer 类型,则返回一个长度等于参数,元素均为 undefined 的数组。

    c. 可以通过 Array.isArray() 判断是否是 Array 类型。

  2. Array 类型数据属性

    • arr.length

      返回当前数组的长度。

  3. Array 类型数据 API

    数组是可变的,数组经过 API 处理以后,原数组可能发生改变。

    • arr.at()

      返回指定位置的元素。

      不传参返回第一个元素;

      传入位置参数则返回当前位置的元素,位置参数范围为 (-str.lengh) - (str.length - 1),如果参数在范围之外,则返回 undefined 。参数为负,实际返回 (负值 + arr.length) 位置的元素。

      注意:arr[i] 也可以返回指定位置的元素,i 为数组索引,i 的范围为 0 - (str.length - 1),范围之外返回 undefined

    • arr.pop()

      不接受参数,原数组从结尾处推出一个元素,并返回推出的元素原数组改变

    • arr.push()

      接收多个参数,原数组从结尾处按照参数原有的顺序推入多个参数,并返回推入后数组的长度原数组改变。如 [3,4,5].push(6,7) 返回 5,原数字变为 [3,4,5,6,7]

    • arr.shift()

      不接受参数,原数组从开头处推出一个元素,并返回推出的元素原数组改变

    • arr.unshift()

      接收多个参数,原数组开头处按照参数原有的顺序推入多个参数,并返回推入后数组的长度原数组改变。如 [3,4,5].push(1,2) 返回 5,原数字变为 [1,2,3,4,5]

    • arr.fill()

      var arr = [2, 3, '', false, null, undefined, 1, 4, 5]
      
      arr.fill() // [undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined]
      
      arr.fill('h') // ['h', 'h', 'h', 'h', 'h', 'h', 'h', 'h', 'h']
      
      arr.fill('l', 4) // ['h', 'h', 'h', 'h', 'l', 'l', 'l', 'l', 'l']
      
      arr.fill('o', 4, 6) // ['h', 'h', 'h', 'h', 'o', 'o', 'l', 'l', 'l']
      

      接收三个参数,第一个参数为替换元素,第二、三为起始和终止位置。

      不传参数,原数组中元素均被替换为 undefined;

      传入一个参数,原数组中元素均被替换为参数元素;

      传入两个参数,原数组中起始位置开始至结束的元素被替换为参数元素;

      参数三个元素,原数组中起始元素至终止位置的元素被替换为参数元素,不含终止位置的元素。

      返回替换后的数组原数组改变

    • arr.copyWithin()

      var arr = [1,2,3,4,5,6]
      
      arr.copyWithin() // [1, 2, 3, 4, 5, 6]
      
      arr.copyWithin(4) // [1, 2, 3, 4, 1, 2]
      
      arr.copyWithin(4, 2) // [1, 2, 3, 4, 3, 4]
      
      arr.copyWithin(2, 0, 1) // [1, 2, 1, 4, 3, 4]
      

      接收三个参数,第一个参数为被替换元素位置,第二、三个参数为复制元素的起始位置和终止位置。

      传入一个参数,该参数既为复制元素的终止位置,又为被替换元素的起始位置。从开始至参数位置复制元素,不包含参数位置的元素,在参数位置处替换当前位置及以后与复制元素同个数的元素,若后续元素个数小于复制元素的个数,则切割复制元素,使数组长度保持不变。

      传入两个参数,从第二个参数位置处复制元素至结尾,在第一个参数位置开始替换元素,包括其后续元素,保持数组长度不变。

      传入三个参数,从第二参数位置出复制至第三个参数处结束,不包含第三个参数位置处的元素,在第一个参数位置开始替换元素,包括其后续元素,保持数组长度不变。

      返回替换后的数组原数组改变

    • arr.concat()

      接收多个参数,参数按照传参顺序拼接在数组的结尾,返回拼接后的数组原数组改变

    • arr.includes()

      接收一个参数,判断参数是否在数组中,是则返回 true,反之返回 false原数组不改变

    • arr.indexOf()

      接收一个参数,从数组开头处查找并判断参数是否在数组中,不在则返回 -1,在则返回查找到的 第一个元素的索引原数组不改变

    • arr.lastIndexOf()

      接收一个参数,从数组结尾处查找并判断参数是否在数组中,不在则返回 -1,在则返回查找到的 第一个元素的索引原数组不改变

    • arr.slice()

      接收两个参数 start 和 end,用法同:str.slice()。返回 切割后的数组原数组不改变

    • arr.splice()

      接收多个参数,第一个参数表示起始位置,第二参数表示删除元素个数,其余参数表示新增的元素。如 arr.splice(2, 3, 'a', 'b'),表示从数组 arr 索引 2 位置开始,删除 3 个元素,后从索引 2 位置开始插入 a 和 b 两个元素。

      接收一个参数,数组从参数位置删除至数组结尾。

      接收两个参数,数组从第一个参数位置开始删除元素,删除与第二个参数相同个数的元素。

      接收两个以上参数,数组从第一个参数位置开始删除元素,删除与第二个参数相同个数的元素,并在第一个参数位置开始添加第三个及以后的参数。

      返回删除元素组成的数组原数组改变

      注意:arr.toSpliced() 同上用法,但 toSpliced() 方法使用过数组的副本进行修改的,不改变原数组。

    • arr.with()

      接收两个参数,第一个参数表示数组索引位置,第二个参数表示替换元素。

      接收一个参数,数组中当前参数位置的元素会被替换为 undefined。

      接受两个参数,数组中第一个参数位置出的元素会被替换为第二参数。

      返回替换后的数组原数组不改变

      注意:修改数组中元素也可以使用 arr[i] = x 的方式修改,但是这种修改方式会改变原数组。

    • arr.reverse()

      数组倒序排列,返回 倒序排列后的数组原数组改变

      注意:arr.toReversed() 同上用法,但 toReversed() 方法使用过数组的副本进行修改的,不改变原数组。

    • arr.sort()

      数组排序,按照数组元素的 Unicode 编码排序,默认升序排列,如 [1,10,2,20].sort() 返回 [1, 10, 2, 20],返回 排序后的数组原数组改变

      注意:
      a. 数字数组正常大小排序需要传入排序函数:arr.sort((a,b) => a -b) 表示升序排列,arr.sort((a,b) => a -b) 表示降序排列。

      arr.toSorted() 同上用法,但 toSorted() 方法使用过数组的副本进行修改的,不改变原数组。

    • arr.join()

      不传入返回当前数组元素的字符串格式,等同于 arr.toString()。如 [1,2,3,4].join() 返回 '1,2,3,4'

      传入参数,以参数为分隔符分隔多个数组元素,如 [1,2,3,4].join(2) 返回 '1222324'

      返回数组元素的字符串形式原数组不改变

      注意: [1,2,3,4].split('') 返回 '1234'

    • arr.toString()

      返回数组的字符串格式原数组不改变。如 [1,2,3,4].toString() 返回 '1,2,3,4',其实是将 [] 替换为 ''

    • arr.find()

      高阶方法,接收一个回调参数,回调参数的参数分别为当前数组的元素,当前数组的索引以及当前数组。

      find() 接收一个断言函数,该函数返回一个断言,按顺序从头开始查找数组元素是否满足断言,一旦满足则返回当前元素,不再继续查找,反之返回 undefined原数组不改变

      [1,2,3,4,5].find((item, index, arr) => item > 3) 返回 4

    • arr.findLast()

      高阶方法,接收一个回调参数,回调参数的参数分别为当前数组的元素,当前数组的索引以及当前数组。

      findLast() 接收一个断言函数,该函数返回一个断言,按顺序从结尾开始查找数组元素是否满足断言,一旦满足则返回当前元素,不再继续查找,反之返回 undefined原数组不改变

      [1,2,3,4,5].find((item, index, arr) => item > 3) 返回 5

    • arr.findIndex()

      高阶方法,接收一个回调参数,回调参数的参数分别为当前数组的元素,当前数组的索引以及当前数组。

      findIndex() 接收一个断言函数,该函数返回一个断言,按顺序从头开始查找数组元素是否满足断言,一旦满足则返回当前元素的索引,不再继续查找,反之返回 -1原数组不改变

      [1,2,3,4,5].findIndex((item, index, arr) => item > 3) 返回 3

    • arr.findLastIndex()

      高阶方法,接收一个回调参数,回调参数的参数分别为当前数组的元素,当前数组的索引以及当前数组。

      findLastIndex() 接收一个断言函数,该函数返回一个断言,按顺序从结尾开始查找数组元素是否满足断言,一旦满足则返回当前元素的索引,不再继续查找,反之返回 -1原数组不改变

      [1,2,3,4,5].findLastIndex((item, index, arr) => item > 3) 返回 4

    • arr.every()

      高阶方法,接收一个回调参数,回调参数的参数分别为当前数组的元素,当前数组的索引以及当前数组。

      every() 接受一个断言函数,该函数返回一个断言,按顺序从头开始查找数组元素是否满足断言,都满足则返回 true,反之返回 false原数组不改变

      [1,2,3,4,5].every((item, index, arr) => item > 0) 返回 true

    • arr.some()

      高阶方法,接收一个回调参数,回调参数的参数分别为当前数组的元素,当前数组的索引以及当前数组。

      some() 接受一个断言函数,该函数返回一个断言,按顺序从头开始查找数组元素是否满足断言,一旦有一个元素满足则返回 true,反之返回 false原数组不改变

      [1,2,3,4,5].some((item, index, arr) => item > 2) 返回 true

    • arr.filter()

      高阶方法,接收一个回调参数,回调参数的参数分别为当前数组的元素,当前数组的索引以及当前数组。

      filter() 接收一个断言函数,该函数返回一个断言,按顺序从头开始查找数组元素是否满足断言,返回满足断言的元素组成的数组,均不满足则返回 []原数组不改变

      [1,2,3,4,5].filter((item, index, arr) => item > 3) 返回 [4,5]

    • arr.forEach()

      高阶方法,接收一个回调参数,回调参数的参数分别为当前数组的元素,当前数组的索引以及当前数组。

      forEach() 接收一个常规函数,该函数无返回值,可以通过 arr[i] 修改元素,无返回,原数组改变

      var arr = [1,2,3,4,5]
      
      arr.forEach((item, index, arr) => {
          if(item > 4) {
              arr[index] = 4
          }
      })
      
      // arr = [1, 2, 3, 4, 4]
      
    • arr.map()

      高阶方法,接收一个回调参数,回调参数的参数分别为当前数组的元素,当前数组的索引以及当前数组。

      map() 接收一个常规函数,该函数有返回值,返回值会修改当前索引位置的元素,返回修改后的数组原数组不改变

      var arr = [1,2,3,4,5]
      
      var temp = arr.map((item, index, arr) => {
          if(item > 4) {
              return 4
          } else {
              retrun item
          }
      })
      
      // temp = [1, 2, 3, 4, 4]
      // arr = [1, 2, 3, 4, 5]
      
    • arr.reduce()

      高阶方法,接收一个回调参数,回调参数的参数分别为汇总值,当前数组的元素,当前数组的索引以及当前数组。

      reduce() 接收一个常规函数,该函数有返回值,返回数组元素从左至右累计计算结果原数组不改变

      注意:reduce() 可以接受第二参数作为汇总值的初始值。

      [1,2,3,4,5].reduce((pre, item, index, arr) => pre + item, 10) 返回 25

    • arr.reduceRight()

      用法同上,返回 数组元素自右向左累计计算结果原数组不改变

    • arr.flat()

      二维数组:数组元素为数组时,则该数组被称为二维数组,如 [1,2,3,[4,5]]

      flat() 扁平化二维数组为一维数组,返回 扁平化后的一位数组原数组不改变

      [1,2,3,[4,5]].flat() 返回 [1,2,3,4,5]

      注意:arr.flat() 只能用于扁平化二维数组,三维及以上无法继续扁平化。

    • arr.flatMap()

      高阶方法,接收一个回调参数,回调参数的参数分别为当前数组的元素,当前数组的索引以及当前数组。

      flatMap() 是 flat() 和 map() 的合体,接收一个常规函数,该函数有返回值,返回值为数组,用于修改当前索引位置的元素,如此会返回一个二维数组,在通过 flat() 扁平化为一维数组返回,原数组不改变

      [1,2,3].flatMap((item, index, arr) => [ item * 2 ]) 返回 [2,4,6]

Function

Function 是定义 Function 数据类型,封装 Function 类型属性的对象。

  1. Function 数据类型

    Function 函数定义的方式有:函数声明法,函数表达式法,构造函数法。

    • 函数声明法

      function fun(arg) {
          // 函数体
          return arg
      }
      
    • 函数表达式法

      var fun = function(arg) {
          // 函数体
          return arg
      }
      

      注意:函数表达式和函数声明因 JavaScript 的声明提升特性,有着本质的区别,可参考 V8中的函数表达式

    • 构造函数法

      var fun = new Function('arg', '// 函数体 \n return arg')
      

    注意:函数的三要素:函数名,参数,返回值。在 JavaScript 中,函数名相当于变量名,函数也可以作为参数和返回值,因此,函数是"一等公民"。

  2. 作用域链

    作用域:变量的可用范围,实质是一个保存变量的对象,各函数的作用域相互独立,互相之间不受干扰,闭包除外

    根据变量定义位置不同,可以区分为全局变量和局部变量。定义函数之外称为全局变量,全局变量挂载的对象,称之为全局作用域;定义函数内称之为局部变量,局部变量挂载的对象称之为局部作用域。

    • 全局作用域

      全局作用域是“天生”存在的,即代码执行前,JavaScript 引擎会初始化一段内存,作为全局作用域对象,在浏览器环境是 window 对象,node 环境是 global 对象。

      全局作用域的特点是:公共。所有函数均可使用其内部保存的全局变量。

    • 局部作用域

      局部作用域是临时的,它的存在仅限于函数的生命周期范围内,即函数创建时,初始化一段内存,作为函数的局部作用域对象,称之为活动对象(AO);函数调用完成,内存释放,局部作用域 AO 对象被销毁。

      局部作用域的特点是:私有,临时。函数不能使用其他函数局部作用域保存的变量,且函数调用结束,局部作用域内的变量销毁。

    函数调用执行变量查找方式为:局部作用域 --> 全局作用域,这便形成了作用域链。

    注意:函数内可以访问函数外的变量,即局部作用域 --> 全局作用域。函数外不可访问函数内部的变量,即全局作用域 -X-> 局部作用域

  3. 函数的调用流程

    var a = 1
    
    function fun() {
        var b = 2
        console.log(a, b)
    }
    
    fun()
    
    console.log(a)
    
  4. 闭包

    闭包是指有权在函数外访问函数内部变量的函数。

    var a = 1
    
    function outer() {
        var b = 1;
        function inner() {
            var c = 1;
            b++;
            console.log(a, b, c)
        }
        return inner
    }
    
    var get = outer()
    
    get() // 1 2 1
    
    get() // 1 3 1
    

    函数对象通过 scope 隐藏属性指向其作用域,函数作用域通过 parent 隐藏属性指向其父作用域,这里能更清晰的理解作用域链。

    上述过程中,outer 函数执行完成以后,outer AO本该销毁,但是 outer 的返回函数 inner 引用了 outer AO,因此 outer AO 会常住内存。

    闭包成立条件:

    • 外部函数必须返回内部函数,即 outer 必须返回 inner

    • 内部函数必须引用外部函数的变量,即 inner 必须引用变量 b

    闭包的特点:

    • 变量 b 是闭包维护的私有变量,外部无法篡改,不会轻易释放,可以被重用。

    • 改变了变量的访问形式,由变量名的访问形式变成了函数调用的访问形式。

    释放闭包: get = null

  5. 匿名函数和IIFE

    • 匿名函数

      匿名函数是未命名的函数,没有函数名,相当于没有变量,匿名函数就如同一个特殊数据类型的值,可以作为参数,返回值等。

      arr.sort(function (a, b) { return a - b }) // 匿名函数作为参数,即回调函数
      
    • IIFE

      IIFE 即立即执行函数,简单来说就是匿名函数自调,定义的同时调用。

      var res = (function (x, y) {
          return x + y
      })(1,2)
      
      console.log(res) // 3
      

      注意:IIFE 可以避免内存泄露以及过多占用内存的情况,开发过程中,使用 IIFE 的作用域特性也可以隔离变量,防止命名冲突。

Object

Object 是定义 Object 数据类型,封装 Object 类型属性的对象。

  1. Object 数据类型

    Object 类型定义的方式有:字面量法,构造函数法。

    • 字面量法

      var obj = {
      	name: 'Jason',
      	age: 19,
      	getName: function () {
      		return this.name
      	}
      }
      
    • 构造函数法

      var obj = new Object()
      obj.name = 'Jason'
      obj.age = 19
      obj.getName = function () {
      	return this.name
      }
      
  2. 对象的属性和方法

    对象的本质是将一组变量和函数封装在同一命名空间下。对象封装的变量称为对象的属性,对象封装的函数称为对象的方法。

    • 属性

      对象的属性通过键值对表示的,键称之为属性名,值称为属性值。

      属性名可以使用无 '' 字符串或数字表示,属性值可以使用任意数据类型。

      var obj = {
          1: 1,
          name: 'jason',
          true: true,
          null: null,
          undefined: 'undefined',
          arr: [1,2,3],
      }
      

      属性的调用可以是使用 .[] 的方式调用,如 obj.nameobj['name']。但是,对于数字类型或者变量的属性名,必须使用 [] 调用,如 obj[1]

      var a = 'name'
      
      var obj = {
          name: 'jason'
      }
      
      console.log(obj[a]) // jason
      

      注意:属性名定义时使用无 '' 字符串,但是使用 [] 调用时,需要添加 '',如果是变量则无需添加。

    • 方法

      可以预见,属性值为 function 类型时,则属性就称之方法。

      var obj = {
          name: 'jason'
          run: function () {
              console.log('runing man')
          },
          getName: function() {
              console.log(this.name)
          }
      }
      

      方法的调用类似函数的调用,如 obj.run()

      注意:对象内部可以定义方法,方法中可以通过 this 关键字引用对象的属性,此时的 this 指向当前对象,可以 替换 this.nameobj.name

  3. 原型链和面向对象

    JavaScript 不是面向对象的语言,没有 class,extends 等类相关的关键字。面向对象语言的三大特性:封装、继承、多态,JavaScript只能通过原型链的方式去模拟。

    • 原型链

      原型链是由多级父对象逐级继承形成的链式结构,规定对象属性查找的范围,类似作用域链,属性查找:当前对象 --> 父对象 --> 祖父对象。具体可参考 v8中JavaScript对象

    • 面向对象

      JavaScript利用构造函数和原型模拟类,通过构造函数和原型的组合模拟继承。

      构造函数创建的同时,原型对象就会被自动创建,构造函数通过 prototype 指向原型对象,原型对象通过 constructor 指向构造函数。

      var fun = new Fun()
      
      function Fun() {
          // 构造函数
      }
      
      Fun.prototype.run = function () {
          console.log('running man')
      }
      
      fun.run() // running man
      console.log(Fun.prototype) // {run: ƒ, constructor: ƒ}
      console.log( Fun.prototype.constructor) // Fun
      

      原型对象添加方法时,只能通过 prototype.xx 的方式逐个添加,不能使用 prototype = { xx: ..., xxx: ... } 的方式添加。其原因是后面方式添加,原型对象赋值等于重写了原型对象,原型对象的 constructor 会指向 Object。此外,也丧失原型的动态性(动态性:实例在原型之前创建,修改原型,实例能立即提现)。

      var fun = new Fun()
      
      function Fun() {
          // 构造函数
      }
      
      Fun.prototype = {
          constructor: Fun,
          run: function () {
              console.log('running man')
          }
      }
      
      console.log(Fun.prototype) // {constructor: 'Fun', run: ƒ}
      console.log( Fun.prototype.constructor) // Fun
      fun.run() // TypeError: fun.run is not a function
      
    • 继承

      原型式继承

      function Father(argus) {
          this.argus = argus
      }
      
      Father.prototype.method = function () { ... }
      
      function Son(params) {
          this.params = params
      }
      
      Son.prototype = new Father()
      Son.prototype.constructor = Son
      
      Son.prototype.methond = function () {  ... } // 可以重写父方法,多态特性
      
      
      

      原型式继承存在的问题:

      • 子类实例化过程中,无法给父类传参。

      • 父类属性为引用类型时,子类继承属性,子类继承的属性修改时,会关联父类实例化的其他子类。

      构造函数式继承

      function Father(argus) {
          this.argus = argus
      }
      
      Father.prototype.method = function () { ... }
      
      function Son(argus, params) {
          Father.call(this, argus)
          this.params = params
      }
      
      Son.prototype.method = function () { ... }
      

      构造函数式继承存在的问题:

      • 子类只继承父类构造函数,父类的原型方法对子类不可见。

      • 子类实例化会执行一次父类构造函数。

      组合式继承

      function Father(argus) {
          this.argus = argus
      }
      
      Father.prototype.method = function () { ... }
      
      function Son(argus, params) {
          Father.call(this, argus)
          this.params = params
      }
      
      Son.prototype = new Father()
      Son.prototype.constructor = Son
      
      Son.prototype.method = function () { ... }
      

      组合式继承存在的问题:

      • 子类实例化执行两次

      寄生式继承

      function inherit(child, parent) {
          function Fun() {
              this.constructor = child
          }
          Fun.prototype = parent.prototype
          child.prototype = new Fun()
          return child
      }
      
      function Father(argus) {
          this.argus = argus
      }
      
      Father.prototype.method = function () { ... }
      
      function Son(params) {
          this.params = params
      }
      
      inherit(Son, Father)
      
      Son.prototype.method = function () { ... }
      

      寄生式继承存在的问题:

      • 子类实例化无法继承父类构造函数属性

      寄生组合式继承

      function inherit(child, parent) {
          function Fun() {
              this.constructor = child
          }
          Fun.prototype = parent.prototype
          child.prototype = new Fun()
          return child
      }
      
      function Father(argus) {
          this.argus = argus
      }
      
      Father.prototype.method = function () { ... }
      
      function Son(argus, params) {
          Father.call(this, argus)
          this.params = params
      }
      
      inherit(Son, Father)
      
      Son.prototype.method = function () { ... }
      

      寄生组合式继承是目前模拟继承较为妥当的方式。

后记

JavaScript 的核心语法知识包括:基本语法和内置对象,新增的 JavaScript 相关语法主要划分为两个版本:ES5 和 ES6。鉴于篇幅问题,ES5 和 ES6 梳理为碎片化学习即可,ES5 主要是规范性问题的研究,ES6 主要是简化开发问题的研究,可作为 JavaScript 内容学习的补充,简而言之,没有 ES5 和 ES6 你照样能工作!