JavaScript 知识梳理

作者: sylvia_yue | 来源:发表于2019-11-10 13:20 被阅读0次

    1.变量

    1.1 变量规则

    • 变量只能包含字母、下划线、美元符号或数字;
    • 第一个字符不能是数字。
    • ECMAScript 的变量是松散类型的 —— 可以用来保存任何类型的数据,即每个变量仅仅是一个用于保存值的占位符而已。

    1.2 定义变量

    1.2.1 var —— ES5及之前使用

    var name = "jane";
    

    var 没有块级作用域,只有函数级作用域和全局作用域:

    • 函数级作用域:
      在函数内用 var 定义一个变量,则该变量为定义该变量的作用域中的局部变量,函数退出后则销毁。
    • 全局作用域:
      省略 var ,则为全局变量,在函数外的任何地方都可被访问到(不推荐,难维护,且在严格模式下会抛出错误)。

    1.2.2 let & const —— ES6 新增

    ES6 中增加了 let 和 const :

    let 用来定义变量,定义后可更改;
    const 用来定义常量,定义后不可更改。

    let 和 var 的区别:

    • let 是块级作用域,var 是函数级作用域,let 的作用域更小;
    • let 无变量提升。
      下面定义的变量,在上面使用会报错;
      var有变量提升,下面定义的变量,在上面值为undefined,不会报错。
    • let 同一个变量只能声明一次,而 var 可声明多次。

    2. 数据类型

    JavaScript 中有基本类型和引用类型两类共七种数据类型。

    undefined 用于未定义的值 —— 只有一个 undefined 值的独立类型。
    null 用于未知的值 —— 只有一个 null 值的独立类型。
    boolean 用于 true 和 false。
    number 数字:整数或浮点数。
    string 字符串:一个字符串可以包含一个或多个字符。
    symbol 用于唯一的标识符。
    object 更复杂的数据结构,用于储存数据集合和更复杂的实体。

    2.1 基本类型

    2.1.1 Undefined

    定义:

    let a = undefined;
    

    使用var声明变量但未初始化,或初始化其值为undefined。

    (var message);alert(message);//undefined 
    

    2.1.2 Null

    定义:

    let a = null;
    

    空对象指针,用于在将来保存对象。这样只要检查变量是否等于null,就知道这个变量内是否已经保存了一个对象的引用。

    注:

    alert(null == undefined); //true
    alert(null === undefined); //false
    

    2.1.3 Boolean

    定义:

    let a = true;
    let b = false;
    

    ECMAScript 中所有类型的值都有与这两个 Boolean 值等价的值,可使用转型函数 Boolean() 进行转换。

    • 非空字符串、非零数值、任何对象都是 true;
    Boolean('111') === true
    
    • 空字符串、数值零或NaN、null、undefined 都是false。
    Boolean('text') === true//true
    Boolean(2) === true//true
    Boolean({}) === true//true
    Boolean({name: 'jane'}) === true//true
    
    • 流控制语句(如if)会自动执行 Boolean 转换。
    var message = 'hello';
    if(message){alert('value is true');}  //message被自动转换为对应的 Boolean 值(true)。
    

    2.1.4 Number

    2.1.4.1 定义

    //整数
    let num = 12;
    //八进制:第一位为0,后面数值若超出范围,则前导零被忽略,将后面数值按十进制解析
    let num1 = 070;//八进制的56
    let num2 = 079;//无效的八进制数值,解析为79
    //十六进制:前两位为0x,后跟十六进制数字
    let num3 = 0xa;  //十六进制的10
    let num4 = 0x1f;  //十六进制的31
    

    2.1.4.2 NaN

    NaN(非数值)——特殊的数值,本该返回数值的操作数未返回数值的情况,不抛出错误。

    1.任何涉及 NaN 的操作都会返回 NaN ,NaN 与任何值都不相等。

    alert(NaN == NaN);   //false
    

    2.isNaN()

    isNaN() 函数收到一个值后,会尝试将这个值转换为数值,可以转换则返回 false,否则返回 true。

    isNaN(true)//false
    isNaN(false)//false
    isNaN(1)//false
    isNaN('1')//false
    isNaN('2')//false
    isNaN(null)//false
    isNaN(undefined)//true
    isNaN({})//true
    isNaN(function(){})//true
    isNaN('2/0')//true
    

    2.1.4.3 数值转换

    Number():将任何类型转换为数值;

    parseInt():将字符串转换为整数;

    parseFloat():将字符串转换为浮点数;

    Number()
    var num1 = Number("Hello world!"); //NaN
    var num2 = Number(""); //0
    var num3 = Number("000011"); //11
    var num4 = Number(true); //1
    
    parseInt()
    var num1 = parseInt("1234blue"); // 1234
    var num2 = parseInt(""); // NaN
    var num4 = parseInt(22.5); // 22
    var num6 = parseInt("70"); // 70
    var num8 = parseInt("s2131"); // NaN
    var num = parseInt("0xAF", 16); //175
    var num1 = parseInt("AF", 16); //175
    var num2 = parseInt("AF"); //NaN
    指定基数转换:
    var num1 = parseInt("10", 2); //2 (按二进制解析)
    var num2 = parseInt("10", 8); //8 (按八进制解析)
    var num3 = parseInt("10", 10); //10 (按十进制解析)
    var num4 = parseInt("10", 16); //16 (按十六进制解析)
    
    parseFloat()
    从第一个字符开始解析,始终忽略前导零
    var num1 = parseFloat("1234blue"); //1234 (整数)
    var num2 = parseFloat("0xA"); //0
    var num3 = parseFloat("22.5"); //22.5
    var num4 = parseFloat("22.34.5"); //22.34
    var num5 = parseFloat("0908.5"); //908.5
    var num6 = parseFloat("3.125e7"); //31250000
    

    2.1.5 String

    • String 类型用于表示由零或多个16 位Unicode 字符组成的字符序列,即字符串。
    • 字符串可以由双引号(")或单引号(')表示,但前后符号应保持一致。

    2.1.5.1 定义

    var firstName = "Nicholas";
    var lastName = 'Zakas';
    

    2.1.5.2 toString()

    1. 数值、布尔值、对象和字符串都有一个 toString() 方法,返回一个对应字符串。
    let num = 2; num.toString();//"2"
    let flag = true; flag.toString();//"true"
    let text = 'hello'; text.toString();//"hello"
    

    数值的 toString() 方法可以传一个参数:输出数值的基数,如下:

    var num = 10;
    alert(num.toString()); // "10"  不传,默认为10
    alert(num.toString(2)); // "1010"
    alert(num.toString(8)); // "12"
    alert(num.toString(10)); // "10"
    alert(num.toString(16)); // "a"
    
    1. 对象的 toString() 方法
    let obj = {name: 'jane'};
    obj.toString() //"[object Object]"`
    
    1. null 和 undefined 没有 toString() 方法。

    2. String():
      不知道要转换的值是不是 null 或 undefined 时,可以使用转型函数 String(),这个函数可以将任何类型的值转换为字符串。String()遵循下列转换规则:

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

    2.1.5.3 String 的其他常用方法

    let testStr = 'testStr';
    testStr.substr(1,3) //"est" 截取 testStr 从第1个开始,取3个
    testStr.substring(1,4)  //"est" 截取 testStr 从第1个开始,到第4个,不包含第4个
    testStr.slice(1,4)  //"est" 截取 testStr 从第1个开始,到第4个,不包含第4个
    testStr.substring(2,0)  //"te"
    testStr.slice(2,0)  //""
    

    2.2 引用类型 —— 对象

    JavaScript 中,对象是及其重要的一点。其他六种是原始类型,他们的值只包含一种东西,而对象是用来存储键值对和更复杂的实体的。

    2.2.1 创建对象

    //“构造函数”语法创建(后面会详细介绍构造函数)
    let info = new Object()
    //“字面量”语法创建 —— 常用
    let info = {
        name: 'jane',   //键"name",值"jane"
        age: 17 //键"age",值17
    }
    

    2.2.2 对象操作

    先创建一个对象

    let info = {
        name: 'jane',   //键"name",值"jane"
        age: 17 //键"age",值17
    }
    

    2.2.2.1 添加属性

    //使用 .
    info.address = 'beijing'//"beijing",此时的 info:{name: "jane", age: 17, address: "beijing"}
    
    //使用 []
    info['friend'] = 'tom'//"tom",此时的 info:{name: "jane", age: 17, address: "beijing", friend: "tom"}
    

    2.2.2.2 计算属性

    计算属性 —— 在对象字面量中使用方括号,方括号内可以是任何属性名或变量

    let s = 'sch'.concat('ool);
    info[s] = 'HIT';//此时的 info:{name: "jane", age: 17, school: "HIT"}
    

    2.2.2.3 删除属性

    delete info.age//true,此时的 info:{name: "jane", address: "beijing", friend: "tom"}
    

    2.2.2.4 获取对象属性值

    //使用 .
    info.address//"beijing"
    
    //使用 []
    info['friend']//"tom"
    

    2.2.2.5 对象的可枚举与不可枚举

    对象的每个属性都有一个描述对象(Descriptor),用来控制该属性。

    Object.getOwnPropertyDescriptor() 方法返回指定对象上一个自有属性对应的属性描述符。

    (自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性)

    语法:

    Object.getOwnPropertyDescriptor(obj, prop)
    //obj:需要查找的目标对象, prop:目标对象内属性名称

    let info = {
        name: 'jane',   //键"name",值"jane"
        age: 17 //键"age",值17
    }
    let des = Object.getOwnPropertyDescriptor(info, 'name')
    //des
    {
        value: "jane", //该属性的值
        writable: true,   //当且仅当属性的值可以被改变时为true
        enumerable: true,   //当且仅当指定对象的属性可以被枚举出时,为 true
        configurable: true  //当且仅当指定对象的属性描述可以被改变或者属性可被删除时,为true
    }
    

    不可枚举(enumerable:false): 有以下操作会忽略不可枚举的属性。

    • for...in:不可枚举的属性不会被遍历。
    • Object.keys():不可枚举的属性不会被遍历。
    • JSON.stringify():不可枚举的属性不会被转换为字符串。
    • Object.assign(): 不可枚举的属性不会被复制。

    2.2.3 对象常用方法

    先创建一个对象

    let info = {
        name: 'jane',   //键"name",值"jane"
        age: 17 //键"age",值17
    }
    

    2.2.3.1 属性检查

    1. hasOwnProperty —— object.hasOwnProperty(propertyName),propertyName 必须为字符串

    info.hasOwnProperty('name') //true
    info.hasOwnProperty('language') //false
    

    2. "key" in object —— key 为要检测的属性

    "name" in info  //true
    "language" in info  //false
    

    2.2.3.2 对象遍历

    先创建一个对象

    let info = {
        name: 'jane',   //键"name",值"jane"
        age: 17 //键"age",值17
    }
    Object.defineProperty(info, 'name', {
      enumerable: true //可枚举
    });
    Object.defineProperty(info, 'age', {
      enumerable: false //可枚举
    });
    //在原型链上添加属性 —— 先了解,原型链部分后再看
    Object.defineProperty(info, 'eg1', {
      value: 'view',
      enumerable: true //可枚举
    });
    Object.defineProperty(info, 'eg2222', {
      value: 'unview',
      enumerable: false //不可枚举
    })
    

    1. for ... in —— 先遍历对象自身的可枚举属性,再遍历继承的可枚举属性。

    for (var index in info) {
      console.log('key:', index, 'value:', info[index])
    }
    //result 只遍历了自身及原型链上的可枚举属性
    key: name value: jane
    key: eg1 value: view
    

    2. Object.keys() —— 返回自身及原型链上的可枚举属性组成的数组,可通过遍历数组来遍历对象。

    Object.keys(info)
    //result 返回自身及原型链上的可枚举属性组成的数组
    ["name", "eg1"]
    
    Object.keys(info).forEach(item=>{
      console.log(item, info[item])
    })
    //result
    name jane
    eg1 view
    

    3. Reflect.ownKeys() —— 返回自身及原型链上所有属性组成的数组,可通过遍历数组来遍历对象。

    Reflect.ownKeys(info)
    //result 返回自身及原型链上的所有属性组成的数组,包括不可枚举属性
    ["name", "age", "eg1", "eg2222"]
    
    Reflect.ownKeys(info).forEach(item=>{
      console.log(item, info[item])
    })
    //result
    name jane
    age 17
    eg1 view
    eg2222 unview
    

    4. Object.getOwnPropertyNames() —— 返回自身及原型链上所有属性组成的数组(包括不可枚举属性),可通过遍历数组来遍历对象。

    Object.getOwnPropertyNames(info)
    //result 返回自身及原型链上的所有属性组成的数组,包括不可枚举属性
    ["name", "age", "eg1", "eg2222"]
    
    Object.getOwnPropertyNames(info).forEach(item=>{
      console.log(item, info[item])
    })
    //result
    name jane
    age 17
    eg1 view
    eg2222 unview
    

    2.3 变量赋值

    基本类型数据赋值:
    从一个基本类型变量向另一个变量赋值时,会在内存中新建一个地址,存放新的变量和复制过来的值;

    let a = 1;
    let b = a;
    b = 3;  
    console.log(a, b)// =>  a = 1; b = 3; a 和 b 的值存在于内存中的两个地址,互不影响。
    

    引用类型数据赋值:
    从一个引用类型变量向另一个变量赋值时,同上,但引用类型的值,实际上是一个指针,与初始变量指向同一个堆内存的对象。因此,这两个变量会互相影响。

    let s = {name:"jane"};let y = s; y.name="tom";console.log(s, y)
    let a = {name: "jane"};
    let b = a;
    b.name = "tom"; 
    b.city="cc";
    console.log(a, b) // =>  {name: "tom", city: "cc"} {name: "tom", city: "cc"} a 和 b 的值存在于内存中的两个地址,但值是同一个指针,指向同一个内存中的对象,属性的改变会相互影响。
    

    3. 函数 & 原型 & 继承

    3.1 函数

    3.1.1 函数声明:

    //语法:
    function 函数名(参数1, 参数2,...,参数N) {
        函数体
    }
    //eg:
    function sayHi(name, message) {
        alert("Hello " + name + "," + message);
    }
    

    3.1.2 函数调用

    //语法
    functionName(arg0, arg1,...,argN);
    //eg:
    sayHi("Nicholas", "how are you today?");//弹出 "Hello Nicholas,how are you today?"
    

    3.1.3 变量作用域

    • 函数内声明的变量,只在该函数内部可见。
    function sayHi() {
      let name = 'jane';
      let age = 18;
      alert(name + ":" + age);
    }
    console.log('age:', age);//age is not defined
    
    • 函数外声明的变量,函数可以访问,也可以修改。
    let age = 20;
    function sayHi() {
      let name = 'jane';
      age = 18;
      console.log(name + ":" + age);
    }
    sayHi() //jane:18
    

    3.1.4 函数返回值

    任何函数、任何时候都可以通过 return 语句后跟要返回的值来是实现返回值。函数会在执行完 return 语句后停止并立即退出。因此,return 之后的任何代码都永远不会执行。

    function sum(num1, num2) {
        return num1 + num2;
    }
    let result = sum(5, 10);  // result 为15
    

    注:

    • return 可以不带任何返回值。则函数将返回 undefined 值。一般用在需要提前停止函数执行,又不需要返回值的情况下。
    • 推荐:要么让函数始终都有返回值,要么就永远都不要有返回值,否则会给代码调试带来不便。

    严格模式下的限制: 发生以下情况,会导致语法错误,代码无法执行。

    • 不能把函数/参数命名为eval 或arguments;
    • 不能出现两个命名参数同名的情况。

    3.1.5 参数

    参数不是必需的,可以不传,个数及类型,即使个数与定义的个数不同,也不会报错。因为参数在内部使用一个数组来表示的。
    函数体内部可以通过 arguments 对象来访问这个参数数组,从而获取传递过来的每一个参数。

    function sayHi(name, message) {
        alert("Hello " + name + "," + message);
    }
    sayHi('jane', 'nice to meet you!')//Hello jane,nice to meet you!
    //重写
    function sayHi() {
        alert("Hello " + arguments[0] + "," + arguments[1]);
    }
    sayHi('jane', 'nice to meet you!')//Hello jane,nice to meet you!
    
    • arguments 对象的长度由传入函数的参数决定;
    • 没有传递值的命名参数自动被赋予 undefined 值,类似于定义了变量但未初始化。

    严格模式下:

    重写arguments 的值会导致语法错误(代码将不会执行)

    3.1.6 ES6 函数新特性

    1. 参数默认值

    声明函数时直接为参数设置默认值,若调用函数时未赋值,则参数的值为默认值。

    function sayHi(name, message='nice to meet you!') {
        console.log("Hello " + name + "," + message);
    }
    sayHi('jane', 'yeah!')  //Hello jane,yeah!
    sayHi('tom')  //Hello tom,nice to meet you!
    

    若默认值为表达式,则执行时才计算其值

    let num = 10;
    function sayHi(name, age=num+8) {
        console.log(name + ", age:" + age);
        num+=10;
    }
    sayHi('jane')  //jane, age:18
    sayHi('tom')  //tom, age:28
    sayHi('lucy', 12)   //lucy, age:12
    

    2. rest 参数
    ...变量名,用于获取函数的剩余参数,将其放在数组中。
    rest 参数必须是最后一个参数,否则会报错。

    function sum(...nums){
      let sum = 0;
      nums.forEach(item => {
        sum += item;
      })
      return sum
    }
    let result1 = sum()   //0
    let result2 = sum(1,2,3,4)   //10
    

    3. 扩展运算符
    ...变量名,用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中。

    //对象
    let obj1 = {name: 'jane', age: 18};
    let obj2 = {color: 'yellow', ...obj1} //obj2: {color: "yellow", name: "jane", age: 18}
    //等价于
    let obj3 = Object.assign({color: 'yellow'}, obj1)   //obj3: {color: "yellow", name: "jane", age: 18}
    
    //数组
    let arr1 = [1,2,3]
    let arr2 = [4,5,6]
    let arr3 = [7,8,9]
    let arr4 = [...arr1, ...arr2, ...arr3]  //[1, 2, 3, 4, 5, 6, 7, 8, 9]
    //等价于
    let arr5 = arr1.concat(arr2, arr3)  //[1, 2, 3, 4, 5, 6, 7, 8, 9]
    

    4. 箭头函数

    //箭头函数定义
    let sum = (a,b) => {
      return a+b
    }
    //等同于
    let sum = function(a,b) {
      return a+b
    }
    
    • 箭头函数的 this 指向的是定义时的 this 对象,而不是执行时的 this 对象
    • 箭头函数是匿名函数 ,不能作为构造函数
    • 箭头函数不可以使用 arguments,可使用 rest 参数

    5. name
    ES6 的 name 会返回函数名。

    function sum(...nums){
      let sum = 0;
      nums.forEach(item => {
        sum += item;
      })
      return sum
    }
    console.log(sum.name);    //"sum"
    

    3.2 构造函数

    1. 构造函数: 一种特殊的函数。

    • 主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new操作符一起使用在创建对象的语句中。
    • 我们可以使用构造函数来创建多个类似的对象,而免于代码重复。
    • JavaScript 内置了一些构造函数

    2. 构造函数特点

    • 首字母大写(通常约定)
    • 只能用 new 操作符来执行
    • 内部使用的 this 对象,会指向即将生成的实例对象

    3. 实例化一个对象的过程

    new 一个新对象 => this 指向这个新对象 => 执行代码(对 this 赋值)=> 返回 this

    function Person(name, age) {
        this.name = name;
        this.age = age;
        this.displayInfo = function() {
            console.log(`name: ${name}, age: ${age}`)
        }
    }
    let jane = new Person('jane', 12)
    jane.displayInfo()  //name: jane, age: 12
    let tom = new Person('tom', 18)
    tom.displayInfo()   //name: tom, age: 18
    

    JavaScript 内置了很多构造函数,如:
    Object、Function、Array、RegExp、Date、Number、String、Boolean。

    Object2.2 引用类型 已经详细介绍过了。
    在 JavaScript 中,Function、Array、RegExp、Date、Number、String、Boolean 是构造函数,也有自己的构造函数,他们的构造函数就是 Object,即 Object 是其他构造函数的构造函数。

    后面还会介绍一个 Math 对象,Math 不是对象的类,没有构造函数,但作为 JavaScript 内置的对象,可直接调用来进行数学方法的处理。

    3.2.1 Function

    3.2.1.1 函数声明:

    //1. 构造函数创建 new Function
    let 函数名 = new Function(参数1, 参数2,...,参数N, 函数体)
    //eg:
    let sayHi = new Function("name", "message", "alert('Hello " + name + "," + message)')
    
    sayHi3('jane','hi')//Hello jane,hi
    

    但实际的代码中不使用这种方法来创建函数,还是会用字面量的方法去创建,关于函数的详细介绍,见 3.1 函数

    3.2.2 Array

    3.2.2.1 创建数组

    创建数组:

    //使用new Array()
    let arr1 = new Array(); //[]
    let arr2 = new Array(5); //[empty × 5]
    let arr3 = new Array('beijing','tianjin',12);//["beijing", "tianjin", 12]
    
    //使用方括号 —— 常用
    let arrs1 = [];//[]
    let arrs2 = ['beijing','tianjin',12];//["beijing", "tianjin", 12]
    

    数组内的元素可以是任何类型:

    let arr = ['beijing', {name: 'jane'}, 12, true, function(){console.log('fn')}];
    //0: "beijing"
    //1: {name: "jane"}
    //2: 12
    //3: true
    //4: ƒ ()
    

    数组编号从 0 开始:

    let arr = ['beijing','tianjin',12];
    //arr[0]: 'beijing'
    //arr[1]: 'tianjin'
    //arr[2]: 12
    

    通过 数组名[下标] 来获取或设置值:

    let arr = ['beijing','tianjin',12];
    console.log(arr[0]);// 'beijing'
    arr[3] = true;
    console.log(arr); //["beijing", "tianjin", 12, true]
    

    3.2.2.2 常用方法

    判断是否为数组:Array.isArray

    //Array.isArray —— 是则返回 true,否责返回 false
    let a = [1,2,3];
    let b = 'str';
    let b = [];
    console.log(Array.isArray(a), Array.isArray(b), Array.isArray(c));  //  true false true
    
    //unshift —— 在数组最前面添加元素,可添加多个
    arr.unshift('a', 'b', 'c')
    console.log(arr);//arr: ["a", "b", "c", 1, 2, 3, 4, 5, 6]
    

    添加数组元素:push、unshift

    //push —— 在数组最后添加元素,可添加多个
    let arr = [1,2,3];
    arr.push(4,5,6);
    console.log(arr);//arr: [1,2,3,4,5,6]
    
    //unshift —— 在数组最前面添加元素,可添加多个
    arr.unshift('a', 'b', 'c')
    console.log(arr);//arr: ["a", "b", "c", 1, 2, 3, 4, 5, 6]
    

    删除数组元素:pop、shift

    //pop() —— 删除并返回数组的最后一个元素
    console.log(arr.pop())//6
    console.log(arr);//["a", "b", "c", 1, 2, 3, 4, 5]
    
    //shift() —— 删除并返回数组的第一个元素
    console.log(arr.shift()) //a
    console.log(arr); //["b", "c", 1, 2, 3, 4, 5]
    

    替换数组中的元素:splice

    //splice(start, num, newitem) —— 把 arr[start] 开始的 num 个,替换成后面的元素,不改变原数组
    console.log(arr)    //[5, 4, 3, 2, 1, "c", "b"]
    console.log(arr.splice(2,3,'jane')) //[3, 2, 1]
    console.log(arr)    //[5, 4, "jane", "c", "b"]
    console.log(arr.splice(1,1,'aa','bb', 'cc')) //[4]
    console.log(arr)    //[5, "aa", "bb", "cc", "jane", "c", "b"]
    

    替换数组中某些元素:fill

    //fill(value, start, end)
    let arr = [1, 2, 3, 4, 5, 5, 6, "b", "c"]
    let result1 = arr.fill('hello'); 
    // result1: ["hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello"]
    let result2 = arr.fill('hello', 2, 5); 
    //result2: [1, 2, "hello", "hello", "hello", 5, 6, "b", "c"]
    let result3 = arr.fill('hello', 2, );
    //result3: [1, 2, "hello", "hello", "hello", "hello", "hello", "hello", "hello"]
    

    取数组中的元素:slice

    //slice(start, end) —— 取 [arr[start], ...,  arr[end-1]],不改变原数组
    console.log(arr)    //[5, 4, 3, 2, 1, "c", "b"]
    console.log(arr.slice(2,5)) //[3, 2, 1]
    console.log(arr)    //[5, 4, 3, 2, 1, "c", "b"]
    

    颠倒数组中元素的顺序:reverse

    //reverse —— 颠倒数组中元素的顺序
    console.log(arr)    //["b", "c", 1, 2, 3, 4, 5]
    console.log(arr.reverse())   //[5, 4, 3, 2, 1, "c", "b"]
    console.log(arr)    //[5, 4, 3, 2, 1, "c", "b"]
    

    数组排序:sort

    //sort —— 将数组中的元素进行排序
    let arrN = [1,2,4,6,12,11]
    arrN.sort() //[1, 11, 12, 2, 4, 6]
    arrN.sort((a, b)=>{return a-b}) //[1, 2, 4, 6, 11, 12] 从小到大排序
    arrN.sort((a, b)=>{return b-a}) //[12, 11, 6, 4, 2, 1] 从大到小排序
    

    数组转换为字符串:toString、join

    //1. toString —— 将数组转换为字符串
    arr.toString()  //"5,4,3,2,1,c,b
    
    //2. join —— 通过指定的分隔符, 把数组的所有元素连接成一个字符串
    arr     //[5, 4, 3, 2, 1, "c", "b"]
    arr.join()  //"5,4,3,2,1,c,b"
    arr.join('-')   //"5-4-3-2-1-c-b"
    

    连接数组:concat

    //concat 连接数组,并返回结果,不改变原数组
    let arr1 = ['jane', 'tom']
    let arr2 = ['green', 'red']
    let arr3 = [true, false]
    let arr4 = [1, 2, 3]
    arr1.concat(arr2)  //["jane", "tom", "green", "red"]
    arr1.concat(arr2, arr3, arr4) //["jane", "tom", "green", "red", true, false, 1, 2, 3]
    

    查询数组: indexOf、lastIndexOf、find、findIndex、filter

    let arr1 = [5, 4, 3, 2, 1, 5, 6, "c", "b"];
    //indexOf(item) —— 从前面开始查询,返回查到的第一个 item 的下标值
    arr1.indexOf(5)   //0
    
    //lastIndexOf(item) —— 从后面开始查询,返回查到的第一个 item 的下标值
    arr1.lastIndexOf(5)   //5
    
    //includes(item) —— 判断数组中是否存在 item,存在则返回 true,不存在则返回 false
    arr1.includes(0)     //false
    arr1.includes(1)     //true
    
    //find —— 遍历数组,返回使函数返回 true 的第一个元素
    let arr2 = [
        { name: 'jane', age:12 },
        { name: 'tom', age:13 },
        { name: 'lucy', age:14 },
        { name: 'jimmy', age:12 }
    ]
    let result1 = arr2.find((item, index, array) => {
        return item.age === 12
    })
    console.log('result1:', result1)    //result: {name: "jane", age: 12}
    
    //findIndex —— 遍历数组,返回使函数返回 true 的第一个元素的下标
    let result2 = arr2.findIndex((item, index, array) => {
        return item.age === 12
    })
    console.log('result2:', result2)    //result: 0
    
    //filter —— 遍历数组,返回使函数返回 true 的所有元素组成的数组
    let result3 = arr2.filter((item, index, array) => {
        return item.age === 12
    })
    console.log('result3:', result3)    
    //result3: [{ name: 'jane', age:12 },{ name: 'jimmy', age:12 }]
    

    检查元素:some、every

    //some —— 数组中至少有一个元素在执行函数时返回的结果为 true
    let arr1 = [1, 2, 3, 4, 5, 5, 6, 7]
    let arr2 = [1, 2, 3, 4, 5, 5, 6, 7, 8]
    let flag1 = arr1.some((item)=>{
        return item > 7
    })  //flag: false
    let flag2 = arr2.some((item)=>{
        return item > 7
    })  //flag: true
    
    //every —— 数组中每一个元素在执行函数时返回的结果为 true
    let arr1 = [1, 2, 3, 4, 5, 5, 6, 7]
    let arr2 = [1, 2, 3, 4, 5, 5, 6, 7, 8]
    let flag1 = arr1.every((item)=>{
        return item > 7
    })  //flag: false
    let flag2 = arr2.every((item)=>{
        return item > 7
    })  //flag: false
    let flag3 = arr2.every((item)=>{
        return item > 0
    })  //flag: true
    

    转换数组:map

    let arr = [1, 2, 3, 4, 5, 5, 6, "b", "c"]
    let result = arr.map(function(item, index, array) {
      return `${item}--hello`;
    })
    //result:   ["1--hello", "2--hello", "3--hello", "4--hello", "5--hello", "5--hello", "6--hello", "b--hello", "c--hello"]
    

    遍历数组:forEach

    let arr = [1, 2, 3, 4]
    arr.forEach(function(item, index, array) {
      console.log(item, index, array)
    })
    // 1 0 (4) [1, 2, 3, 4]
    // 2 1 (4) [1, 2, 3, 4]
    // 3 2 (4) [1, 2, 3, 4]
    // 4 3 (4) [1, 2, 3, 4]
    

    以上会改变数组的方法:
    push、unshift、pop、shift、splice、reverse、sort、fill

    3.2.3 Date

    Date 为 JavaScript 内置的一个对象,用来存储日期、时间,并提供了一些管理它们的方法。

    //创建 Date 对象
    let date = new Date()//date: Wed Jun 05 2019 17:10:27 GMT+0800 (中国标准时间)
    
    //getDay:返回 0-6,对应星期日到星期六
    date.getDay()   //3
    
    //getDate:返回 1-31,对应一个月中的 1-31 号
    date.getDate()  //5
    
    //getMonth:返回 0-11,对应1-12月
    date.getMonth() //5——6月 
    
    //getFullYear:以四位数字返回年份
    date.getFullYear()  //2019
    
    //getHours:返回小时
    date.getHours()   //17
    //getMinutes:返回分钟
    date.getMinutes()   //10
    //getSeconds:返回秒
    date.getSeconds()   //27
    //getMilliseconds:返回毫秒
    date.getMilliseconds()  //511
    
    //getTime:返回 1970 年 1 月 1 日至今的毫秒数
    date.getTime()  //1559725827511
    

    Date.now()

    有一个特殊的方法 Date.now(),它会返回当前的时间戳,不需要整个 Date 对象。

    它相当于 new Date().getTime(),但不会在中间创建一个 Date 对象。因此更快,且不会对垃圾处理造成额外的压力。

    Date.now()  //1559725827511
    

    3.2.4 RegExp

    RegExp 是 JavaScript 内置的正则表达式对象,用来对字符串进行匹配。

    3.2.4.1 定义正则表达式

    1)字面量语法:/pattern/attributes

    2)创建 RegExp 对象的语法:new RegExp(pattern, attributes);

    3.2.4.2 参数

    1. pattern 是一个字符串,指定了正则表达式的模式或其他正则表达式。
    2. attributes 是一个可选的字符串,包含属性 "g"、"i" 和 "m",分别用于指定全局匹配、区分大小写的匹配和多行匹配。如果 pattern 是正则表达式,而不是字符串,则必须省略该参数。

    3.2.4.3 修饰符

    i  执行对大小写不敏感的匹配。
    g  执行全局匹配(查找所有匹配而非在找到第一个匹配后停止)。
    m  执行多行匹配。
    

    3.2.4.4 方括号——查找某个范围内的字符

    [abc]  查找方括号之间的任何字符。(字母、数字、符号、中文均可)
    [^abc]  查找任何不在方括号之间的字符。(字母、数字、符号、中文均可)
    [0-9]  查找任何从 0 至 9 的数字。
    [a-z]  查找任何从小写 a 到小写 z 的字符。
    [A-Z]  查找任何从大写 A 到大写 Z 的字符。
    [A-z]  查找任何从大写 A 到小写 z 的字符。
    [adgk]  查找给定集合内的任何字符。
    [^adgk]  查找给定集合外的任何字符。
    (red|blue|green)  查找任何指定的选项。(可匹配单词)
    

    3.2.4.5 元字符(Metacharacter)——拥有特殊含义的字符

    .  查找单个字符,除了换行和行结束符。
    \w  查找单词字符。(数字、字母、下划线)
    \W  查找非单词字符。(除数字、字母、下划线的字符,包括中文、特殊符号、空格等)
    \d  查找数字。
    \D  查找非数字字符。
    \s  查找空白字符。
    \S  查找非空白字符。
    \b  匹配单词边界。
    \B  匹配非单词边界。
    \0  查找 NUL 字符。
    \n  查找换行符。
    \f  查找换页符。
    \r  查找回车符。
    \t  查找制表符。
    \v  查找垂直制表符。
    \xxx  查找以八进制数 xxx 规定的字符。
    \xdd  查找以十六进制数 dd 规定的字符。
    \uxxxx  查找以十六进制数 xxxx 规定的 Unicode 字符。
    

    3.2.4.6 量词

    n+  =>  匹配任何包含至少一个 n 的字符串。
    n*  =>  匹配任何包含零个或多个 n 的字符串。
    n?  =>  匹配任何包含零个或一个 n 的字符串。
    n{X}  =>  匹配包含 X 个 n 的序列的字符串。 a{2}  aa
    n{X,Y}  =>  匹配包含 X 至 Y 个 n 的序列的字符串。a{2,4} aa aaa aaaa  
    n{X,}  =>  匹配包含至少 X 个 n 的序列的字符串。a{2}  aa aaa aaaa aaaaa...
    n$  =>  匹配任何结尾为 n 的字符串。
    ^n   => 匹配任何开头为 n 的字符串。
    ?=n   => 匹配任何其后紧接指定字符串 n 的字符串。
    ?!n   => 匹配任何其后没有紧接指定字符串 n 的字符串。
    

    3.2.4.7 RegExp 对象方法

    1. test => 检索字符串中指定的值。返回 true 或 false。

    let reg = /[a-z]/i;
    reg.test('A')   //true
    reg.test('z')   //true
    reg.test(1)    //false
    

    2. exec => 检索字符串中指定的值。返回找到的值,并确定其位置。

    let str = 'hello everybody!';
    let reg = /e/i;
    reg.exec(str)   //["e", index: 6, input: "hello everybody!", groups: undefined]
    reg.exec('abc')    //null
    

    3. compile => 编译正则表达式。用于改变 RegExp 对象
    RegExpObject.compile(regexp,modifier)

    let str = 'hello everybody!';
    let reg = /e/i;
    reg.compile('[0-9]', g)//reg: /[0-9]/g
    

    3.2.4.8 支持正则表达式的 String 对象的方法

    1. search => 检索与正则表达式相匹配的值(返回第一个匹配到的位置)

    let str = 'hello everybody!';
    let reg = /l/i;
    str.search(reg) //2
    reg = /f/g;
    str.search(reg)  //-1
    

    2. match => 找到一个或多个正则表达式的匹配。(返回匹配到的值)

    let str = 'hello everybody!';
    let reg = /l/g;
    str.match(reg) //["l", "l"]
    reg = /f/g;
    str.match(reg)  //null
    

    3. replace => 替换与正则表达式匹配的子串(返回替换后的整个字符串,改变原字符串)

    let str = 'hello everybody!';
    let reg1 = /l/;
    str.replace(reg1, 'p')  //"heplo everybody!"
    str = 'hello everybody!';
    let reg2 = /l/g;
    str.replace(reg2, 'p')  //"heppo everybody!"
    

    4. split => 在符合 separator 的位置把字符串分割为字符串数组。

    let str = 'hello everybody!';
    let reg = /e/g;
    str.split(reg)  //["h", "llo ", "v", "rybody!"]
    

    3.2.5 Number、String、Boolean 构造函数

    前面 2.1 基本类型 章节已经介绍了 Number、String、Boolean 类型,下面介绍下 Number、String、Boolean 三种构造函数。

    3.2.5.1 Number 构造函数

    • 通过 Number 构造函数创建的是对象
    let numObj = new Number(2);
    let num = 2;
    
    typeof numObj  //"object"
    typeof num //"number"
    numObj === num; //false
    
    //通过 Number 构造函数创建的是对象可以调用数字的方法
    let sum = numObj + numObj;  //4
    let str = numObj + 'hello';  //"2hello"
    
    //通过 Number 构造函数创建的是对象可以添加属性
    numObj.name='jane'//
    console.log(numObj.name)   //"jane"
    num.name='tom'
    console.log(num.name)  //undefined
    
    • Number() 也可以把各种值转换为数字,不能转换的就是 NaN。
    Number('1')   //1
    Number(true)  //1
    Number(false)   //0
    Number('text')  //NaN
    

    3.2.5.2 String 构造函数

    • 通过 String 构造函数创建的是对象
    let stringObj = new String('abc');
    let string = 'abc';
    
    typeof stringObj  //"object"
    typeof string //"string"
    
    stringObj === string; //false
    
    //通过 String 构造函数创建的是对象可以添加属性
    stringObj.name='jane'//
    console.log(stringObj.name)   //"jane"
    string.name='tom'
    console.log(string.name)  //undefined
    
    //通过 String 构造函数创建的是对象可以调用字符串的方法
    let testStr = new String('testStr');
    testStr.substr(1,3) //"est" 截取 testStr 从第1个开始,取3个
    testStr.substring(1,4)  //"est" 截取 testStr 从第1个开始,到第4个,不包含第4个
    testStr.slice(1,4)  //"est" 截取 testStr 从第1个开始,到第4个,不包含第4个
    testStr.substring(2,0)  //"te"
    testStr.slice(2,0)  //""
    
    • String() 也可以把各种值转换为字符串。
    String(1)   //"1"
    String(true)  //"true"
    String(false)   //"false"
    String('text')  //"text"
    String([1,2,3])   //"1,2,3"
    String({name: 'jane'})  //"[object Object]"
    

    3.2.5.3 Boolean 构造函数*

    通过 Boolean 构造函数创建的是对象

    let booleanObj = new String(true);
    let boolean = true;
    
    typeof booleanObj  //"object"
    typeof boolean //"boolean"
    
    booleanObj === boolean; //false
    
    //通过 Boolean 构造函数创建的是对象可以添加属性
    booleanObj.name='jane'//
    console.log(booleanObj.name)   //"jane"
    boolean.name='tom'
    console.log(boolean.name)  //undefined
    
    
    • Boolean() 也可以把各种值转换为布尔值,同 2.1 基本类型 里面介绍的一样 。
    Boolean('111') === true
    Boolean('text') === true//true
    Boolean(2) === true//true
    Boolean({}) === true//true
    Boolean({name: 'jane'}) === true//true
    

    3.2.6 Math

    Math 不是对象的类,没有构造函数,但作为 JavaScript 内置的对象,可直接调用来进行数学方法的处理。

    let num = 12345.12;
    //四舍五入
    Math.round(num);    //12345
    //下舍入
    Math.floor(num);    //12345
    //上舍入
    Math.ceil(num);    //12346
    //取两个数中的最高值
    Math.max(2,3)   //3
    //取两个数中的最低值
    Math.max(2,3)   //2
    //返回 0 ~ 1 之间的随机数
    Math.random()   //0.657487039270241
    

    3.3 原型

    所有引用类型其隐式原型都指向它的构造函数的显式原型

    obj.__proto__ === Object.prototype

    • 当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,会去它的 __proto__ (即其构造函数的 prototype )中寻找。

    隐式原型:

    所有引用类型(数组、对象、函数),都有一个 __proto__ 属性,属性值是一个普通对象。

    显式原型:

    所有的函数都有一个 prototype 属性,属性值是一个普通对象。

    hasOwnProperty:

    只判断对象本身是否包含某属性,不去其原型链中寻找。

    3.4 原型链

    原型链结构图

    当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,会去它的 __proto__ (即其构造函数的 prototype )中寻找,这在编程中被称为“原型继承”。

    3.5 判断数据类型(构造函数、原型、typeof)

    3.5.1 typeof

    typeof——返回给定变量的数据类型,可能返回如下字符串:

    返回字符串 —— 数据类型
    'undefined' —— Undefined
    'boolean' —— Boolean
    'string' —— String
    'number' —— Number
    'symbol' —— Symbol
    'object' —— Object / Null (Null 为空对象的引用)
    'function' —— Function
    
    • typeof 返回的类型都是字符串形式,需注意,例如:
    alert(typeof 'abcd' === "string")   =>   true
    alert(typeof 'abcd' === String)   =>    false
    
    • typeof 对于Array、RegExp、Date 类型统一返回 'object'
    alert(typeof [] === 'object')   =>    true
    alert(typeof new Date)   =>    'object'
    
    • typeof 对于 function 类型统一返回 'function'
    alert(typeof function(){} === 'function')   =>    true
    
    • typeof 是操作符而非函数,所以可以使用圆括号,也可以不使用。
    alert(typeof 'abcd'); /     alert(typeof ('abcd'));
    

    3.5.2 instanceof

    判断一个函数是否是变量的构造函数
    语法: objectA instanceof constructorB
    判断逻辑: 变量a的 _ proto _ 一层一层往上找,用来检测 constructor.prototype 是否存在于参数 object 的原型链上,是则返回 true,不是则返回 false。

        alert([1,2,3] instanceof Array) ---------------> true
        alert(new Date() instanceof Date) ------------> true
        alert(function(){this.name="22";} instanceof Function) ------------> true
        alert(function(){this.name="22";} instanceof function) ------------> false
    

    3.5.3 constructor

    返回对象对应的构造函数

    alert(({}).constructor === Object);  =>  true
    alert(([]).constructor === Array);  =>  true
    alert('abcde'.constructor === String);  =>  true
    alert((1).constructor === Number);  =>  true
    alert(true.constructor === Boolean);  =>  true
    alert(false.constructor === Boolean);  =>  true
    alert((function s(){}).constructor === Function);  =>  true
    alert(new Date().constructor === Date);  =>  true
    alert(new Array().constructor === Array);  =>  true
    alert(new Error().constructor === Error);  =>  true
    alert(document.constructor === HTMLDocument);  =>  true
    alert(window.constructor === Window);  =>  true
    alert(Symbol().constructor === Symbol);    =>    true
    null 和 undefined 是无效的对象,没有 constructor,因此无法通过这种方式来判断。
    

    注:

    • constructor 不能用来判断 Null 及 Undefined 类型
    • 函数的 constructor 不稳定。
      当一个函数被定义时,JS 引擎会为其添加 prototype 原型,然后在 prototype 上添加一个 constructor 属性,并让其指向函数的引用。
      但函数的 prototype 被重写后,原有的 constructor 引用会丢失。再次新建一个次函数的实例后,其 constructor 指向的内容已发生改变。
      因此为了规范开发,在重写对象原型时,一般都需要重新给 constructor 赋值,以保证对象实例的类型不被更改。

    3.5.4 Object.prototype.toString()

    • toString() 是 Object 的原型方法,调用该方法,默认返回当前对象的 [[Class]] 。
    • 这是一个内部属性,其格式为 [object Xxx] ,是一个字符串,其中 Xxx 就是对象的类型。
    • 对于 Object 对象,直接调用 toString() 就能返回 [object Object] 。而对于其他对象,则需要通过 call / apply 来调用才能返回正确的类型信息。
    Object.prototype.toString.call(new Date) === '[object Date]';   //true
    Object.prototype.toString.call(new String) === '[object String]';   //true
    Object.prototype.toString.call(Math) === '[object Math]';   //true
    Object.prototype.toString.call(undefined) === '[object Undefined]';   //true
    Object.prototype.toString.call(null) ==='[object Null]';   //true
    Object.prototype.toString.call('') === '[object String]' ;      //true 
    Object.prototype.toString.call(123) === '[object Number]' ;       //true
    Object.prototype.toString.call(true) === '[object Boolean]' ;    //true 
    Object.prototype.toString.call(Symbol()) === '[object Symbol]';    //true
    Object.prototype.toString.call(new Function()) === '[object Function]';    //true
    Object.prototype.toString.call(new Date()) === '[object Date]' ;    //true
    Object.prototype.toString.call([]) === '[object Array]';   //true
    Object.prototype.toString.call(new RegExp()) === '[object RegExp]' ;    //true
    Object.prototype.toString.call(new Error()) === '[object Error]';   //true
    Object.prototype.toString.call(document) === '[object HTMLDocument]';   //true
    Object.prototype.toString.call(window) === '[object Window]';   //true
    

    类型判断小结:

    • 1)typeof 更适合判断基本类型数据,因为对于引用类型数据,typeof 只会返回 ‘function’ 或 ‘object’,不会返回其他的数组等类型;
    • 2)instanceof 只能用来判断实例类型,包括 Array、Date 等;
    • 3)constructor 不能用来判断 Null 及 Undefined 类型
    • 4)注:new String()、new Number() 生成的实际上为对象,但只能通过 typeof 能判断出来,constructor 和 .toString 只会返回 String 或 Number,无法判断是基本类型或是引用类型。

    3.6 模拟面向对象的类

    面向对象的编程中的类: 是用于创建对象的可扩展的程序代码模版,它为对象提供了状态(成员变量)的初始值和行为(成员函数和方法)的实现。

    ES6 中封装了 class,在 ES6 中的 class 出现前,也可以通过其他的编程模式来创建类。

    3.6.1 基于构造函数的类

    function Person(name, age) {
        function getAge() {
            return age + 10;
        } 
        this.outputInfo = function() {
            console.log('name:', name, 'currentAge: ', getAge())
        }
    }
    let jane = new Person('jane', 14);
    jane.outputInfo()     //name: jane currentAge:  24
    
    • 优点:
      如上,getAge 方法是外部不可见的(私有的),可隐藏内部方法的实现;而 outputInfo 被分配到 this 上,则是外部可见的(即公有的),用此构造函数创建的对象可以使用共有方法。

    • 缺点:
      所有的方法都在构造函数内部,每次 new 一个实例对象,都会创建内部的这些方法,且不同实例对象间不能共享这些方法,浪费内存资源。

    3.6.2 基于原型的类

    function Person(name, age) {
        this.name = name;
        this.age = age;
    }
    Person.prototype._getAge = function() {
        return this.age + 10;
    }
    
    Person.prototype.outputInfo = function() {
        console.log('name:', this.name, 'currentAge: ', this._getAge())
    }
    let jane = new Person('jane', 14);
    jane.outputInfo()     //name: jane currentAge:  24
    
    • 优点:
      如上,将公共方法放在原型中,创建新对象直接调用原型中的方法,对象中没有的属性和方法,可以在原型链中寻找。

    3.7 ES6 中的 class

    在 JavaScript 中,class 实际上是一种“语法糖”,我们可以使用 class 去干净整洁地定义基于原型的类。

    3.7.1 class 使用

    3.7.1.1 基于原型实现类

    function Person(name, age) {
        this.name = name;
        this.age = age;
    }
    Person.prototype._getAge = function() {
        return this.age + 10;
    }
    
    Person.prototype.outputInfo = function() {
        console.log('name:', this.name, 'currentAge: ', this._getAge())
    }
    let jane = new Person('jane', 14);
    jane.outputInfo()     //name: jane currentAge:  24
    

    3.7.1.2 基于 class 实现相同的类

    class Person {
        constructor(name, age) {
            this.name = name;
            this.age = age;
        }
        getAge () {
            return this.age + 10;
        }
        outputInfo () {
            console.log('name:', this.name, 'currentAge: ', this.getAge())
        }
    }
    let jane = new Person('jane', 14);
    jane.outputInfo()     //name: jane currentAge:  24
    

    3.7.2 class 完成了如下过程:

    • 声明了 Person 变量,且将它的值指向 constructor 函数,如下图:
    Person
    • 所有类中定义的方法挂到了 Person.prototype 上,如下图:
    Person.prototype

    3.7.3 class 特点:

    • 必须与 new 一同使用,如下图:
    without new
    • class 拥有一个默认的 constructor(){}
      class 中没有定义 constructor 的话,会默认生成一个空的构造函数:constructor(){}

    • class 中的方法都是不可枚举的
      这样遍历新建的对象时,类上的方法不会被遍历。

    • class 中只能定义方法

    class Person {
        constructor(name, age) {
            this.name = name;
            this.age = age;
        }
        subName: 'jane'//只能定义方法
    }
    //Uncaught SyntaxError: Unexpected identifier
    //如果一定要添加可在原型链上添加或通过 getter 添加
    

    3.7.4 静态方法

    新增在 class 上而非新增在 prototype 上的方法,称为静态方法。

    3.7.4.1 定义静态方法

    class Person {
        static sum(a, b){
            console.log(a+b)
        }
    }
    

    3.7.4.2 静态方法调用

    静态方法需直接通过该类来调用,无需实例化,实例化调用会报错

    class Person {
        static sum(a, b){
            console.log(a+b)
        }
    }
    Person.sum(1, 2);//3
    let person = new Person();
    person.sum(1, 2);//报错:Uncaught TypeError: person.sum is not a function
    

    3.7.5 类继承

    3.7.5.1 类继承的实现

    假设现在有两个类:Person 和 Student

    class Person {
        constructor(name, age) {
            this.name = name;
            this.age = age;
        }
        getAge () {
            return this.age + 10;
        }
        outputInfo () {
            console.log('name:', this.name, 'currentAge: ', this.getAge())
        }
    }
    
    class Student {
        constructor(name, age) {
            this.name = name;
            this.age = age;
        }
        getAge () {
            return this.age + 10;
        }
        outputInfo () {
            console.log('name:', this.name, 'currentAge: ', this.getAge())
        }
        startStudy () {
            alert(`我是${this.name}, 我要学习啦!`)
        }
    }
    let student = new Student('jane', 12)
    student.startStudy()
    

    如上,两个类是独立的,但其实 Student 是属于 Person 的,Person 中的方法 Student 中都有,且可以自己进行拓展,所以我们可以使用 “extends” 让 Student 来“继承” Person:

    class Person {
        constructor(name, age) {
            this.name = name;
            this.age = age;
        }
        getAge () {
            return this.age + 10;
        }
        outputInfo () {
            console.log('name:', this.name, 'currentAge: ', this.getAge())
        }
    }
    
    class Student extends Person {
        startStudy () {
            alert(`我是${this.name}, 我要学习啦!`)
        }
    }
    let student = new Student('jane', 12)
    student.startStudy()
    

    如上,便实现了 Student 对 Person 的继承,如果在 Student.prototype 中没有找到某个方法,就会从 Person.prototype 中继续寻找。

    3.7.5.2 super 的作用

    1. 重写构造函数 —— 继承父类参数

    当子类有构造函数时,必须调用 super 方法来继承父类的参数。

    class Person {
        constructor(name, age) {
            this.name = name;
            this.age = age;
        }
        getAge () {
            return this.age + 10;
        }
        outputInfo () {
            console.log('name:', this.name, 'currentAge: ', this.getAge())
        }
    }
    
    class Student extends Person {
        constructor(name, age) {
            super(name, age); //继承父类参数,必须写在构造函数最前面
            this.studyStatus = false;   //必须写在 super() 之后,否则报错
        }
    
        startStudy () {
            super.outputInfo()
            alert(`我是${this.name}, 我要学习啦!`)
        }
    }
    let student = new Student('jane', 12)
    student.startStudy()
    

    2. 调用父类的方法 —— super.fun()

    class Person {
        constructor(name, age) {
            this.name = name;
            this.age = age;
        }
        getAge () {
            return this.age + 10;
        }
        outputInfo () {
            console.log('name:', this.name, 'currentAge: ', this.getAge())
        }
    }
    
    class Student extends Person {
        constructor(name, age) {
            super(name, age); //继承父类参数,必须写在构造函数最前面
            this.studyStatus = false;   //必须写在 super() 之后,否则报错
        }
    
        startStudy () {
            super.outputInfo()  //会执行父类的这个方法
            alert(`我是${this.name}, 我要学习啦!`)
        }
    }
    let student = new Student('jane', 12)
    student.startStudy()
    

    箭头函数没有自己的 super,它们的 super 即是就近的上下文的 super,同它的 this 一样。

    3.7.5.3 重写父类方法

    class Person {
        constructor(name, age) {
            this.name = name;
            this.age = age;
        }
        getAge () {
            return this.age + 10;
        }
        outputInfo () {
            console.log('name:', this.name, 'currentAge: ', this.getAge())
        }
    }
    
    class Student extends Person {
        constructor(name, age) {
            super(name, age); //继承父类参数,必须写在构造函数最前面
            this.studyStatus = false;   //必须写在 super() 之后,否则报错
        }
        outputInfo () {
            alert(`name: ${this.name}, currentAge:  ${this.getAge()}`)
        }
    }
    let student = new Student('jane', 12)
    student.outputInfo()
    

    如上,子类中有与父类相同的方法,会直接覆盖父类的方法,因为执行时,会现在 Student.prototype 中寻找这个方法,没有找到时,才会从 Person.prototype 中继续寻找。

    参考:现代 JavaScript 教程

    相关文章

      网友评论

        本文标题:JavaScript 知识梳理

        本文链接:https://www.haomeiwen.com/subject/hofabctx.html