原生JavaScript知识点整理(更新中)

156 阅读29分钟

什么是 JavaScript?

  • JavaScript(简称 JS) 是一种具有函数优先的轻量级解释型即时编译型的编程语言
  • JavaScript 在 1995 年由网景公司的布兰登·艾奇发明
  • JavaScript 本身是作为开发 Web 页面的脚本语言而出名的,但它也被用到了很多非浏览器环境中,比如 Node.js
  • JavaScript 最新的版本由 ECMA 国际组织发布的,该版本正式名称为 ECMAScript 2015,但在社区里我们通常称它为 ECMAScript 6 或者 ES6

JavaScript 的组成

  • ECMAScript 描述了该语言的语法和基本对象
  • BOM 浏览器对象模型
  • DOM 文档对象模型

JavaScript 的特点

  • 是一种解释性脚本语言(代码不进行预编译)
  • 主要用来向 HTML 页面添加交互行为
  • 可以直接嵌入 HTML 页面,但写成单独的 JS 文件有利于结构和行为的分离
  • 跨平台特性,在绝大多数浏览器的支持下,可以在多种平台下运行
  • JavaScript 脚本有自身的基本数据类型,表达式和算术运算符及基本框架
  • JavaScript 提供了六种基本数据类型一种复杂数据类型来处理数据和文字。
  • 变量提供存放信息的地方,表达式则可以完成较为复杂的信息处理。

JavaScript 中的数据类型

  • JavaScript 中共有七种内置数据类型,包括基本类型引用类型

基本数据类型:

  • String(字符串)
  • Boolean(布尔值)
  • Number(数字)
  • Symbol(符号)
  • undefined(未定义)
  • null(空值)

复杂(引用)数据类型:

  • Function(函数)
  • Object(对象)
  • Array(数组)

数组

  • 是一个复杂数据类型
  • 是一个有序数据的集合
  • 是按照索引(下标)进行排列的
  • 索引从 0 ~ 正无穷

数组的创建方法

  1. 字面量创建
    • var arr = []
    • 可以在创建的时候 直接添加内容
  2. 内置构造函数创建(Array)
    • var arr = new Array()
    • 内置构造函数的方式可以 使用传递参数的形式 直接写入内容

创建数组的内置构造函数的三种使用方式

  1. 不传递参数
    • 得到的就是一个空数组
  2. 传递一个大于 0 的整数
    • 这个数字表示数组的长度
    • 里面的每一个数据都是空的 => [empty × 传递进来的数字]
  3. 传递多个参数(任意个)
    • 每一个参数就是数组里的每一个数据

操作数组的方法

  • 我们的数组不好进行修改
  • 需要一些方法来对数组进行操作

1. push() 向数组的末尾追加内容

  • 语法数组.push(你要添加的内容)
  • 返回值:改变以后的数组的长度
  • 直接改变原始数组

2. pop() 把数组的最后一个数组删除

  • 语法数组.pop()
  • 返回值:被删除的那一条数据
  • 直接改变原始数组

3. unshift() 向数组的最前面添加内容

  • 语法数组.unshift(你要添加的内容)
  • 返回值:改变以后的数组的长度
  • 直接改变原始数组

4. shift() 删除数组最前面的一个数据

  • 语法数组.shift()
  • 返回值:被删除的那一条数据
  • 直接改变原始数组

5. splice() 截取数组中的某些内容

  • 语法数组.splice(开始索引, 截取多少个)
  • 返回值:以一个数组的形式返回被截取出来的内容
    • 直接截取一个的时候,也是以数组的形式返回
    • 一个都不截取的时候,返回的是一个空数组
  • 直接改变原始数组

splice() 的第二个使用方式

  • 语法数组.splice(开始索引, 截取多少个,用什么内容替换)
    • 理论上可以传递无限个参数,从 第三个参数 开始全部是替换的新内容
    • 在替换的过程中,你从哪里开始截取,就把替换的内容填充到哪里
  • 返回值:以一个数组的形式,返回被截取出来的内容

6. reverse() 用来反转数组

  • 语法数组.reverse()
  • 返回值:反转过来的数组
  • 直接改变原始数组

7. sort() 用来对数组进行排序的

  • 语法数组.sort()
  • 返回值:排序好的数组
    • 如果你在使用 sort 方法的时候不传递参数
      • 按照一位一位来看待进行排列
    • 如果你在使用 sort 方法的时候传递参数
      • 那么会按照 从小到大 或者 从大到小 排序
  • 直接改变原始数组

sort() 传递参数的语法

  • 语法数组.sort(function (a, b) {})
  • 这个传递进去的函数接收两个形参
  • 这个函数需要一个返回值
    • return a-b 升序
    • return b-a 降序

8. concat() 对数组进行拼接

  • 把多个数组连接在一起变成数组
  • 语法数组.concat()
  • 返回值:是拼接好的数组
  • 不改变原始数组

9. join() 对数组进行拼接

  • 把数组里面的每一项连接在一起,变成一个字符串
  • 语法数组.join(你以什么内容进行连接)
  • 返回值:按照你给定的内容连接好的字符串
    • 你不传递参数的时候,默认以 , 连接
    • 你传递参数的时候,传递什么就用什么连接
  • 不改变原始数组

10. slice() 对数组进行截取

  • 语法数组.slice(开始索引, 结束索引) (包前不包后)
  • 返回值:以一个数组的形式返回被截取出来的字符串
  • 不改变原始数组

slice() 的特殊使用方式

  • 传递索引的时候,可以传递一个负整数
  • 当你传递一个负整数的时候
    • 这个负整数的位置就是 这个数组length 相加的结果

ES5 的数组常用方法

1. indexOf() 查询数组中是否有该数据

  • 语法数组.indexOf(你要查询的内容)
  • 返回值:你要查询的数据的索引位置
    • 如果没有这个数据,就返回 -1
  • indexOf(你要查询的内容, 设置一个开始的索引位置)
    • 就是限制了一个开始查询的位置
var arr = [1, 2, 3, 4, 5]

var res = arr.indexOf(6)
console.log(res) // -1

var res2 = arr.indexOf(2)
console.log(res) // 1

2. forEach() 用来遍历数组的

  • 取代了 for循环 的作用
  • 语法数组.forEach(function (item, index, arr) {})
    • item:数组里面的每一项
    • index:数组里面每一项的索引
    • arr:原始数组
  • 无返回值
var arr = [1, 9, 4, 6, 5, 8, 3, 2, 7]

var res = arr.forEach(function (item, index, arr) {
    console.log(item) // 随着循环 item 就是数组里面的每一项
    console.log(index) // 随着循环 index 就是数组里面的索引
    console.log(arr) // 每次循环的时候 arr 都是原始数组
})

console.log(res) // undefined

3. map() 用来映射数组的

  • 该方法与 forEach() 相同,区别就是多了一个返回值

  • 语法数组.map(function (item, index, arr) {})

    • item:数组里面的每一项
    • index:数组里面每一项的索引
    • arr:原始数组
  • 返回值:是一个数组

    • 原始数组的长度是多少,返回的数组长度就是多少
    • 返回的数组里面的数据有传递的函数的返回值决定
  • map 的执行

    • 先准备一个新数组 [1, 8, 7, 3, 5, 0, 9, 6]
    • 随着循环函数每次都在执行
    • 第一次这个函数调用的时候返回值是什么,就填充在数组的第 0
    • 最后把这个新数组返回给你
var arr = [1, 8, 7, 3, 5, 0, 9, 6]

var res = arr.map(function (item, index, arr) {
    console.log(item) // 随着循环 item 就是数组里面的每一项
    console.log(index) // 随着循环 index 就是数组里面的索引
    console.log(arr) // 每次循环的时候 arr 都是原始数组

    return item * 10
})

console.log(res) // [10, 80, 70, 30, 50, 0, 90, 60]

4. filter() 用来过滤数组的

  • 语法数组.filter(function (item, index, arr) {})

    • item:数组里面的每一项
    • index:数组里面每一项的索引
    • arr:原始数组
  • 返回值:是一个数组

    • 原始数组的长度是多少,返回的数组长度就是多少
    • 返回的数组里面的数据有传递的函数的返回值决定
  • filter 的执行

    • 先准备一个 空数组 [1, 8, 7, 3, 5, 0, 9, 6]
    • 随着循环,如果你的函数返回的是一个 true,那么就把这一项放在新数组里面
    • 如果你的函数返回的是一个 false,那么就什么也不做
    • 最后把这个新数组给你返回
var arr = [1, 8, 7, 3, 5, 0, 9, 6]

var res = arr.filter(function (item, index, arr) {
    console.log(item) // 随着循环 item 就是数组里面的每一项
    console.log(index) // 随着循环 index 就是数组里面的索引
    console.log(arr) // 每次循环的时候 arr 都是原始数组
    
    return item !== 3 && item !== 8
})

console.log(res) // [1, 7, 5, 0, 9, 6]

5. every() 判断数组里面是不是每一个都满足条件

  • 语法数组.every(function (item, index, arr) {})

    • item:数组里面的每一项
    • index:数组里面每一项的索引
    • arr:原始数组
  • 返回值:一个布尔值

    • 当你的数组中每一个内容都满足条件的时候,就会返回 true
    • 只要你的数组中有一个内容不满足条件,就会返回 false
  • every 的执行

    • 准备一个变量是 true
    • 随着循环的的执行,只要你函数内部返回的条件是一个 false
    • 那么他就把变量改成 false
    • 如果你函数返回的是一个 true,那么它就什么都不做
    • 给你返回结果

6. some() 判断数组里面是不是有一个满足条件

  • 语法数组.some(function (item, index, arr) {})

    • item:数组里面的每一项
    • index:数组里面每一项的索引
    • arr:原始数组
  • 返回值:一个布尔值

    • 数组中只要有一个满足条件的,那么就会返回 true
    • 如果数组中全都不满足条件,那么就会返回 false
  • some 的执行

    • 一开始定义一个变量时 false
    • 随着循环函数的执行,只要有一个函数里面返回一个 true
    • 那么就把变量变成 true
    • 如果函数返回的是 false,那么什么都不做
    • 最后把这个变量的结果给你

for in 循环

  • 也是循环的一种
  • 语法:for (var key in obj) {}
    • key 对象中的每一项(key)
    • obj 要循环的那个对象
  • 主要是用来循环对象
  • 但是也可以用来循环数组

冒泡排序

  • 就是对数组排序的一种方法
  • 循环一遍以后,得到一个结果
    • 最大的一定在数组的最后面
  • 优化:两个循环
    • 使用里面循环交换变量
    • 两个循环一个减一次
    • 里面循环减去外层循环变量
var arr = [1, 9, 4, 6, 5, 8, 3, 2, 7]

// 外层循环 - 1
// 每循环一次,最后一位永远是最大的数,不用继续循环
for (var j = 0; j < arr.length - 1; j++) {
    // 内层循环 - 1
    // 最后一个数字不用比,索引超出去了
    // 内层循环 - j
    // 循环完毕一次,就不用管最后一个数了
    for (var i = 0; i < arr.length - 1 - j; i++) {
        // 对这两个数字进行比较,谁大谁放在后面
        if (arr[i] > arr[i + 1]) {
            var temp = arr[i]
            arr[i] = arr[i + 1]
            arr[i + 1] = temp
        }
    }
}

console.log(arr)

选择排序

  • 就是对数组排序的一种方法
  • 先假设最小的那个数字的索引是 0
    • 遍历一下数组,只要谁比我小,那么就把最小的索引替换掉
  • 优化:外层循环
    • 假设外层循环变量
    • 里层循环,里层循环的开始就是外层循环变量 + 1
    • 假设的数组和外层循环变量的数据交换
var arr = [1, 9, 4, 6, 5, 8, 3, 2, 7]

for (var j = 0; j < arr.length - 1; j++) {
    var minIndex = j
    for (var i = j + 1; i < arr.length; i++) {
        // 判断某一个数字比我假设最小索引位置的数字还要小
        if (arr[i] < arr[minIndex]) {
            minIndex = i
        }
    }

    // 循环结束以后,就得到真正的最小数字的索引
    // 我这个索引位置的数字和我假设的 j 位置进行交换
    if (minIndex !== j) {
      var temp = arr[minIndex]
      arr[minIndex] = arr[j]
      arr[j] = temp
    }
}

console.log(arr)

字符串

  • 是一个基本数据类型
  • 被引号或者双引号包裹的内容
  • 也叫做包装数据类型
    • 当你使用它的时候,它会把自己转换成对象的形式
    • 当你使用完毕以后,它又会自己转换回基本数据类型

字符串的创建方式

  1. 字面量
    • var str = 'hello'
  2. 内置构造函数
    • var str = new String('hello')

字符串的排列是按照索引进行排列的,可以使用索引的方式获取字符串中的内容 str[0]

有一个 length 属性,表示这个字符串中的字符的数量,也就是这个字符串的长度

字符串的常用方法

  • 全都不改变原始字符串

1. CharAt() 用于返回指定位置的字符

  • 语法字符串.charAt(索引)
  • 返回值
    • 如果有这个索引位置
      • 那么就返回你传递的这个索引位置所对应的字符
    • 如果没有这个索引位置
      • 那么就会返回一个空的字符串

2. charCodeAt() 用于返回指定位置的字符的 Unicode 编码

  • 语法字符串.charCodeAt(索引)
  • 返回值
    • 如果有这个索引位置
      • 那么就返回你传递的这个索引位置所对应的字符的编码
    • 如果没有这个索引位置
      • 那么返回的是 NaN

3. substring() 用于提取字符串中介于两个指定下标之间的字符

  • 语法字符串.substring(开始索引, 结束索引)
  • 返回值:就是截取出来的字符串

4. substr() 可在字符串中抽取从 start 下标开始的指定数目的字符

  • 语法字符串.substr(开始索引, 多少个)
  • 返回值:就是截取出来的字符串

5. concat() 用于拼接字符串

  • 语法字符串.concat(要拼接的字符串)
  • 返回值:拼接好的字符串

6. split() 用于截取字符串

  • 语法字符串.split(以什么内容切割)
  • 返回值:是一个数组
    • 不传递参数的时候是整个内容
    • 你传递了内容,就是按照你传递的进行切割
    • 传递空字符串,就是按照一位一位的切割

7. slice() 用于截取字符串

  • 语法字符串.slice(开始索引, 结束索引)(包前不包后)
  • 返回值:截取出来的字符串

8. toUpperCase() 用于将字符串转大写

  • 语法字符串.toUpperCase()
  • 返回值:把能转大写的都给你转成大写

9. toLowerCase() 用于将字符串转小写

  • 语法字符串.toLowerCase()
  • 返回值:把能转小写的都给你转成小写

10. replace() 用于在字符串中用一些字符替换另一些字符

  • 或替换一个与正则表达式匹配的子串
  • 语法字符串.replace(你要把什么替换, 替换成什么)
  • 返回值:替换好的字符串
    • 一次只能替换一个

ES5 的字符串常用方法

1. indexOf() 按照字符来查找索引位置

  • 语法字符串.indexOf(要找的字符片段, 开始的索引位置)
  • 返回值
    • 如果有对应字符片段的内容
      • 返回的值就是开始位置的索引
    • 如果没有对应字符片段的内容
      • 返回的就是 -1

字符串和数组相同的几个方法

  1. slice() 截取
  2. concat() 拼接
  3. indexOf() 按照片段查询索引

indexOf()replace() 结合实例:

// 给一段文章中的全部指定词语进行过滤
var str = 'asdasdSMasdasdasdSMsdasdasdSMsadasd'

while (str.indexOf('SM') !== -1) {
    str = str.replace('SM', '**')
}

console.log(str)

什么是作用域

  • 就是一个变量的使用范围
  • 我定义一个变量可以在哪里进行使用

作用域分为两种

作用域和函数怎么调用没有关系,是在函数定义的时候就决定好了

  1. 全局作用域
    • 一个页面就是一个全局作用域
  2. 私有作用域
    • 只有函数能够生成一个私有作用域(重点)

变量的访问机制

变量的访问机制决定着一个变量是否可以访问

  • 当你访问一个变量的时候,会先在自己的作用域里面找
    • 如果有,直接拿过来使用,停止查找
    • 如果没有,就去上一级作用于里面找,如果有就拿过来使用,停止查找
    • 如果还没有,就再去上一级作用域里面查找,如果有就拿过来使用
    • 如果还没有,再去上一级查找,直到全局作用域都没有,就报错
  • 注意:只能去上一级找,不能去下一级查找

变量的赋值机制

  • 当你想给一个变量赋值的时候
    • 如果自己作用域里面有,那么就直接给自己作用域的变量赋值
    • 如果自己作用域里面没有,那么去看看上一级作用于里面有没有,如果有就赋值
    • 如果还没有,那么就再去上一级看看有没有
    • 直到全局作用域都没有这个变量的时候
    • 会先把这个变量定义为全局变量,再给它赋值(重点)
  • 注意:只能去上一级查找,不能去下一级查找

什么是递归函数?

在最后一个函数没有执行完毕之前,前面的函数一直都是等待的状态

在等待的状态就会占用内存,所以慎用递归函数

  • 是函数的一种特殊的使用手段
  • 一个函数当自己调用自己的时候,就是递归函数

var、let、const 的区别

  • var 关键字定义的变量会进行变量提升
  • var 声明的变量会被提升到作用域顶部
  • 我们需要了解函数也会被提升,并且优先于变量提升
  • 函数提升会把整个函数挪到作用域顶部,变量提升只会把声明挪到作用域的顶部
  • letconst 在全局作用域下声明变量,并不会被挂载到 window
  • letconst 必须先声明,后使用,不然会报错(暂时性死区)
  • const 声明的变量不能再次进行赋值

什么是变量提升?

  • 变量提升也被称为预解析
  • 虽然变量还没有被声明,但是我们却可以使用这个未被声明的变量,这种情况就叫做变量提升,并且提升的是声明

什么是闭包?

function fn() {
    var n = 10
    return function a() {
        console.log('我执行了,我是 a 函数')
        console.log(n)
    }
}
var res = fn()
  • 在外部函数(fn)里面返回了一个内部函数(a)
  • 在外部函数(fn)外面有一个变量(res)在接收着它返回的内部函数(a)
  • 内部函数(a)使用者外部函数(fn)里面的变量
  • 当上面三个条件满足的时候,我们就称它为闭包函数

闭包的特点

  • 延长了变量的生命周期
    • 一个不销毁的函数执行空间
  • 可以获取函数内部变量的值
    • 一个不销毁的函数执行空间
    • 函数执行空间只要存在,就会占用内存
    • 占用内存过多,就会导致内存泄漏
  • 因为是函数的关系,会保护一些私有变量
    • 变量不会污染全局
    • 不能在外部直接访问

函数的定义阶段

  • 在堆里面开辟了一个函数存储空间
  • 把函数体内的代码一模一样放到这个空间里面
  • 这个时候不会解析变量
  • 存储空间地址赋值给了函数名(变量名)

函数调用阶段

函数内部的代码,是不会在一开始进行预解析的

而是在函数调用的时候才开始进行预解析

  • 按照变量存储的地址找到对应的存储空间
  • 形参赋值
  • 预解析
  • 再次开辟一个空间,叫做函数执行空间
  • 存储空间里面的代码,拿到函数执行空间里面来执行
  • 代码执行完毕,函数执行空间销毁

数据类型在内存中的存储

  1. 基本数据类型
  2. 复杂数据类型
    • 因为是依靠地址来进行关联
    • 也叫做地址数据类型
    • 我们又管这个地址叫做引用
    • 也叫做引用数据类型

堆和栈的区别

堆和栈的区别可以用如下的比喻来看出:

  • 使用栈就像我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,它的好处是快捷,但是自由度小。
  • 使用堆就像是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。

含义:

  • 在计算机内存中分为两个内存空间,分别为
  • 它们都是一种数据项按序排列的数据结构

栈 (heap)

  • 基本数据类型直接存储在栈里面

堆 (stack)

  • 由系统自动随机分配的空间
  • 复杂数据类型的 内容 存储在堆里面
  • 地址存储在栈里面

this 指向

  • this 指向只和 函数的调用方式 有关系
    1. 全局调用
      • 函数名()
      • this => window
    2. 对象调用
      • 对象名.函数名()
      • this => 点前面是谁就是谁
    3. 事件处理函数
      • this => 事件源(谁身上的事件)
    4. 定时器处理函数
      • this => window
    5. 构造函数
      • this => 当前实例
    6. 自调用函数
      • this => window
    7. 箭头函数
      • this => 上下文
  • 三个改变 this 指向的方法
    1. call
    2. apply
    3. bind

call、apply、bind 的区别

  • 三个方法都可以强行改变 this 指向

    • 不管你原先的 this 指向谁
    • 现在我让你指向谁,你就指向谁
  • 三个方法的第一个参数都是改变 this 指向

    • 这个参数可以不写或者写 null
    • 你不写或者写 null 的时候,函数内部的 this 都是 window

call

  • 是一个属于函数的方法
  • 语法:函数名.call() / 对象名.函数名.call()
  • 第一个参数:就是你要改变的 this 指向
  • 第二个参数开始,依次是给函数传递的参数
function fn(a, b) {
    console.log(this)
    console.log(a)
    console.log(b)
}

var obj = {
    name: '我是 obj 对象'
}

var name = '我是 window'

fn(100, 200) // this => window

console.log('====================================')

fn.call(obj, 10, 20) // this => obj

/******************** 我是一条华丽的分割线 ********************/

var obj = {
    name: '我是 obj 对象',
    fn: function (a, b) {
        console.log(this)
        console.log(a)
        console.log(b)
    }
}

obj.fn(100, 200) // this => obj

console.log('====================================')

obj.fn.call(window, 10, 20) // this => window

apply

  • 是一个属于函数的方法
  • 语法:函数名.apply() / 对象名.函数名.apply()
  • 第一个参数:就是你要改变的 this 指向
  • 第二个参数:是一个数组伪数组
    • 数组或伪数组里面的每一项,依次是给函数传递的参数
function fn(a, b) {
    console.log(this)
    console.log(a)
    console.log(b)
}

var obj = {
    name: '我是 obj 对象'
}

var name = '我是 window'

fn(100, 200) // this => window

console.log('====================================')

fn.apply(obj, [10, 20]) // this => obj

/******************** 我是一条华丽的分割线 ********************/

var obj = {
    name: '我是 obj 对象',
    fn: function (a, b) {
        console.log(this)
        console.log(a, b)
    }
}

obj.fn(100, 200) // this => obj

console.log('====================================')

obj.fn.apply(window, [10, 20]) // this => window

bind

  • 是一个属于函数的方法
  • 语法:函数名.bind() / 对象名.函数名.bind()
  • 返回值:已经改变好的 this 指向的新函数
  • 第一个参数:就是你要改变的 this 指向
    • 你写的是谁,那么这个函数里面的 this 就是谁
    • bind() 不会把函数执行,而是返回一个新的函数,这个新的函数内部的 this 是改变好的
  • 给函数传递参数,有两种方式
    • bind() 的第二个参数开始,依次是给函数传递的参数
    • 在调用返回的那个函数的时候,直接传参
    • 当你在两个位置都写参数的时候,以 bind 第二个参数开始的那些内容为准
function fn(a, b) {
    console.log(this)
    console.log(a, b)
}

var obj = {
    name: '我是 obj 对象'
}

var name = '我是 window'

fn(100, 200) // this => window

console.log('====================================')

// 会改变 this 指向,但是不会让函数调用
var res = fn.bind(obj, 10, 20)
// res 是一个新的函数,当你 res() 的时候,它里面的 this 指向已经改变
res(1000, 2000) // this => obj

手写 call 原理

  • call 函数会将要改变的方法放到目标对象之下作为一个成员属性
var b = 2
function sum(a) {
    return a + this.b
}

var obj = {
    b: 3,
    // sum: sum
}

// let result = obj.sum(1)
let result = sum.call(obj, 1)
console.log(result)

/******************** 我是一条华丽的分割线 ********************/

var b = 5

function sum(a, c, d) {
    // this.b 就是挂载到 window 上的 b = 5
    return a + this.b + c + d
}

let obj = {
    b: 3
}

// Function.prototype 属性存储了 Function 的原型对象
Function.prototype.myCall = function (context) {
    var context = context || window

    // this 指向的是执行 myCall 的函数
    context.fn = this

    /*
        this => obj = {
            b: 3,
            fn: function sum(a, c, d) {
                return a + this.b + c + d
            }
        }
    */

    // 函数中的 arguments 可以获取到所有的参数集合
    let params = [...arguments].slice(1)

    // 获取除了第一项之外的其余参数
    // console.log(params)
    let res = context.fn(...params)

    // 手动添加的方法用完之后要删除
    delete context.fn
    return res
}

// 每一个函数都可以使用原型对象上的 myCall 方法
let result = sum.myCall(obj, 2, 3, 4)
console.log(result)

什么是继承?

  • 构造函数的应用
  • 继承出现在两个构造函数之间
    • 两个构造函数有父子关系了
    • a 函数b 函数 的父类
    • b 函数a 函数 的子类
function Person(name) {
    this.name = name
}

Person.prototype.sayHi = function () {
    console.log('我是 sayHi 方法')
}

const p = new Person('Jack')
console.log(p)

/*
    p = {
        name: 'Jack',
        __proto__: Person.prototype {
            sayHi: function () {},
            __proto__: Object.prototype
        }
    }
*/

function Student(age) {
    this.age = age
}

const s = new Student(18)
console.log(s)

// 如果我让 s 也能调用 sayHi 方法
// Student 继承了 Person
// 就说 Student 是 Person 的子类
// 就说 Person 是 Student 的父类

什么是原型?

  • 原型:构造函数的 prototype
  • 目的:为了解决在函数体内写方法的缺点
  • 一个构造函数 A 有属性和方法
  • 另一个构造函数 B 使用了 A 构造函数的属性和方法

原型链

  • 含义:从任意一个对象出发,按照 __proto 串联起来的对象链状结构。
  • 作用:为了对象成员的访问。
<body>
    <div></div>
    <script>
        const box = document.querySelector('div')

        // 详细打印 div
        console.dir(box)
        console.log(box.gender) // undefined

        /*
          div: (__proto__)
          => __proto__: HTMLDivElement
          => __proto__: HTMLElement
          => __proto__: Element
          => __proto__: Node // 元素也是节点的一部分
          => __proto__: EventTarget // 事件对象
          => __proto__: Object
          
          顶级对象:
          __proto__: Object
            constructor: ƒ Object()
            hasOwnProperty: ƒ hasOwnProperty()
            isPrototypeOf: ƒ isPrototypeOf()
            propertyIsEnumerable: ƒ propertyIsEnumerable()
            toLocaleString: ƒ toLocaleString()
            toString: ƒ toString()
            valueOf: ƒ valueOf()
            __defineGetter__: ƒ __defineGetter__()
            __defineSetter__: ƒ __defineSetter__()
            __lookupGetter__: ƒ __lookupGetter__()
            __lookupSetter__: ƒ __lookupSetter__()
            get __proto__: ƒ __proto__()
            set __proto__: ƒ __proto__()
        */
    </script>
</body>

对象的访问机制

决定着对象中的成员是否可以访问

const obj = {
    name: 'jack'
}

obj.__proto__.age = 18

console.log(obj.name) // 自己有,直接返回结果
console.log(obj.age) // __proto__: age = 18 有结果
  • 当你访问一个对象中的成员的时候
  • 如果它自己有这个成员,那么就直接返回结果给你,停止查找
  • 如果它自己没有这个成员,就会自动去 __proto__ 上查找
  • 如果它的 __proto__ 上也没有,再去 __proto__ 上查找
  • 再没有,就再去 __proto__ 上查找
  • 一直找到 Object.prototype 上都没有的话,就返回 undefined
  • Object 是我们 JavaScript 中的顶级构造函数
  • 所以 JS 中的顶级原型就是 Object.prototype
  • 它不再有 __proto__ 了,它的 __protonull

定义:一个对象,如果没有准确的所属构造函数,那么它就是 Object 的实例

Object.prototype.gender = '男'

function Person() {
    // this => p1.__proto__
    this.name = 'Jack'
}

Person.prototype.age = 18

// Person.prototype 是一个对象
// Person.prototype 所属的构造函数是谁 Object
// Person.prototype.__proto__ === Object.prototype

// p1 所属的构造函数是 Person
const p1 = new Person()

console.log(Person.prototype.__proto__ === Object.prototype) // true
console.log(p1.__proto__ === Person.prototype) // true
console.log(p1.__proto__.__proto__ === Object.prototype) // true

console.log(Object.prototype.__proto__) // null

console.log(p1.name) // 自己有就直接使用自己的
console.log(p1.age) // 自己没有,去自己的 __proto__ 上找,也就是去 Person.prototype 上找
console.log(p1.gender) // 自己没有,去自己的 __proto__ 上找没有,再去 __proto__ 上找,就相当于去了 Object.prototype 上找
console.log(p1.score) // 自己没有,一直找到 Object.prototype 上都没有,就返回 undefined

// arr 所属的构造函数是谁 Array
var arr = []
console.log(arr.__proto__ === Array.prototype) // true

// time 所属的构造函数是谁 Date
var time = new Date()
console.log(time.__proto__ === Date.prototype) // true

var reg = /^abcd$/
console.log(reg.__proto__ === RegExp.prototype) // true

var obj = {}
console.log(obj.__proto__ === Object.prototype) // true

原型继承

  • 当你使用 Person 这个构造函数去实例化对象的时候

  • 你能得到一个对象

  • prototype 是一个函数自带的对象空间,里面可以存储一些数据

    • 因为它是一个对象空间的地址
    • 我能不能给你换一个空间地址
function Person(name) {
    this.name = name
}

Person.prototype.sayHi = function () {
    console.log('我是 sayHi 方法')
}

function Student(age) {
    this.age = age
}

Student.prototype = new Person('Jack')

const s = new Student(18)
console.log(s)

/*
    s = {
        age: 18
        __proto__: Student.prototype === p {
            name: 'Jack',
            __proto__: Person.prototype {
                sayHi: function () {},
                __proto__: Object.prototype
            }
        }
    }

    s.sayHi() 能不能用?
*/

s.sayHi()
  • 缺点
    • 继承来的属性是在 __proto__
    • 会按照 原型链查找机制 向上查找一层(浪费性能)
    • 当我 new 子类的时候,不能设置这个继承来的属性的值

constructor

  • 函数天生自带的那个 prototype 上面有一个成员叫做 constructor ,指向这个原型的构造函数
  • 你查看哪个构造函数的 prototype ,那么这个 prototype 里面的 constructor 就指向哪个构造函数
function Person() {}

console.log(Person.prototype.constructor === Person) // true
console.log(Array.prototype.constructor === Array) // true
console.log(Object.prototype.constructor === Object) // true

/******************** 我是一条华丽的分割线 ********************/

// 访问一个数组的 constructor
const arr = []
// arr 自己没有 constructor 属性
// 去到自己的 __proto__ 上找,也就是去 Array.prototype 上找
console.log(arr.constructor === Array) // true

const obj = {}
console.log(obj.constructor === Array) // false

// 我们知道 typeof 判断复杂数据类型不好使 => 全都是 object
// 通过上述例子,我们现在了解到 constructor 也可以帮助我们判断数据类型

借用构造函数继承

  • 借用构造函数继承(也称为 call 继承)
    • 就是在子类的构造函数体内,调用一下父类的构造函数体
  • Person 是一个构造函数
    • 使用的时候需要和 new 连用
    • 可不可以不和 new 连用
function Person(name) {
    this.name = name
}

Person.prototype.sayHi = function () {
    console.log('我是 sayHi 方法')
}

function Student(age, name) {
    this.age = age

    // 调用 Person 这个函数
    Person.call(this, name)
}

const s = new Student(18, 'Jack')
console.log(s)
  • 缺点
    • 只能继承构造函数体内的内容
    • 原型的内容不能继承

组合继承

  • 把借用构造函数和原型继承组合在一起使用
function Person(name) {
    this.name = name
}

Person.prototype.sayHi = function () {
    console.log('我是 sayHi 方法')
}

function Student(age, name) {
    this.age = age

    // 调用 Person 这个函数
    Person.call(this, name)
}

Student.prototype = new Person()

const s = new Student(18, 'Rose')
console.log(s)

ES6 的继承

  • 两个关键字
  • extends
    • 在你书写构造函数的后面使用
  • super
    • 在子类构造函数体内调用一下
    • 注意super 必须调用在构造函数体内的最前面
  • 属性、方法全继承
class Person {
    // constructor 构造函数体
    constructor (name) {
        this.name = name
    }

    sayHi() {
        console.log('我是 sayHi 方法')
    }
}

class Student extends Person {
    constructor (age) {
        // super 就相当于在调用父类构造函数的函数体
        super('Rose')
        this.age = age
    }
}

const s = new Student(18)
console.log(s)

类继承构造函数

  • ES6 的继承语法,是可以继承 ES5 的构造函数的
function Person(name) {
    this.name = name
}

Person.prototype.sayHi = function () {
    console.log('我是 sayHi 方法')
}

class Student extends Person {
    constructor (age, name) {
        super(name)
        this.age = age
    }
}

const s = new Student(20, 'Rose')
console.log(s)

instanceOf 运算符

  • instanceOf 运算符 用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链
function Car(make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
}
var auto = new Car('Honda', 'Accord', 1998);

console.log(auto instanceof Car);
// expected output: true

console.log(auto instanceof Object);
// expected output: true

HTTP 超文本传输协议

  • 传输:互相传递数据
  • 协议:传输规则
    • 传输只能有前端发起
    • 传输的过程要经历四个步骤
      • 建立连接
      • 前端发送一个内容(请求)
      • 后端回复一个内容(响应)
      • 断开连接
  • 前后端交互的规则

基于 TCP/IP 协议的三次握手(建立连接)

让前端和后端互相知道我们的收发是正常的

  • 前端丢给后端一个包
    • 前端知道前端有发送的能力
  • 后端接收到包以后,回复给前端一个包
    • 后端知道前端有发送的能力
    • 后端知道后端有接收的能力
    • 后端知道后端有发送的能力
  • 前端接收到后端回复的包以后,再回复给后端一个包
    • 前端知道前端有接收的能力
    • 前端知道后端有接收的能力
    • 前端知道后端有发送的能力
  • 后端接收到前端再次回复的包
    • 后端知道前端有接收的能力
  • 连接通道建立成功

基于 TCP/IP 协议的四次挥手(断开连接)

  • 前端和后端说,我要断开连接了
  • 后端接收到以后,返回给前端一个信息
    • 我知道了,我已经进入了断开连接状态
  • 后端再次回复一个信息
    • 当我再接收到你的信息的时候,我就断开了,不再回复了
  • 前端收到以后,前端关闭连接通道
    • 前端发送给后端一个消息,我已经关闭了

GET 和 POST 的区别

  • GET
    • 一般是向服务端索要数据的时候使用
      • 渲染表格需要的数据
  • POST
    • 一般是给服务端一些内容
      • 登录、注册
      • 用户名、密码
      • 邮箱等

GET

  • GET 参数拼接在地址栏后端(明文发送)
  • GET 相对于 POST 请求不安全一些
  • GET 请求有大小限制(因为 IE 浏览器给的限制)
  • GET 参数数据类型被限制为 ASCII 码
  • GET 请求会被浏览器自主缓存

POST

  • POST 参数在请求体里面(暗文发送)
  • POST 相对于 GET 请求安全一些
  • POST 理论上没有大小限制,但是会被服务器限制
  • POST 参数数据格式理论上没有限制,但是要和 Content-Type 配套
  • POST 请求不会被浏览器自主缓存

什么是 ajax ?

什么是回调函数?