什么是 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 ~ 正无穷
数组的创建方法
- 字面量创建
var arr = []- 可以在创建的时候
直接添加内容
- 内置构造函数创建(Array)
var arr = new Array()- 内置构造函数的方式可以
使用传递参数的形式直接写入内容
创建数组的内置构造函数的三种使用方式
- 不传递参数
- 得到的就是一个空数组
- 传递一个大于 0 的整数
- 这个数字表示数组的长度
- 里面的每一个数据都是空的 =>
[empty × 传递进来的数字]
- 传递多个参数(任意个)
- 每一个参数就是数组里的每一个数据
操作数组的方法
- 我们的数组不好进行修改
- 需要一些方法来对数组进行操作
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)
字符串
- 是一个基本数据类型
- 被引号或者双引号包裹的内容
- 也叫做包装数据类型
- 当你使用它的时候,它会把自己转换成对象的形式
- 当你使用完毕以后,它又会自己转换回基本数据类型
字符串的创建方式
- 字面量
var str = 'hello'
- 内置构造函数
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
- 返回的就是
- 如果有对应字符片段的内容
字符串和数组相同的几个方法
slice()截取concat()拼接indexOf()按照片段查询索引
indexOf()与replace()结合实例:
// 给一段文章中的全部指定词语进行过滤
var str = 'asdasdSMasdasdasdSMsdasdasdSMsadasd'
while (str.indexOf('SM') !== -1) {
str = str.replace('SM', '**')
}
console.log(str)
什么是作用域
- 就是一个变量的使用范围
- 我定义一个变量可以在哪里进行使用
作用域分为两种
作用域和函数怎么调用没有关系,是在函数定义的时候就决定好了
- 全局作用域
- 一个页面就是一个全局作用域
- 私有作用域
- 只有函数能够生成一个私有作用域(重点)
变量的访问机制
变量的访问机制决定着一个变量是否可以访问
- 当你访问一个变量的时候,会先在自己的作用域里面找
- 如果有,直接拿过来使用,停止查找
- 如果没有,就去上一级作用于里面找,如果有就拿过来使用,停止查找
- 如果还没有,就再去上一级作用域里面查找,如果有就拿过来使用
- 如果还没有,再去上一级查找,直到全局作用域都没有,就报错
- 注意:只能去上一级找,不能去下一级查找
变量的赋值机制
- 当你想给一个变量赋值的时候
- 如果自己作用域里面有,那么就直接给自己作用域的变量赋值
- 如果自己作用域里面没有,那么去看看上一级作用于里面有没有,如果有就赋值
- 如果还没有,那么就再去上一级看看有没有
- 直到全局作用域都没有这个变量的时候
- 会先把这个变量定义为全局变量,再给它赋值(重点)
- 注意:只能去上一级查找,不能去下一级查找
什么是递归函数?
在最后一个函数没有执行完毕之前,前面的函数一直都是等待的状态
在等待的状态就会占用内存,所以慎用递归函数
- 是函数的一种特殊的使用手段
- 一个函数当自己调用自己的时候,就是递归函数
var、let、const 的区别
var关键字定义的变量会进行变量提升var声明的变量会被提升到作用域顶部- 我们需要了解
函数也会被提升,并且优先于变量提升 - 函数提升会把
整个函数挪到作用域顶部,变量提升只会把声明挪到作用域的顶部 let和const在全局作用域下声明变量,并不会被挂载到window上let和const必须先声明,后使用,不然会报错(暂时性死区)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)里面的变量
- 当上面三个条件满足的时候,我们就称它为
闭包函数
闭包的特点
- 延长了变量的生命周期
- 一个不销毁的
函数执行空间
- 一个不销毁的
- 可以获取函数内部变量的值
- 一个不销毁的
函数执行空间 - 函数执行空间只要存在,就会占用内存
- 占用内存过多,就会导致内存泄漏
- 一个不销毁的
- 因为是函数的关系,会保护一些私有变量
- 变量不会污染全局
- 不能在外部直接访问
函数的定义阶段
- 在堆里面开辟了一个
函数存储空间 - 把函数体内的代码一模一样放到这个空间里面
- 这个时候
不会解析变量 - 把
存储空间地址赋值给了函数名(变量名)
函数调用阶段
函数内部的代码,是不会在一开始进行预解析的
而是在函数调用的时候才开始进行预解析
- 按照变量存储的
地址找到对应的存储空间 - 形参赋值
- 预解析
- 再次开辟一个空间,叫做
函数执行空间 - 把
存储空间里面的代码,拿到函数执行空间里面来执行 - 代码执行完毕,函数执行空间销毁
数据类型在内存中的存储
- 基本数据类型
- 复杂数据类型
- 因为是依靠地址来进行关联
- 也叫做
地址数据类型 - 我们又管这个地址叫做
引用 - 也叫做
引用数据类型
堆和栈的区别
堆和栈的区别可以用如下的比喻来看出:
- 使用栈就像我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,它的好处是快捷,但是自由度小。
- 使用堆就像是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。
含义:
- 在计算机内存中分为两个内存空间,分别为
堆和栈 - 它们都是一种数据项按序排列的数据结构
栈 (heap)
- 基本数据类型直接存储在栈里面
堆 (stack)
- 由系统自动随机分配的空间
- 复杂数据类型的
内容存储在堆里面 - 地址存储在栈里面
this 指向
- this 指向只和
函数的调用方式有关系- 全局调用
- 函数名()
- this => window
- 对象调用
- 对象名.函数名()
- this => 点前面是谁就是谁
- 事件处理函数
- this => 事件源(谁身上的事件)
- 定时器处理函数
- this => window
- 构造函数
- this => 当前实例
- 自调用函数
- this => window
- 箭头函数
- this => 上下文
- 全局调用
- 三个改变 this 指向的方法
- call
- apply
- 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__了,它的__proto是null
定义:一个对象,如果没有准确的所属构造函数,那么它就是 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请求不会被浏览器自主缓存