01: 作用域
作用域类型
- 局部作用域:
- 函数作用域: 在函数内部声明的变量只能在函数内部被访问,外部无法直接访问。函数执行完毕后,函数内部的变量实际被清空。
- 块作用域: 在JavaScript中使用
{}包裹的代码称为代码块。
- 注意:
let声明的变量会产生块作用域,const常量也会产生块作用域,var不会产生块作用域。
- 全局作用域: 在
<script>标签和.js文件的【最外层】就是所谓的全局作用域。
作用域链
本质上是底层的变量查找机制。在函数被执行时,会优先查找当前函数作用域中的变量,如果没有,则依次逐级查找父级作用域直到全局作用域。
垃圾回收机制
简称GC,JavaScript内存的分配和回收都是自动完成的,内存在不使用的时候会被垃圾回收器自动回收。
- 内存泄漏: 不再用到的内存,没有及时释放。
- 垃圾回收算法:
- 引用计数法(老版本IE浏览器): 看一个对象是否有指向它的引用,如果被引用一次,那么记录次数1,反之减少引用则减1,如果引用次数是0,则释放内存。
- 缺点: 如果两个对象相互引用,尽管他们已不再使用,垃圾回收器不会进行回收,导致内存泄漏。
- 标记清除法(目前大部分浏览器): 标记清除算法将"不再使用的对象"定义为"无法达到的对象",就是从根部出发定时扫描内存中的对象,凡是能从根部到达的对象,都是还需要使用的,反之回收。
闭包
通俗来说就是内层函数访问到其外层函数的作用域,如闭包 = 内层函数 + 外层函数的变量。
- 作用: 封闭数据,提供操作,外部也可以访问函数内部的变量。
- 代码示例:
代码语言:JavaScript
代码运行次数:0
自动换行运行
AI代码解释
function out(){ let i = 1 function fn(){ console.log(i) } return fn } const fun = out() fun()
变量提升与函数提升
- 变量提升: 在JavaScript中允许在变量声明之前即被访问(仅存在
var声明变量)。
- 注意:
- 变量在
var声明之前被访问,变量的值为undefined let/const声明的变量不存在变量提升
- 函数提升: 函数提升与变量提升比较类似,是指函数在声明之前即被调用。
函数参数
- 动态参数:
arguments是函数内置的伪数组变量,它包含了调用函数时传入的所有实参(伪数组只存在函数中)。
- 代码示例:
代码语言:JavaScript
代码运行次数:0
自动换行运行
AI代码解释
function sum(){ let s = 0 for(let i = 0; i < arguments.length; i++){ s += arguments[i] } console.log(s) } sum(1,2) sum(1,2,3)
- 剩余参数:
- 语法:
...是语法符号,置于最末函数形参之前,用于获取多余的实参(...获取的剩余实参,是个真数组)。 - 代码示例:
代码语言:JavaScript
代码运行次数:0
自动换行运行
AI代码解释
function config(a,...other){ console.log(a) console.log(other) } config(1,2,3)
- 展开运算符:
- 语法:
...将一个数组进行展开。 - 代码示例:
代码语言:JavaScript
代码运行次数:0
自动换行运行
AI代码解释
const arr = [1,2,3,4,5] console.log(...arr) console.log(Math.max(...arr))
- 注意: 剩余参数主要是在函数实参使用,得到真数组;展开运算符主要是数组使用,数组展开。
箭头函数
- 目的: 引入箭头函数的目的是更简短的函数写法并且不绑定
this,箭头函数的语法比函数表达式更简洁。 - 语法:
() => {} - 注意:
- 只有一个参数可以省略小括号
- 如果函数体只有一行代码,可以写到一行上,并且无需写
return直接返回值 - 如果最后返回的是一个对象,加括号的函数体返回对象字面量表达式
- 箭头函数没有
arguments动态参数,但是有剩余参数...args - 箭头函数不会创建自己的
this,它只会从自己的作用域链的上一层沿用this - 由于上一点的原因,DOM事件回调函数为了简便,还是不太推荐使用箭头函数
解构赋值
- 目的: 使用解构简洁语法为变量快速赋值。
数组解构
- 代码示例:
代码语言:JavaScript
代码运行次数:0
自动换行运行
AI代码解释
// 基本语法 const [a,b,c] = [1,2,3] // 交换两个变量 let a = 1 let b = 3; [b,a] = [a,b]
- 注意事项:
- JavaScript在两种情况下需要加分号: 立即执行函数、数组解构
- 变量多,单元值少时,多余的变量将被赋值为
undefined - 变量少,单元值多时,依旧按顺序赋值
- 可以利用剩余参数解决变量少,单元值多的情况,剩余参数返回的是一个数组
- 防止有
undefined传递单元值,可以设置默认值:
代码语言:JavaScript
代码运行次数:0
自动换行运行
AI代码解释
const [a = '手机', b = '华为'] = ['小米']
- 数组解构可以按需导入,忽略某些返回值:
代码语言:JavaScript
代码运行次数:0
自动换行运行
AI代码解释
const [a,,c,d] = [111,222,333,444]
对象解构
- 代码示例:
代码语言:JavaScript
代码运行次数:0
自动换行运行
AI代码解释
// 基础语法 const user = { name: '小明', age: 18 } const {name, age} = user // 数组对象解构 const pig = [ { name: '佩奇', age: 6 } ] const [{name, age}] = pig // 多级对象解构 const pig = { name: '佩奇', family: { mother: '猪妈妈', father: '猪爸爸', sister: '乔治' }, age: 6 } const {name, family: {mother, father, sister}, age} = pig
数组方法
forEach()方法
- 语法:
代码语言:JavaScript
代码运行次数:0
自动换行运行
AI代码解释
arr.forEach(function(item, index, self){ // item代表数组中每一个元素 // index代表每一个索引 // self代表数组本身 })
filter()方法
- 使用场景: 筛选数组符合条件的元素,并返回筛选之后元素的新数组。
- 语法:aly.sos-tofl.com55
代码语言:JavaScript
代码运行次数:0
自动换行运行
AI代码解释
arr.filter(function(item, index){ // item代表数组中每一个元素 // index代表每一个索引 // 例如筛选大于30的元素 return item > 30 })
02: 内置构造函数
创建对象的三种方式
- 利用对象字面量创建对象
代码语言:JavaScript
代码运行次数:0
自动换行运行
AI代码解释
const o = { name: '佩奇' }
- 利用
new Object创建对象
代码语言:JavaScript
代码运行次数:0
自动换行运行
AI代码解释
const o = new Object({name: '佩奇'})
- 利用构造函数创建对象
代码语言:JavaScript
代码运行次数:0
自动换行运行
AI代码解释
function Pig(name, age, gender){ this.name = name this.age = age this.gender = gender } const Peppa = new Pig('佩奇', 6, '女')
- 注意事项:
- 对象的命名以大写字母开头
- 使用
new关键字调用函数的行为称为实例化 - 构造函数内部无需写
return,返回值即为新创建的对象 - 构造函数内部的
return返回值无效
实例化执行过程
- 创建新对象
- 构造函数
this指向新对象 - 执行构造函数代码,修改
this,添加新的属性 - 返回新对象
成员类型
- 实例成员: 通过构造函数创建的对象称为实例对象,实例对象中的属性和方法称为实例成员。
- 静态成员: 构造函数的属性和方法被称为静态成员。
内置构造函数
包装类型
当把字符串、数字这种原始类型当作对象使用时,JavaScript会自动包装他们,把他们包装为对象。
- 代码示例:
代码语言:JavaScript
代码运行次数:0
自动换行运行
AI代码解释
// String/Number/Boolean let str = 'hello' console.log(typeof str) // string str = new String('hello') // JS内部,自动 new String(),得到一个对象 console.log(typeof str) // object console.log(str.length) console.log(str.toUpperCase())
Object
Object.keys(obj): 获取对象中的所有属性Object.values(obj): 获取对象中的所有属性值Object.assign(obj): 用于对象拷贝
Array
forEach: 遍历数组,不返回,用于不改变值,经常用于查找打印输出filter: 筛选数组元素,并生成新数组map: 迭代数组,返回新数组,经常用于处理数据reduce: 累加器,返回函数累计处理的结果
- 注意:
arr.reduce(function(total,current){},起始值),起始值可以省略,如果写就作为第一次累积的起始值
some: 根据条件,如果有元素符合,返回true,反之falseevery: 根据条件,如果全部元素符合,返回true,反之falsefind: 根据条件,如果有符合的元素,返回第一个元素,并终止查找,如果没有,返回undefinedfindIndex: 根据条件,如果有符合条件的元素,则返回这个元素的下标,反之返回-1sort: 语法:arr.sort((a,b) => {})reverse: 翻转数组splice: 从指定的下标,删除几个元素,并可以添加新的元素Array.from(): 将伪数组转成真数组
String
split: 将字符串按指定的符号分割为数组substring: 字符串截取(包头不包尾)includes: 判断字符串是否包含xxx,如果有指定下标,表示从指定下标开始,看字符串是否包含xxxstartWith(): 判断字符串开头,如果有指定下标,表示从指定下标开始的开头endsWith(): 判断字符串结尾,如果有指定下标,表示先截取n个字符,看这n个字符的结尾是否以xxx结尾replace(): 替换字符串的某个字符,如果要全部替换,使用正则表达式
Number
toFixed(aly.play01.net): 去掉多余的小数(四舍五入)
03: 对象高级知识
面向对象与面向过程
- 面向对象: 把需求中遇到的事物,分为一个个对象。
- 三个特点: 封装、多态、继承
- 优点: 性能高
- 缺点: 没有面向对象易维护、易复用、易扩展
- 面向过程: 一步一步的完成需求。
- 优点: 易维护,可以设计出高内聚低耦合的系统
- 缺点: 性能比面向过程低
构造函数
构造函数的this指向实例对象。
原型对象
任何函数都有原型对象,构造函数、内置构造函数也不例外。
- 语法:
构造函数.prototype - 注意: 构造函数和原型对象中的
this都指向实例对象。
实际上,实例对象的[[Prototype]]属性才指向原型对象,由于[[Prototype]]是隐藏属性,不可访问,所以浏览器才实现__proto__,用于访问原型对象,但是不推荐使用,建议使用Object.getPrototypeOf(实例对象)来找原型对象。
原型继承
- 注意:
- 通过修改构造函数的原型对象,可以实现对象的继承
- 通过原型继承后,需要手动修改原型对象的
constructor属性,否则不符合上述的三角关系 - 固定语法:
构造函数.prototype.constructor = 构造函数
原型链
基于原型对象的继承使得不同构造函数的原型对象关联在一起,并且这种关联的关系是一种链状的结构,我们将原型对象的链状结构关系称为原型链(面试爱问)。
instanceof运算符
检测在p的原型链上,是否有这个构造函数。
- 语法:
实例对象 instanceof 构造函数
04: 对象赋值
对象赋值
- 直接赋值:
- 原始类型(字符串,数字...)的值,直接赋值之后,只是把存储的值赋值给变量。赋值之后,两个变量互不影响。
- 引用类型的值,因为存储的是地址,等号赋值之后,是把地址赋值给了另一个变量,修改其中一个对象的属性,另一个对象也跟着改了。
浅拷贝
只关注对象的第一层,只拷贝对象第一层的属性和属性值,这样的拷贝,就叫做浅拷贝。
- 代码示例:
代码语言:JavaScript
代码运行次数:0
自动换行运行
AI代码解释
let obj1 = { name: 'zs', age: 20, height: 180 } let obj2 = {} // obj2.name = 'zs' // obj2.age = 20 // 循环遍历 obj1,循环一次,取obj1里面的一个属性,然后给obj2加上 for (let key in obj1) { // key 是 name、age、height // obj1[key] 是 zs、20、180 // console.log(obj1[key]) // key是变量,必须用[]语法,而且绝对不能加引号 obj2[key] = obj1[key] }
深拷贝
通过递归,逐层将对象的属性、属性值拷贝给另一个对象,使得两个对象能够完全分开。
- 代码示例:
代码语言:JavaScript
代码运行次数:0
自动换行运行
AI代码解释
let obj2 = {} function fn(obj1, obj2) { for (let key in obj1) { // 循环的时候,先判断,参见上面的 1、 2 if (Array.isArray(obj1[key])) { obj2[key] = [] fn(obj1[key], obj2[key]) } else if (obj1[key] instanceof Object) { obj2[key] = {} fn(obj1[key], obj2[key]) } else { // 如果进入else,说明对象的值是普通的值,则直接拷贝 obj2[key] = obj1[key] } } } fn(obj1, obj2) // 尝试修改一个值 obj1.dog.color = '绿色'
其他拷贝方案
Object.assign(): 本质是实现对象的合并,但是可以用于实现对象的浅拷贝。JSON.parse和JSON.stringify: 注意:JSON中不能出现函数,undefined,但是可以实现深拷贝。
- 代码示例:
代码语言:JavaScript
代码运行次数:0
自动换行运行
AI代码解释
// 通过 JSON.stringify 将上述对象先转为 JSON 字符串 let str = JSON.stringify(obj1) // 把 str 字符串,转成对象 let obj2 = JSON.parse(str)
- 使用前端知名的库
lodash实现深、浅拷贝(注意要加载lodash.min.js)。
递归
自己调用自己,注意写递归时,必须设置一个终止条件,否则会形成死循环。
异常处理
- 语法1:
throw和new Error('错误提示')(了解) - 语法2:
代码语言:JavaScript
代码运行次数:0
自动换行运行
AI代码解释
try { // 代码 } catch(e) { // 错误处理 }
this指向及修改this指向
- 普通函数中的
this,表示调用者 - 箭头函数中的
this,需要按照作用域链去查找即可也可以分开记忆,以下是非箭头函数的总结:
- 全局中的
this表示window - 普通函数中的
this表示window - 定时器中的
this表示window - 事件处理函数中的
this表示事件源 - 构造函数、对象方法、原型对象方法中的
this,表示实例对象 - 静态方法中的
this,表示构造函数