目录
JavaScript
内容记录的是JavaScript进阶笔记。
作用域
作用域(scope)
规定了变量能够被访问的"范围
",离开了这个范围
变量便不能被访问,作用域可以堆叠成层次结构,子作用域可以访问父作用域,作用域分为:
局部作用域
全局作用域
模块作用域
1.1 局部作用域
局部作用域分为
函数作用域
和块作用域
函数作用域:
在函数
内部声明的变量
只能在函数内部被访问
,外部无法直接访问。function sum(num) { // 形参num相当于局部变量 const num = 10 } sum(1); console.log(num) // 外部无法使用函数内部变量
总结
函数内部
生命的变量,在函数外部无法被访问
- 函数的
形参
也可以看做是
函数内部的局部变量
块级作用域
在JavaScript中使用
{}
包裹的代码成为代码块,代码块内部声明的变量外部【有可能
】无法被访问。for (let i = 0; i < 10; i++) { // i 只能在改代码块中被访问 console.log(i); // 正常 } // 超出了 i 的作用域 console.log(i) // 报错
总结
let/const
声明会产生块作用域
,var
不会产生作用域不同
代码之间声明的变量无法互相
访问- 推荐使用
let或const
1.2 全局作用域
<script>标签
和.js文件
的【最外层】就是全局作用域
,在此声明的变量在函数内部可以被访问。全局
作用域中声明的变量
,在任何其他作用域都可以访问
<script> // 全局作用域 // 全局作用域下声明了num变量 const num = 10 function fn() { // 函数内部可以使用全局作用域的变量 console.log(num); } </script>
总结
- 为
window
对象动态添加属性默认也是全局的,不推荐使用(其中当某个变量没有被声明变量声明时,该变量是挂载到window身上的
)函数
中为适应的任何关键字声明的变量为全局变量,不推荐- 尽可能少的声明全局变量,防止
全局变量污染
1.3 作用域链
嵌套关系的
作用于串联
起来形成了作用域链
作用:作用域链本质上是底层的
变量查找机制(就近原则)
- 在函数被执行时,会优先查找
当前
函数作用域
中查找变量- 如果当前作用域查找不到则会
逐级向上查找
赋值作用域直到全局作用域// 全局作用域 let a = 11 let b = 22 // 局部作用域 function f() { let a = 1 // 局部作用域 function g() { a = 2 console.log(a) // 2 } g() // 调用g } f() // 调用f
总结
- 嵌套关系的
作用域串联
起来形成了作用域链- 查找规则:
就近原则
- 当前作用于找不到,则会
逐级查找父级作用域
直到全局作用域- 都找不到则提示错误,这个变量没有被定义过
- 子作用域
能够
访问父作用域,父作用域无法访问
子级作用域
1.4 垃圾回收机制
垃圾回收机制(Garbage Collection)简称GC
- JS中
内存
的分配和回收都是自动完成
的,内存在不适用的时候都会被垃圾回收器
自动分配内存的生命周期
JS环境中分配的内存,一般有如下生命周期:
内存分配
:当我们声明、函数、对象的时候,系统会自动为他们分配内存内存使用
:即读写内存,也就是使用变量、函数登内存回收
:使用完毕,由垃圾回收器
自动回收不再使用的内存说明
- 全局变量一般不会回收(关闭页面回收)
- 一般情况下
局部变量的值
,不再使用,会被自动回收
掉内存泄漏:程序中分配的
内存
由于某种原因程序未释放
或无法释放
叫做内存泄漏
。
原理
- 引用计数法
- 这是最初级的垃圾收集算法,此算法简化理解为“对象有没有其他对象引用到它”,如果没有引用指向该对象(零引用),对象将被垃圾回收机制回收
let o { name: 'eason'; } // 创建一个对象,被o变量所引用,技术为1 let o2 = o // o2 变量是第二个对该对象的引用,计数为2 o = null // 该对象的原始引用 o 已经没有了,技术为1 o2 - null // 此时对象时所有引用都没有了,计数为 0 垃圾回收机制会回收该对象
缺点:循环引用
- 标记清除法
- 从2012年起,所有现代浏览器都使用了标记-清楚垃圾回收机制
1.5 闭包
闭包是什么?
闭包就是函数的嵌套,内层函数访问了外层函数的局部变量。
闭包有什么用?
实现变量的私有化,让外面的人无法修改内部的变量。
闭包会产生什么问题或者问题?
- 消耗内存,使用不当会造成内容泄露
- 本该被释放的变量,无法得到及时释放,会存在闭包空间中
function outer() { let name = 'eason'; function inside() { return name; } return inside(); }
总结
- 用哪个关键字声明变量会有变量提升?
var
- 变量提升是什么流程?
- 先把var变量提升到当前作用域最前面
只提升变量声明
,不提升变量赋值- 然后依次执行代码
我们不建议使用var声明变量
函数进阶
1. 函数提升
函数提升与变量提升比较类似
// 调用函数 foo() // 声明函数 function foo() { console.log('声明之前被调用...'); }
// 不存在提升变量 bar() var bar = function(){ console.log('函数表达式不存在提升现象...'); }
说明:
- 函数提升提升到
当前作用于最前面
- 函数提升
之提升声明,不提升调用
- 函数表达式
不存在提升
的现象- 函数提升能够使用函数的声明调用更灵活
arguments
arguments
就是一个伪数组,仅存在于function中,它的作用是装载着所有的实参,因为arguments
是动态伪数组,数据变化带来的性能消耗较大,每次都会同步更新所有的参数,如果参数过多则会出现问题,在开启严格模式后就会彻底禁用。现在开发中
基本不用arguments
了,可以使用可变参数完成代替
function fn() { console.log(arguments); } fn(1,2,3,4,5,6) /* Arguments(6) [1, 2, 3, 4, 5, 6, callee: ƒ, Symbol(Symbol.iterator): ƒ] 0: 1 1: 2 2: 3 3: 4 4: 5 5: 6 callee: ƒ fn() length: 6 Symbol(Symbol.iterator): ƒ values() [[Prototype]]: Object */
2. 函数参数
剩余参数
:允许我们将一个补丁数量的参数
表示为一个数组
简单理解:
用于获取多余的参数,并形成一个真数组
function sum(...other) { // 可以得到[1, 2, 3, 4] console.log(other) } sum(1, 2, 3, 4)
3. 展开运算符
**剩余参数:**函数参数使用,把多个元素
收集
起来生成
一个真数组
(凝聚)**展开运算符:**将数组展开成各个元素(
拆散
)const arr = [1, 5, 3, 8, 2]; console.log(...arr)// 1 5 3 8 2
4.箭头函数
- 当箭头函数
只有一个参数时
,可以省略
参数的小括号
,其余个数不能省略(没有参数也需要写小括号)
- 当箭头函数的函数体只有
一句代码
时,可以省略函数体的大括号
,这句代码就是返回值(可以不用写return
)- 如果
返回
的是个对象
,则需要把对象用小括号包裹
- 箭头函数里面
没有arguments
,但是有剩余参数
总结
- 箭头函数里面有
this
吗?
- 箭头函数本身
没有this
,它只会沿用上一层作用域的this
- 我们如何选择用不用箭头函数呢?
根据需求来选择是否需要
- dom元素注册事件,需要用this,就不用箭头函数
- 定时器里面需要用到this,就需要箭头函数
5.结构赋值
**解构赋值:**可以将
数组
中的值
或者对象
的属性
取出,赋值
给其他变量
**解构:**其实就是把一个事物的
结构
进行拆解
const obj = { name: 'eason', age: 21, address: '广州' } const arr = [1, 2, 3, 4, 5, 6] const {name, age, address} = obj const [a, b, c, d, e, f] = arr; console.log(name, age, address) // 'eason', 21, '广州' console.log(a, b, c, d, e, f) // 1, 2, 3, 4, 5, 6
6. 解构
**数组解构:**变量和值不匹配的情况
变量多值少的情况:
const [a, b, c, d] = ['小米', '苹果', '华为'] console.log(a); // 小米 console.log(b); // 苹果 console.log(c); // 华为 console.log(d); // undefined
防止 undefined 传值,可以设置默认值
let [a = '大亚亚', b, c, d = '小亚亚'] = ['龙志豪', '陈厚霖', '张泽文'] console.log(a); console.log(b); console.log(c); console.log(d);
利用剩余参数解决变量少值多的情况
let [a, ...b] = ['龙志豪', '陈厚霖', '张泽文'] console.log(a); console.log(b);
多种数组情况
// 一维数组 let arr1 = [1, 2, 3]; // 二维数组 let arr2 = [1, 2, 3, [4, 5, 6]] let [a, b, c, [d, e, f]] = arr2 console.log(a); console.log(b); console.log(c); console.log(d); console.log(e); console.log(f);
对象解构
可以从一个对象中提取变量并同时修改新的变量名
格式:变量名:新变量名
// 普通对象 const user = { name: '小明', age: 18 } // 把原来的name 变量重命名为 uname const {name: uname, age} = user console.log(uname); // 小明 console.log(age) // 18 // 多级对象解构 const pig = { name: '佩奇', family: { mother: '猪妈妈', father: '猪爸爸', sister: '乔治' }, age: 6 } let {name, age, family: {mother, father, sisiter}} = pig console.log(name, age. mother, father, sister);
7. 构造函数
构造函数:
一种特殊的函数,专门用于帮助程序员创建对象的
本质:
构造函数本质就是普通函数,只有在使用new
关键字调用它的时候,就被称为构造函数,定义构造函数和普通函数没有任何区别,只有在调用时才能决定它是否为构造函数、
特点:
- 调用构造函数会自动创建一个新对象
- 可以给当前创建的新对象添加属性
- 默认返回这个新对象,不需要写return
function Person(name, age, address) { this.name = name; this.age = age; this.address = address; } const person = new Person("eason", 21, "广州")
new实例化执行过程
- 创建新
空对象
- 构造函数
this
指向新对象- 执行构造函数代码
返回
新对象
数组进阶
1. filter
- 语法:array.filter(callback(element, index, array), thisValue);
- 其中,callback为回调函数,它有三个参数:element表示数组中当前正在遍历的元素,index表示当前元素在数组中的索引,array表示原数组。thisValue为可选的参数,它表示在回调函数中使用的this指向。
不会影响原数组
,会返回一个符合判断条件的新数组
const arr = [1, 2, 3, 4, 5, 6, 7, 8];
const newArr = arr.filter((item, index, arr) => {
if(item >= 3) {
return true;
} else {
return false;
}
console.log(index); // 0-7
})
console.log(newArr) "[3,4,5,6,7,8]"
2. reduce
- 语法:array.reduce(callback(accumulator, currentValue, currentIndex, arr), initialValue)
- callback为回调函数,它有四个参数:accumulator表示累加器,即上一次迭代后的累加结果;currentValue表示当前正在迭代的数组元素;currentIndex表示当前元素在数组中的索引;array表示原数组。initialValue为可选的参数,它表示累加器的初始值。
- 应用场景:
用于通过对数组元素的累加、累乘、字符串连接等操作,将数组元素转换为单个值。
注意:
若第二个参数没有设置,则reduce函数会自动的以数组中的第一个参数为初始值,且只遍历arr.length - 1次。
const arr = [1, 2, 3, 4, 5];
const sum = arr.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
console.log(sum); // 15e
3. ervey
在JavaScript中,
every()
方法是用于数组的高阶函数之一。他接受一个回调函数作为参数,并对数组的每个元素调用该方法,返回一个布尔值。如果该数组的每个元素对应的对调函数都返回true
,则every()
方法返回true
,否则返回false
。例子:
const arr = [6, 7, 8, 9]; const result = arr.every(item => item > 5); console.log(result); // true
4. some
在JavaScript中,
some()
方法也是数组的高阶函数之一,他接受一个回调函数作为参数,并对数组的每个元素调用该函数,返回一个布尔值。如果该数组的任一元素的对应的回调函数返回true
,则some()
方法返回true
,否则返回false
。const arr = [5, 9, 12, 15]; consta result = arr.some(item => item > 10); console.log(result); // true
5. findIdenx
在JavaScript中,
findIndex()
方法是用于数组的高阶函数之一,他接受一个回调函数作为参数,并对数组的每个元素调用该函数,如果该函数返回true
,则findIndex()
方法返回该元素在数组中的索引,否则返回-1
。const arr = [5, 9, 12, 15]; const result = arr.findIndex(item => item > 10); console.log(index) // 2
构造函数
1. 编程思想
面向过程编程
面向过程
就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步步
实现,使用的时候再一个一个
的一次调用就可以了。面向过程,就是按照我们分析好了的步骤,按照步骤解决问题。
2. 构造函数
- 封装是面向对象思想中比较重要的一部分,js面向对象可以通过
构造函数
实现的封装- 把公共的属性和方法
抽取封装到
构造函数里面来实现数据的共享
,这样创建的实例对象可以使用这些属性和方法了
构造函数:
一种特殊的函数,专门用于帮助程序员创建对象的
本质:
构造函数本质就是普通函数,只有在使用new
关键字调用它的时候,就被称为构造函数,定义构造函数和普通函数没有任何区别,只有在调用时才能决定它是否为构造函数、
特点:
- 调用构造函数会自动创建一个新对象
- 可以给当前创建的新对象添加属性
- 默认返回这个新对象,不需要写return
function Person(name, age, address) { this.name = name; this.age = age; this.address = address; } const person = new Person("eason", 21, "广州")
new实例化执行过程
- 创建新
空对象
- 构造函数
this
指向新对象- 执行构造函数代码
返回
新对象
3. 原型对象
问题引出:
前面我们学过的构造函数方法很好用,但是
存在浪费内存的问题
// 构造函数 function Person(name, age) { this.name = name; this.age = age; this.sayHi = function() { console.log("hi!!"); } } // 实例对象使用属性和方法 const person1 = new Person("1", 21); const person2 = new Person("2", 21);
原因:
sayHi是作为Person的一个构造参数,每次使用new
一个实例对象都会在堆内存
中开辟一个空间用来存储。
那我们是否有方法让它只创建一个呢?
实例对象
可以直接访问
原型对象中函数
执行过程:先找
实例对象属性或者函数
,找不到会再找原型对象中属性或函数
(有点像作用域查找机制
)
- 原型对象是什么?
prototype
,每个构造函数都有原型对象
- 原型对象的作用是什么?
- 可以把公共的属性和方法,直接定义在
prototype
对象上实例对象
可直接访问
原型对象中属性和方法- 这些属性和方法
不会
多次在内存中创建,从而节约内存
4. this指向
构造函数和原型对象中的
this
都指向实例化对象
// 构造函数this指向、实例对象 function Person(name, age) { this.name = name; } // 原型对象this指向 实例对象 Person.prototype.sayHi = function () { console.log("hi~"); console.log(this); // 指向实例对象 person } const person = new Person("1", 21) person.sayHi();
5. 原型
对象都会有一个属性
__proto__指向
构造函数的prototype原型对象
之所以我们对象可以使用构造函数
prototype原型对象
的方法,就是因为对象有__proto__原型
的存在
- prototype是什么?哪里来的?
原型对象
构造函数
都自动有原型对象- __proto__属性是什么?在哪里?指向谁?
原型
- 在实例对象中
- 指向
prototype原型对象
,这样实例对象
就可以访问原型对象
里面的方法
6. 原型链
__proto__
属性链状结构成为原型链
**作用:**原型链为
对象成员
查找机制提供一个方向,或者说一条路线
查找规则
- 当访问一个对象成员(属性/方法)时,首先查找这个
对象自身
有没有该成员(属性/方法)- 如果没有就查找它的原型对象(也就是__proto__指向的
prototype原型对象
)- 如果还没有就查找原型对象的原型对象(
Object
的原型对象)- 以此类推一直找到Object为止(
null
)原型链
就在于为对象成员
查找机制提供一个方向,或者说一条路线
7. instanceof
语法:
实例对象
instanceof
构造函数
作用:
用来检测构造函数.prototype
是否存在于实例对象
的原型链
上
8. 原型继承(组合式继承)
继承
是面向对象编程的另一个特征。龙生龙、凤生凤、老鼠的儿子会打动描述的正是继承
的含义,有些公共的属性和方法
可以写到父级
身上,子级
通过继承
也可以使用这些属性和方法。JavaScript中大多数是借助原型对象
实现继承
的特征function Person() { this.eyes = 2; } Person.prototype.eat = function() { console.log("我可以吃饭..."); } // 继承 function Man() { } Man.prototype = new Person(); Man.prototype.constructor = Man; const m1 = new Man();
原型继承总结:
- 创建父级构造函数
- 将所有公共的方法放到父级的原型对象上
- 将子级构造函数的原型对象 指向 父级构造函 创建的实例对象
throw 抛异常
异常处理是指预估代码执行过程中
可能发生的错误
,然后最大程度的避免错误的发生导致整个程序无法继续运行function counter(x, y) { if(!x || !y) { throw new Error('参数不能为空!'); } return x + y; } counter();
总结:
- throw抛出异常信息,程序会
终止执行
- throw后面跟的是
错误
提示信息Error
对象配合throw使用,能够设置更详细的错误信息
try/catch
我们想要测试某些代码是否异常,可以通过
try/catch
捕获错误信息(浏览器提供的错误信息)try
试试catch
拦住finally
最后try { const p = document.querySelector('.p'); p.style.color = 'red'; } catch(error) { console.log(error); } finally { console.log('不管有没有错误都执行') }
说明:
- 将预估可能发生错误的代码写在
try
代码中- 如果try代码段中出现错误后,会执行
catch
代码段,并截获到错误信息finally
不管是否有错误,都会执行