<笔记> JavaScript 引用类型

200 阅读27分钟

什么是引用类型 ?

  • 在 JavaScript 中 , 只有两种数据类型 : 基本数据类型和复杂数据类型 , 其中基本数据类型有五种 , 复杂数据类型只有一种 , 就是 object , 复杂数据类型又叫做引用数据类型
  • object 是一系列无序的键值对集合 , 是一种数据结构

如果说 JavaScript 中只有一种引用类型 object, 那么 Array、Function、Date、RegExp、Object 这些是什么呢 ?

  • 在 JavaScript 中有一些特殊的对象 , 这些特殊的对象生来就是为了干某件事的 , 这种特殊的对象叫做原生内置对象
  • Array、Function 等等这些都属于原生内置对象 , 是 object 的子类型 , 它们在一开始就拥有了许多属性和方法 , 所以数组 ( Array ) 是对象 ( object )、函数 ( Function ) 是对象 ( object )、对象 ( Object ) 自然也是对象 ( object )

Object 类型

object 和 Object 是什么关系 ? 有什么区别吗 ?

  • object 是一种表示无序键值对集合的数据结构
  • Object 是 object 的子类型 , 属于 JavaScript 原生内置对象中的一种 , 用来区别Function、Array 等其他原生内置对象

我们怎么在 JavaScript 中创建一个 Object 类型的对象呢 ?

  • 在 JavaScript 中创建对象我们可以使用 new 操作符后面跟一个构造函数来创建
    • 构造函数是一个专门用于创建对象的函数 , 所以对象都是由构造函数创建的
    • 构造函数是一个函数 , 所以构造函数也是对象
    • 构造函数和对象之间的关系将在原型链中得到解答
    var obj = new Object();         // 构造函数方法创建对象
    obj.name = "oswald";
    obj.age = 23;
  • 我们还可以使用对象字面量方法创建一个对象
    var obj = {                     // 对象字面量方法创建对象
        name : "oswald",
        age : 23
    };

对象字面量方法和构造函数方法创建对象有什么区别 ?

  • 对象字面量方法是对象定义的一种简写形式 , 目的是为了简化创建包含大量属性的对象的过程
  • 使用对象字面量方法创建对象的时候 , 并不会调用 Object 构造函数
  • 当我们需要向函数传递大量可选参数的时候 , 对象字面量是首选的方式

怎么访问对象中的属性和方法 ?

  • 点表示法 : 访问固定的属性
    var obj = {
        name : "oswald"
    };
    alert(obj.name);        // "oswald"
  • 方括号表示法 : 可以访问一些属性名中有语法错误的属性 , 也可以使用变量访问
    var pro = "name",
        obj = {
        name : "oswald"
    }
    alert(obj[pro]);        // "oswald"
    alert(obj.pro);         // undefined

Array 类型

我们怎么创建一个 Array 类型的数组对象呢 ?

  • 使用构造函数创建
    var arr = new Array(),                      // 创建一个空数组
        arr1 = new Array(3),                    // [,,] <-> [empty × 3]
        arr2 = new Array("red","white"),        // 创建一个包含两个字符串的数组
        arr3 = new Array;                       // 创建空数组时的简写
  • 数组字面量表示法创建
    var arr = [],                               // 创建一个空数组
        arr1 = ["red","white"];                 // 创建一个包含两个字符串的数组
  • 和对象一样 , 在使用数组字面量表示法时 , 不会调用 Array 构造函数

怎么访问数组中的值 ?

  • 方括号访问下标 , 如果访问的下标在数组中不存在或者值未初始化 , 返回 undefined
  • 数组第一项的下标是 0

数组中值的类型有没有限制 ?

  • 没有限制 , 数组中可以存储所有类型的值

怎么确定一个数组中有多少个值 ?

  • 一般我们使用数组的 length 属性来判断数组的长度

数组中的 length 属性表示数组中可用属性的数量吗 ?

  • 不一定 , 数组的长度值等于最后一项索引加1
    var arr = [];
    arr[99] = "red";                            // 实际初始化的属性只有一个
    alert(arr.length);                          // 数组长度值为 100

数组中的 length 属性的值是固定不变的吗 ?

  • 不是 , 数组的 length 属性的值是根据数组的长度动态改变的 , 当添加或者删除了数组中的值的时候 , 数组的 length 也会相应的进行改变
  • 我们也可以手动的调整数组的长度
    var arr = ["red","white","black"];          // arr.length = 3
    arr.length = 2;                             // 把数组的长度设置为 2
    alert(arr[2]);                              // undefined
    arr.length = 10;                            // 把数组的长度设置为 10
    alert(arr.length);                          // 10

动态改变的 length 属性有什么作用 ?

  • 我们可以利用这种特性在数组的最后动态添加值
    var arr = ["red"];                          // arr.length = 1
    arr[length] = "white";                      // arr[1] = "white"
    alert(arr);                                 // ["red","white"]
    arr[length] = "black";                      // arr[2] = "black"
    alert(arr);                                 // ["red","white","black"]

既然数组也是对象 , 那么我们怎么判断一个变量储存的是数组还是对象呢 ?

  • 当只有一个全局作用域的时候 , 我们可以使用 instanceof 操作符来判断一个变量是不是数组 , 但是如果网页中包含多个框架 , 也就存在两个以上不同的全局作用域 , 这个时候 instanceof 操作符就不准确了
  • ES5 中新增了 Array.isArray() 方法 , 这个方法的目的是最终确定某个值到底是不是数组
    var arr = [];
    alert(Array.isArray(arr));                  // true

怎样才能将数组中的所有项转换为字符串 ?

  • 我们可以使用数组对象的 join( ) 方法 和继承的 toString( )、toLocalString( ) 和 valueOf( ) 方法来实现
  • 上述的方法默认都是用逗号将数组各项分割开
    var arr = ["red","white","black"];
    alert(arr.valueOf());                       // red,white,black

假如我想让数组以 "|" 为分隔符 , 应该怎么操作 ?

  • join( ) 方法可以接收一个参数作为数组各项之间的分割符
    var arr = ["red","white","black"];
    alert(arr.join("|"));                       // red|white|black

如果我想让数组和其他数据结构中的栈一样后进先出应该使用什么方法 ?

  • JavaScript 中为数组提供了 pop( ) 和 push( ) 方法 , 用来实现数据的后进先出
  • pop( ) 方法可以删除并返回数组的最后一项 , 减少数组的 length 值
  • push( ) 方法可以接收任意数量的参数 , 然后把它们逐个添加到数组的末尾 , 最后返回添加修改后数组的长度
    var arr = [],
        count = arr.push("red","white");        // 推入数据
    alert(count);                               // 2 ( 返回修改后数组的长度 )
    count = arr.push("black");                  // 推入数据
    alert(count);                               // 3
    alert(arr);                                 // ["red","white","black"]
    var item = arr.pop();                       // 删除并返回数组最后一项
    alert(item);                                // "black"
    alert(arr);                                 // ["red","white"]
    alert(arr.length);                          // 2

那如果我想让数组实现队列方法的先进先出呢 ?

  • JavaScript 中为数组提供了 shift( ) 方法 , 可以和 push( ) 方法结合实现数据的先进先出
  • shift( ) 方法可以删除并返回数组的第一项 , 减少数组的 length 值
    var arr = [],
        count = arr.push("red","white");        // 推入数据
    alert(arr);                                 // ["red","white"]
    var item = arr.shift();                     // 删除并返回数组第一项
    alert(item);                                // "red"
    alert(arr);                                 // ["white"]

有没有方法可以让数组实现队列方法的逆转 , 变成在数组的前端添加数据 , 从数组的末尾删除数据 ?

  • 我们可以使用数组的 unshift( ) 方法配合 pop( ) 方法实现
  • unshift( ) 和 push( ) 方法使用类似 , 只不过 unshift( ) 方法是在数组的开头添加数据
    var arr = ["red"],                          // 初始化数组
        count = arr.unshift("white");           // 推入数据
    alert(count);                               // 2 ( 返回修改后的数组长度 )
    alert(arr);                                 // ["white","red"]
    var item = arr.pop();                       // 删除并返回最后一项
    alert(item);                                // "red"
    alert(arr);                                 // ["white"]

数组有哪些排序的方法 ?

  • JavaScript 中的数组对象拥有 reverse( ) 和 sort( ) 两种内置方法来处理排序问题
  • reverse( ) 方法可以用来翻转数组各项的顺序
  • sort( ) 方法默认是按照升序排列数组各项 , 也可以接收一个比较函数作为参数
    • sort( ) 方式默认是先将数组各项转换为字符串 , 然后比较字符串 , 这样就会出现字符串"10"在字符串"5"前面的情况 , 通常都会用比较函数来代替默认的比较方式
    • 比较函数接收两个参数 , 用返回值来决定两个参数的位置关系 , 返回正数的话第二个参数在第一个参数前面 , 返回零位置不变 , 返回负数第一个参数在第二个参数前面
  • sort( ) 和 reverse( ) 方法都会返回经过排序后的数组 , 不会修改原数组
    var arr = [2,1,3,4,5];
    alert(arr.reverse());                       // [5,4,3,1,2]
    var sortArr = arr.sort(function(a, b){
        return a - b;                           // 通常用于对数值类型进行排序
    })
    alert(sortArr);                             // [1,2,3,4,5]

如果我想要合并两个数组 , 应该怎么做呢 ?

  • 我们可以使用 concat( ) 方法来实现合并两个数组
  • 如果不给 concat( ) 方法传递任何参数 , concat( ) 方法会复制并返回当前数组
  • 如果给 concat( ) 方法传递一个或多个数组 , concat( ) 方法会将这些数组的每一项添加到结果数组中
  • 如果给 concat( ) 方法传递的不是数组 , 这些值就会被简单地添加到结果数组的末尾
  • concat( ) 方法不会改变原数组
    var arr = ["red"],
        arr1 = ["white"],
        arr2 = ["black"];                       // 声明三个只有一项的数组
    var concatArr = arr.concat(arr1,arr2);      // 传入数组 , 合并三个数组
    alert(concatArr);                           // ["red","white","black"]
    alert(arr);                                 // ["red"] 不会改变原数组
    concatArr = concatArr.concat("green");      // 传入值 , 添加到合并后数组的结尾
    alert(concatArr);                           // ["red","white","black","green"]

怎么将数组中的某一段截取出来 ?

  • 我们可以使用 slice( ) 方法来实现
  • slice( ) 方法最多可以接收 2 个参数 , 第一个参数是想要截取的开始位置下标 , 第二个参数是结束位置下标
  • 如果只给 slice( ) 方法一个参数 , slice( ) 方法会截取从开始位置到数组结尾的所有项
  • 如果不给 slice( ) 方法传递任何参数 , 默认会从数组的最开始截取到末尾 , 也就是复制了一遍原数组
  • slice( ) 方法不会改变原数组
    var arr = [1,2,3,4,5];
    alert(arr.slice(1,2));                      // [2] 截取从下标 1 (包括下标1)到 下标 2 (不包括下标2)的数组项
    alert(arr.slice(1));                        // [2,3,4,5]
    alert(arr);                                 // [1,2,3,4,5]
  • slice( ) 方法可以传递负数的参数 , 如果有参数是负数 , 那么就会用数组长度加上这个负数来确定相应的位置 , 如果结束位置小于开始位置 , 返回一个空数组
    var arr = [1,2,3,4,5];
    alert(arr.slice(-2,-1));                    // [5]
    alert(arr.slice(3,4));                      // [5]
    alert(arr.slice(-1,1));                     // [](空数组)

如果我想要删除数组中间的某几项应该怎么办 ?

  • 好办 , 我们可以使用 splice( ) 方法 , 指定两个参数 , 第一个是开始位置的下标 (能取到) , 第二个是要删除项目的数量 , 最后 splice( ) 方法会返回一个数组 , 这个数组包含被删除的项 , 如果没有删除任何项 , 就会返回一个空数组

数组也有插入和替换的功能吗 ?

  • 当然有 , 我们还是使用 splice( ) 方法就可以做到插入和替换这两个功能
  • 首先 , 插入功能 , splice( ) 方法可以接收三个参数 , 前两个参数作用不变 , 第三个参数可以是一个或多个值 , 表示要插入的项
  • 替换功能和插入功能类似 , 只不过插入功能不用删除项 , 替换就是删除几个项 , 再插入几个项 , 这样就完成了替换数组项的目的
  • 替换数组项的时候 , 删除的项数不必和插入的项数相等
    var arr = ["red","white","black"];
    alert(arr.splice(1,1));                     // ["white"] 删除并返回被删除项组成数组
    alert(arr);                                 // ["red","black"]
    alert(arr.splice(1,0,"green"));             // [] 没有删除任何项
    alert(arr);                                 // ["red","green","black"]
    alert(arr.splice(0,1,"blue"));              // ["red"]
    alert(arr);                                 // ["blue","green","black"]

怎么在数组中查找某一特定的项 ?

  • 数组对象内置了 indexOf( ) 和 lastIndexOf( ) 两个方法来查找数组中的项
  • 这两个方法都可以接受两个参数 , 第一个参数是要查找的项 , 第二个参数表示查找起点位置的索引(可选)
  • indexOf( ) 方法是从数组的开头开始向后查找 , lastIndexOf( ) 方法是从数组的结尾开始向前查找
  • 这两个方法都返回要查找的项在数组中的位置 , 如果没有找到 , 返回 -1
  • 在比较第一个参数的时候 , 使用的是全等操作符
    var arr = [1,2,3,4,5,4];
    alert(arr.indexOf(4));                      // 3 从前往后查找
    alert(arr.lastIndexOf(4));                  // 5 从后往前查找

    var person = {name: "oswald"};
    var people = [{name: "oswald"}];
    var morePeople = [person]
    alert(people.indexOf(person));              // -1 引用地址不同
    alert(people[0] === person);                // false 等同于上面
    alert(morePeople.indexOf(person));          // 0  引用地址相同
    alert(morePeople[0] === person);            // true 等同于上面

如果我想要知道数组中的每一项是不是都符合某个条件 , 应该怎么做 ?

  • 数组对象的 every( ) 方法正好可以实现这个目标
  • every( ) 方法需要传入一个函数 , 如果数组中的每一项在运行给定函数之后 , 都返回了 true , 则返回 true , 只要有一项不符合就返回 false , 可以和 && 操作符相互对照进行理解
    var arr = [1,2,3,4,5],
        result = arr.every(function(item,index){
            return item > 0;
        })
    alert(result);                              // true

如果我想要知道数组中是不是有满足某个条件的项而不是必须全部都满足呢 ?

  • 数组对象的 some( ) 方法可以实现
  • 和 every( ) 方法类似 , some( ) 方法也需要传入一个函数 , 如果数组中至少有一项在运行了给定函数之后返回了 true , 则返回 true , 如果全部都不满足 , 返回 false , 可以和 || 操作符相互对照进行理解
    var arr = [1,2,3,4,5],
        result = arr.some(function(item,index){
            return item === 1;
        })
    alert(result);                              // true
    alert(arr);                                 // [1,2,3,4,5]

如果我想要将一个数组中满足某个条件的项全部取出来呢 ?

  • 我们可以用数组对象的 filter( ) 方法来筛选数组中的项
  • filter( ) 方法需要传入一个函数 , 当数组中的项在运行给定函数后返回了 true , 将这个项添加到一个新的数组中 , 最后返回这个由返回 true 的项组成的数组
    var arr = [1,2,3,4,5],
        result = arr.filter(function(item,index){
            return item > 2;
        })
    alert(result);                              // [3,4,5]
    alert(arr);                                 // [1,2,3,4,5]

如果我们想要在不改变原数组的情况下对数组的项进行运算 , 得到运算后的数组 , 应该怎么做 ?

  • map( ) 方法可以非常简单的实现这个目的
  • map( ) 方法可以接收一个运算函数 , 然后让原数组中的每一项运行这个函数 , 然后返回各项运算后的结果组成的新数组
    var arr = [1,2,3,4,5],
        result = arr.map(function(item,index){
            return item * 2;
        })
    alert(result);                              // [2,4,6,8,10]
    alert(arr);                                 // [1,2,3,4,5]

除了使用 for 循环 , 我们还可以使用什么方法进行类似的数组迭代 ?

  • 数组对象内置了 forEach( ) 方法 , 本质上和 for 循环进行数组迭代没有区别
  • forEach( ) 方法需要传入一个函数 , 然后数组中的每一项都运行给定的函数 , 这个方法没有返回值
    var arr = [1,2,3,4,5];
    for(index in arr){
        arr[index]  = arr[index] + 1;
    }
    alert(arr);                                 // [2,3,4,5,6]

如果想要在运行数组迭代方法的时候改变函数的作用域 , 也就是改变 this 指针 , 应该怎么操作 ?

  • every( )、some( )、filter( )、map( ) 和 forEach( ) 这五个数组迭代方法都可以接收两个参数 , 第一个参数是需要运行的函数 , 第二个参数则是该函数的作用域对象 , 只要改变了函数的作用域对象 , 也就改变了 this 的指针
    var page = {
        init: function () {
            this.arr = [1];
            
            this.arr.filter(function (item, index) {
                console.log(this);              // Window 对象
                return;
            })

            this.arr.filter(function (item,index) {
                console.log(this);              // page 对象
                return;
            },this)
        }
    }
    page.init()

如果我想知道一个数值型数组各项的和应该怎么做 ?

  • ECMAScript 5 中新增了两个归并数组的方法 , 分别是 reduce( ) 和 reduceRight( ) 方法 , 这两个方法都会迭代数组的所有项 , 然后构建一个最终返回的值
  • reduce( ) 方法是从第一项开始遍历 , 一直到最后一项
  • reduceRight( ) 方法从最后一项开始遍历 , 一直到第一项
  • 这两个方法都可以接收两个参数 , 一个是在每一项上调用的函数和一个作为归并基础的初始值 , 初始值这个参数是可以选的
    /* 只有一个参数的时候 */
    var arr = [1,2,3,4,5],
        sum = arr.reduce(function(prev,cur,index){
            return prev + cur;
        })
    alert(sum);                                 // 15
    /* 传递了归并基础值的时候 */
    var arr = [1,2,3,4,5],
    sum = arr.reduce(function(prev,cur,index){
        return prev + cur;
    },10)
    alert(sum);                                 // 25

Date 类型

Date 类型的数据中保存着哪些东西 ?

  • 在 JavaScript 中 , Date 类型保存着自 1970年1月1日零时至现在为止经过的毫秒数
  • Date 类型保存的日期能够精确到1970年1月1日之前或者之后的一亿年

我们怎么使用 Date 构造函数获取当前日期和时间 ?

  • 在调用 Date 构造函数但是不传递参数的情况下 , 新创建的 Date 类型对象会自动获取当前日期和时间

如果我想要根据指定日期和时间创建 Date 对象 , 应该怎么做 ?

  • 如果我们想要根据指定的日期和时间创建 Date 对象 , 必须传入表示该日期的毫秒数
  • 不过 ECMAScript 提供了两个方法来简化这一过程 : Date.parse( ) 、Date.UTC( )
  • Date.parse( ) 方法接收一个表示日期的字符串参数 , 然后根据这个字符串返回相应日期的毫秒数
    var time = new Date(Date.parse("2018-6-20"));
    // Date.parse( ) 方法会根据当前时区创建日期毫秒数
  • 如果传入 Date.parse( ) 中的字符串参数不能表示日期 , 则返回 NaN
  • Date.UTC( ) 方法和 Date.parse( ) 方法类似 , 但是接收的参数不同 , 它的参数分别是年、月、日、时、分、秒以及毫秒 , 其中年和月是必须的 , 月从0开始( 0表示1月 , 1表示2月 ) , 日缺省填充1 , 其他参数缺省默认填充0
    var time = new Date(Date.UTC(2018,5));
    // Dtae.UTC( ) 方法会根据 GMT 时区创建日期毫秒数
    // GMT时区 2018-6-1 00:00:00 => 北京时间 2018-6-1 08:00:00
  • Date 构造函数会默认调用 Date.parse( ) 和 Date.UTC ( ) 这两个方法 , 但是 Date 构造函数都是根据本地时区来创建日期和时间对象的 , 而 Date.UTC( ) 这个方法是根据 GMT 时区 ( 0时区 , 北京时间是东八时区 ) 来创建日期和时间对象的
    var time = new Date(2018,5);                   // Fri Jun 01 2018 00:00:00 GMT+0800 (中国标准时间)
    var timeGMT = new Date(Date.UTC(2018,5));      // Fri Jun 01 2018 08:00:00 GMT+0800 (中国标准时间)
    /* 使用构造函数的时候会创建当前时区的时间对象 , 和 GMT 时区时间不同 */

如果我想知道当前时间的毫秒数怎么办 ?

  • ES5 中 Date 类型的对象还添加了一下 Date.now( ) 方法 , 可以快速知道调用这个方法时日期和时间的毫秒数
  • Data 类型的对象的 valueOf( ) 方法不会将日期字符串化 , 它返回的是日期的毫秒表示 , 所以我们只要获取当前时间对象之后调用它的 valueOf( ) 方法即可
  • 因为 + 操作符可以调用对象的 valueOf( ) 方法 , 所以我们只需要使用 + 操作符获取 Date 对象的时间戳也可以达到同样的目的

Function 类型

什么是函数 ?

  • 函数就是对象 , 一个可以被调用的对象
  • 所有函数 , 包括构造函数都是 Function 类型的实例

如果说函数是对象 , 那么函数可以保存在变量、对象或者是数组中吗 ?

  • JavaScript 中所有引用类型都是对象 , 函数也不例外 , 所以函数可以拥有方法 , 并且 JS 中保存对象的变量保存的都是对象的引用 , 所以函数可以被当做参数传递给其他函数 , 函数也可以再返回一个函数

函数名和函数是什么关系 ?

  • 函数名其实就是一个变量名 , 保存着指向函数对象的指针 , 不会和某个函数绑定
  • 我们使用不带圆括号的函数名是访问函数指针 , 而不是调用函数

声明函数的方式有哪些 ?

  • 我们可以使用函数表达式和函数声明语法两种方式来声明函数
    var func = function() {         // 函数表达式
        //do something...
    }
    function func (){               // 函数声明语法
        // do something...
    }

函数声明和函数表达式有什么区别 ?

  • 在功能上没有区别 , 但是在加载顺序上有些区别
  • 解析器会在代码运行前就读取函数声明 , 这个时候任何代码都可以调用函数
  • 函数表达式声明的函数 , 必须等到解析器执行到它所在的代码行时才可以被调用 , 此时声明的变量名已经存在但是还未赋值 , 所以保存着 undefined
    func();                             // 函数声明提升 , 所有代码都可以调用这个函数
    function func() {
        console.log("我被执行了");
    }
    // 正确执行 , 不报错
    -------------------------------
    sum();                              // 普通声明提升 , 此时sum 变量存在 , 但未赋值 , 保存着 undefined
    var sum = function(){               // 将函数对象的指针赋值给 sum , 此时 sum 才可以被当做函数调用
        console.log("sum被执行了");
    };
    // 报错 sum is not a function

函数是怎么被调用的 ?

  • 函数拥有四种调用方法 : 方法调用、函数调用、构造器调用、apply 和 call 调用
  • 当一个函数保存在对象的一个属性中 , 我们称这个函数为这个对象的方法 , 当这个方法被调用的时候 , this 被指向这个对象 , 如果调用表达式包含了一个提取属性的动作 , 那么它就是被当做一个方法来调用
    var page = {
        init : function(){
            console.log("init 方法被调用了");
            console.log(this);          // page 对象
        }
    }
    page.init();                        // 方法调用
  • 当一个函数不是一个对象的属性 , 那么它就会被当做一个函数来调用 , 调用的时候 this 指向全局对象
    var page = {};
    page.init = function(){
        console.log("init 方法被调用了");
        var helper = function() {
            console.log("helper 函数被调用了");
            console.log(this);
        };
        helper();   // this 指向了 Window 对象
        /* 这里其实是一个语言设计上的错误 */
    }
  • 如果在一个函数的前面加上 new 操作符再调用这个函数 , 那么背地里将会创建一个连接到这个函数 prototype 成员的新对象 , this 将会被绑定到这个新对象中 , 如果在构造器模式中 , return 了一个非对象的值 , 则会自动修改为这个新对象 , 如果返回了一个对象 , 则会丢弃这个新建的对象
    function people(string){
        this.name = string;
        return 0;                       // 构造器调用模式中无效 , 自动修改为this对象
    }
    /* 构造器调用函数模式 */
    var person = new people("oswald");
    /* 
    此时
    person.__proto__ === people.prototype 
    people.prototype.__proto__ === Object.prototype
    */
    console.log(person.name);           // oswald
    ----------------------------------------------------------
    /* 模拟构造器调用函数模式 */
    var person = {};
    function people(string) {
        this.name = string;
    }
    people.call(person,"oswald");
    person.__proto__ = people.prototype;
    console.log(person.name);           // oswald
  • 函数自带了一些特定用途的方法和属性 , 其中 call( ) 方法和 apply( ) 方法可以让我们在特定的作用域中调用函数 , 这两个方法都接收两个参数 , 第一个参数是要绑定的作用域 , 第二个是函数参数 , call( ) 方法可以接收多个函数参数 , 函数参数之间用逗号隔开 , apply( ) 方法则是接收一个参数数组 , 这是这两个方法之间唯一的区别
    var sum = function(num1,num2) {
        return num1 + num2;
    }
    var obj = {};
    obj.data = [1,2];
    console.log(sum.apply(obj,obj.data));       // 3
    /* 在 obj 作用域中调用 sum 函数 , 并将 obj.data 这个数组中的数 当做参数传递给 sum 函数 */

当一个函数被调用的时候发生了什么 ?

  • 当一个函数被调用的时候 , 会停止当前函数的执行 , 然后把控制权和参数传给新的函数 , 除了函数定义的形参 , 每个函数还接收两个参数 , 一个是 this 指针, 一个是 arguments 对象 , 函数调用模式的不同 this 的指向也不同 , 而 arguments 对象则是一个类数组的存在 , 包含了传递进来的所有参数 , 如果形参个数和 arguments 的个数不同也不会报错 , 如果实际参数多了 , 多余的部分会被忽略( 但我们仍然可以通过下标访问 ), 如果实际参数少了 , 缺失的值会被替换为 undefined
  • arguments 对象只有 length 属性 , 没有任何数组的方法
  • 我们可以通过下标访问 arguments 对象中的参数

函数中的 this 到底是什么 ? 是一个固定的值吗 ?

  • 函数中的 this , 其实就是当函数被调用时 , 调用者所在的作用域 , 所以函数中的 this 不是一个固定的值 , 函数中的 this 是在被调用时确定的
    var page = {};
    page.init = function(){
        console.log(this);
        return this;
    }
    page.init();                // 被当做 page 对象的一个方法调用 , 作用域是 page 对象
    var init = page.init;       // init 此时保存着指向 page.init 函数的指针 , 函数没有运行
    init();                     // 被当做函数调用 , 作用域是全局对象 Window
    init = page.init();         // init 此时保存的是 page.init 方法运行后的结果 , 函数是被 page 对象调用的 , 所以作用域是 page 对象
  • 如果函数是作为 DOM 元素事件处理函数的时候 , 它的 this 指向了触发事件的 DOM 元素

函数中的 this 可以被改变吗 ?

  • 通过上面我们可以知道 , 在构造器调用模式中 , new 操作符会改变函数的 this 指针
  • 函数的 call( ) 和 apply( ) 方法可以指定函数的作用域 , 也就是 this 指针
  • 在 ES5 中函数新增了一个 bind( ) 方法 , 我们可以使用 bind( ) 方法给函数指定一个 this 值 , 无论谁调用了函数 , this 始终指向绑定的值 , 并且 bind( ) 方法在同一个函数上只能绑定一次
  • bind( ) 方法不会执行函数 , 而是会创建一个具有相同函数体和作用域的新函数 , 只是改变了新函数的 this 指针
    var page = {};
    page.init = function () {
        console.log(this);
    }.bind(page);               // 绑定函数的 this 值为 page

    page.init();                // page 对象

    var init = page.init;
    init();                     // page 对象

    init = page.init();         // page 对象

基本包装类型

啥是基本包装类型 ?

  • 我们都知道在 JS 中有五种基本数据类型 , 其中 null、undefined 这两种是不能进行任何操作的 , 但是我们发现 , 我们在操作字符串的时候经常会用到包括 indexOf( ) 在内的一些方法 , 字符串不是基本数据类型吗 , 怎么会有方法存在呢 , 其实 ECMAScript 提供了三个特殊的引用类型 : Boolean、Number 和 String
  • 每当我们读取一个基本类型值的时候 , 后台就会创建一个对应的基本包装类型的对象 , 所以我们才能够调用一些方法来操作这些数据
    var s1 = "some text";       // 创建一个字符串
    var s2 = s1.slice(2);       // 截取字符串
    /* 这是一个日常使用的截取字符串的方法 */
    -----------------------------------
    /* 其实上面的第二行代码在运行时的过程是这样的 */
    var s3 = new String(s1);    // 创建一个 String 类型的字符串实例
    var s2 = s3.slice(2);       // 在实例上调用指定的方法
    s3 = null;                  // 销毁这个对象实例
    -----------------------------------
    /* 也可以写成这样 */
    var s2 = new String(s1).slice(2);

那基本包装类型和其他的引用类型有什么区别 ?

  • 基本包装类型和其他引用类型最主要的区别就是对象的生存期
  • 使用 new 操作符生成的引用类型实例 , 在执行流离开作用域之前一直存在
  • 自动创建的基本包装类型实例 , 只存在于调用方法的一瞬间 , 执行完立即被销毁
    /* 自动创建的包装类型 */
    var s1 = "some text";
    s1.name = "name";       // 创建一个 String 类型实例 , 添加 name 属性 , 然后销毁
    console.log(s1.name);   // 创建另一个 String 类型实例 , 读取 name 属性 , 没找到 name 属性 , 返回 undefined , 然后销毁这个实例
    console.log(typeof s1);             // string
    ----------------------------------
    /* 手动创建的包装类型 */
    var s1 = new String("some text");
    s1.name = "name";
    console.log(s1.name);               // name
    console.log(typeof s1);             // Object

我们还有其他办法来创建基本包装类型对象的实例吗 ?

  • 除了使用 new + 指定包装类型的创建方式 , 我们还可以使用 new + Object 构造函数的创建方式
  • Object 构造函数会根据传入值的类型返回相应的基本包装类型的实例
    var s1 = new Object("some text");   // String 类型的实例对象
    var n1 = new Object("23");          // Number 类型的实例对象
    var b1 = new Object("true");        // Boolean 类型的实例对象

基本包装类型有哪些方法可以使用呢 ?

  • Number 类型
    • valueOf( ) 方法返回对象表示的基本类型的数值
    • toString( ) 方法返回数值的字符串形式 , 并且可以接收一个用来表示基数的参数
    • toFixed( ) 方法可以把数值转换为一个十进制形式的字符串 , 它接收一个参数可以控制小数点后的数字位数
    • toExponential( ) 方法可以把数值转换为一个指数形式的字符串 , 可以接收一个参数用来控制小数点后的数字位数
    • toPrecision( ) 方法接收一个参数 , 可以控制数字的精度 , 并且会根据转换后的数值自动适应十进制形式或指数形式
  • String 类型
    • slice(start , end) : 截取字符串 , 参数为负数时转换为字符串长度加上这个负数
    • substr(start , length) : 截取字符串 , 参数为负数时转换为 0
    • concat( ) : 拼接字符串 , 可以接收任意个数的字符串
    • indexOf( ) : 在字符串中查找子字符串 , 从前往后 , 找到返回第一个字符所在下标 , 未找到返回 -1
    • lastIndexOf( ) : 在字符串中查找子字符串 , 从后往前
    • trim( ) : 删除字符串前面和后面的空格 , 返回删除后的字符串 , 不修改原字符串
    • toLowerCase( ) : 全部转换为小写
    • toUpperCase( ) : 全部转换为大写
    • charAt( ) : 在字符串中根据指定下标查找字符 , 返回字符 , 未找到返回空字符串
    • charCodeAt( ) : 在字符串中根据指定下标查找字符 , 返回字符的字符码位 , 未找到返回空字符串
    • localeCompare( ) : 字符串排序
    • match( ) : 接收一个正则对象参数 , 查找字符串 , 可以返回全部符合要求的字符串
    • search( ) : 接收一个正则对象参数 , 只能查找第一个符合要求的字符串
    • replace( searchValue , replaceValue ) : searchValue 可以是一个字符串或者一个正则对象 , 如果是字符串只会查找替换第一个符合要求的字符 , 如果是正则对象并带有 g 标识 , 会查找和替换所有符合要求的字符 ; replaceValue 可以是一个字符串或者一个函数 , 表示要替换的内容
    • split( separator , limit ) : 分割字符串并添加到一个新的数组里 , separator 参数可以是一个字符串或者是一个正则表达式 , 用来表示分隔符 , limit 参数可以限制被分割的片段数量
    • fromCharCode( ) : 和 charCodeAt( ) 相反 , 根据字符编码返回字符串

Global 对象

什么是 Global 对象 ?

  • Global 对象就是我们之前所说的全局作用域 , 准确的说 , 所有在全局作用域中定义的对象和函数 , 都是 Global 对象的属性 , 像 parseInt 函数、isNaN 函数等等都是 Global 对象的方法

那我可以在浏览器中访问 Global 对象吗 ?

  • 基本上所有浏览器都是将全局对象作为 Window 对象的一部分来实现的 , 所以我们可以通过访问 Window 对象来查看 Global 对象的属性和方法

Math 对象

什么是 Math 对象 ?

  • Math 对象保存了一系列的数学公式和信息 , 拥有进行科学计算的方法以及一些可能会用到的特殊值

Math 对象有哪些科学计算方法和特殊值 ?

  • Math.PI : 保存的是 π 的值 , 即 3.1415926...
  • min( ) 和 max( ) 方法 : 传入任意个数的参数比较参数的大小
  • ceil( num ) : 向上取整
  • floor( num ) : 向下取整
  • round( num ) : 四舍五入
  • abs( num ) : 返回 num 的绝对值
  • random( ) : 返回一个 0 - 1 之间的一个随机数