JS 进阶

JS进阶

一、作用域

作用域规定了变量可以被访问的范围。

1.1 局部作用域

函数作用域:在函数内部声明的变量只能在函数内部被访问,外部无法访问。

块作用域:在JS中使用{}包裹的代码称为代码块,代码块内部声明的变量有可能无法被访问。

  • let声明的变量会产生块作用域,var不会产生块作用域
  • const声明的变量也会产生块作用域
  • 不同代码块之间的变量无法相互访问
1.2 全局作用域
  • <script>标签内部,函数作用域和块作用域之外的区域

  • .js文件中

1.3 作用域链-------------面试

作用域链的本质是底层的变量查找机制

过程:

  1. 在函数被执行时,会优先在当前函数作用域查找变量
  2. 若当前作用域中找不到,则会依次逐级查找父作用域直到全局作用域

总结:

  • 嵌套关系的作用域串联起来形成了作用域链

  • 相同作用域链中按着从小到大的规则查找变量

  • 子作用域能够访问父级作用域,父级作用域无法访问子级作用域

1.4 垃圾回收机制------面试

内存的生命周期:

  • 内存分配: 当声明变量、函数、对象的时候,系统会自动为他们分配内存,

  • 内存使用: 读写内存,也就是使用变量、函数等,

  • 内存回收: 使用完毕,由垃圾回收器回收不再使用的内存,

notice:

  1. 全局变量一般不会回收(页面关闭时才回收)
  2. 一般情况下局部变量的值不使用了,会被自动回收
  3. 内存泄漏: 程序中分配的内存由于某种原因程序未释放或无法释放

垃圾回收机制算法:

  • 引用计数法: 看一个对象是否有指向它的引用,若没有引用就释放对象
    • 跟踪记录被引用的次数
    • 若被引用一次,那么记录数就加一,多次引用会累加
    • 若减少一个引用就减一
    • 如果引用次数为0,则释放内存

引用计数法的问题:嵌套引用时,不再使用也无法回收内存,导致内存泄漏

function fn(){
  let obj1 = {}
  let obj2 = {}
  obj1.a = obj2
  obj2.a = obj1
  return '引用计数无法回收'
}
fn()
  • 标记清除法
    • 标记清除法将“不再使用的对象”定义为“无法到达的对象”;
    • 根部(在JS中就是全局对象)出发定时扫描内存中的对象,凡是能从根部到达的对象,都是还需要使用的;
    • 那些无法由根部出发触及到的对象被标记为不再使用,稍后回收。
1.5 闭包-----------面试

闭包: 内层函数+外层函数的变量

作用:

  • 封闭数据,实现数据私有

  • 外部也可以访问函数内部的变量(只能访问,不能修改)

  • 允许将函数与其所操作的某些数据关联起来

问题:可能会引起内存泄漏

// 闭包示例
function counter(){
            let i = 0
            function fn(){
                i ++
                console.log(`函数被调用了${i}`);
            }
            return fn
        }
        let func = counter()
        func()
1.6 变量提升

流程: 先把var声明的变量提升到当前作用域的最前面(只提升声明,不提升赋值),然后依次执行代码

notice:

  • 变量在未声明前被访问时会报语法错误
  • 变量在var声明之前即被访问,变量值为undefined
  • let/const声明的变量不存在变量提升
  • 变量提升出现在相同作用域中

二、函数进阶

2.1 函数提升

会将所有的函数声明提升到当前作用域的最前面,不提升函数调用。即以下代码可以执行。

fun()
function fun(){
  console.log('函数提升')
}

notice: 函数表达式必须先声明和赋值后调用,否则会报错。

2.2 函数参数

动态参数:arguments是函数内部内置的伪数组变量,包含了调用函数时传入的所有实参

  • arguments是一个只存在于函数中的伪数组

  • arguments用于动态获取函数的实参

  • 可以通过for循环得到传递过来的实参

剩余参数: 允许我们将不定量的参数表示为一个数组

  • …是语法符号,置于最末函数形参之后,用于获取多余的参数
  • 借助…获取的剩余实参,是个真数组
function getSum(){
   let sum = 0
   for(let i = 0; i < arguments.length;i ++){
      sum += arguments[i]
   }
   return sum
}
        console.log('====================================================================')
// 剩余参数
function getSum(a,b,...arr){
  let sum = a + b
  for(let i = 0; i < arr.length;i ++){
    sum += arr[i]
  }
  return sum
}

展开运算符:(…)将一个数组或对象展开,不修改原数组 arr = [1,2,3,4] …arr = 1,2,3,4

应用场景:求最大最小值,合并数组

<script>
   arr1 = [8,3,9,4]
   arr2 = [3,2,7,5]

    console.log(Math.max(...arr1))   // 求数组最大值
    console.log(Math.min(...arr1))   // 求数组最小值
    console.log([...arr1,...arr2])   // 合并数组
</script>
2.3 箭头函数

目的:实现更简短函数写法且不绑定this,箭头函数的语法比函数表达式更简洁

使用场景:用于本来需要匿名函数的地方

语法:

箭头函数属于表达式,因此不存在函数提升。

// 箭头函数基本语法
const fn = () =>{
  console.log('这是箭头函数')
}
fn()

箭头函数只有一个参数时,可以省略圆括号();只有一行代码可以省略return。

// 只有一行代码时,可以不用return
const fn4 = x => x + x
console.log(fn4(444))

箭头函数只有一行代码时可以省略花括号{},并自动作为返回值被返回。

// 箭头函数只有一行代码时,可以不写{}
const fn3 = x => console.log(x)
fn3(333)

箭头函数中加括号的函数体返回对象字面量表达式。

// 箭头函数可以直接返回一个对象
const re_obj = (uname,age) => ({uname:uname,age:age})
console.log(re_obj('WXR',24))

**箭头函数参数:**没有动态参数arguments,但是有剩余参数…arr

剪头函数的this:箭头函数不会创建自己的this,它只会从自己的作用域链的上一层沿用this

 <script>
        const fn = () => console.log(this)                 // this指向window
        fn()

        const obj1 = {
            uname : 'WRX',
            fun1 : () => console.log(this)                // this指向window,函数内才有this
        }
        obj1.fun1()

        const obj2 = {
            uname : 'WRX',
            fun1 : function(){
                const fun2 = () => console.log(this)     // this指向obj
                fun2()
            }                  
        }
        obj2.fun1()
    </script>

三、解构赋值

3.1 数组解构

数组解构是将数组元素快速批量赋值给一系类变量的简洁语法。

基本语法:

  • 赋值运算符=左侧的[]用于批量声明变量,右侧数组的元素将被依次赋值给左侧的变量。

  • 变量的顺序对应数组元素的位置依次进行赋值。

JS中前面必须加分号的情况:(1)立即执行函数 (2)数组解构

// 数组解构的相关知识点
<script>
        // 基本语法
        const [max,min,avg] = [100,60,80]
        console.log(max,min,avg)

        // 应用,交换两个变量
        let a = 1
        let b = 100;
        [b,a] = [a,b]
        console.log(a,b);

        // 变量值少,单元值多
        const [c,d,e,f] = [1,2,3]  // d是undefined

        // 变量多,单元值少
        const [g,h,...arr] = [1,2,3,4]  // arr是数组[3,4]  

        // 可以赋初值,防止undefined
        const [i=1,j=2] = [100,80]

        // 支持多维数组解构
        const [x,y,[aa,bb]] = [1,2,[11,22]]
        console.log(x,y,aa,bb)
    </script>
3.2 对象解构

对象解构是将对象属性和方法快速批量赋值给一系类变量的语法。

基本语法:

  • 赋值运算符=左侧{}用于批量声明变量,右侧对象的属性将被赋值给左侧变量。
  • 对象的属性值将被赋值给与属性名相同的变量
  • 解构的变量名不能与外面的变量名相同,否则会报错。
  • 对象中找不到与变量名一致的属性时变量值为undefined。
 <script>
        // 对象解构,基本语法
        const {uname,uage} = {uname:'wxr',uage:24}
        console.log(uname,uage)

        // 对象解构,修改变量名
        const {uname:name,uage:age} = {uname:'WXR',uage:23}
        console.log(name,age)

        // 数组对象解构
        const [{name1,age1}] = [{name1:'WYX',age1:22}]
        console.log(name1,age1)
        
        // 对象的多级解构
        const pig1 = {
            name3 : '佩奇',
            family : {
                mother : '猪妈妈',
                father : '猪爸爸',
                sister : '乔治'
            }
        }
        const {name3,family:{mother,father,sister}} = pig1
        console.log(name3,mother,father,sister)
    </script>
3.3 forEach()和filter()

forEach()作用: 遍历数组的每个元素(不返回数组,也不需要return,map会返回数组)

语法:

被遍历的数组.forEach(function(item,index){
  // 函数体
})

filter()作用: 按条件选取数组中的值。

语法:

let arr = [1,2,4,5,4,8,7,6]
let arr1 = arr.filter(function(item){     // arr1为[5,8,7,8]
return item >= 5
})

四、深入对象

4.1 创建对象的三种方式
  • 对象字面量创建对象:const obj = { }
  • 利用new Object创建对象:const obj = new Object()
  • 利用构造函数创建对象
4.2 构造函数

是一种特殊的函数,主要用来初始化对象

构造函数两个约定:

  1. 命名以大写字母开头。
  2. 只能由’new’操作符来执行。

说明:

  • 使用new关键字调用函数被称为实例化
  • 构造函数内部无需写return(即使写了return,返回的值也无效),返回值即为新创建的对象
  • new Object() new Date() 也是实例化构造函数
  • 存在内存浪费问题,由于某些对象的方法是相同的,但是在实例化时会给每个实例对象的该方法都开辟内存
4.3 new实例化过程-------面试
  1. 创建新的空对象
  2. 构造函数的this指向新对象
  3. 执行构造函数代码,修改this,添加新属性
  4. 返回新对象
4.4 实例成员和静态成员

实例成员:

通过构造函数创建的对象称为实例对象,实例对象中的属性和方法称为实例成员(实例属性和实例方法)

说明:

  • 为构造函数传入参数,创建的是结构相同但值不同的对象
  • 构造函数创建的实例对象彼此独立互不影响
<script>
        function Person(name,age){
            Person.number = 123           // 静态成员
            Person.type = 'people'        // 静态成员
            Person.info = () =>{          // 静态方法
                console.log(Person.number,Person.type)
            }
            this.name = name
            this.age = age
        }

        const son = new Person('WXR',24)
        son.job = 'IT'
        son.info = () => {                          // 实例方法
            console.log(son.name,son.age,son.job)   // 三个实例成员
        }
        son.info() 

        console.log(Person.number,Person.type)
        Person.info()
</script>

静态成员:

构造函数的属性和方法被称为静态成员(静态属性和静态方法)

说明:

  • 静态成员只能构造函数访问
  • 静态方法中的this指向构造函数
  • Date.now()、Math.PI、Math.random()

五、内置构造函数

JS中基本数据类型:字符串、数值、布尔、undefined、null

JS中字符串、数值、布尔等基本类型也都有专门的构造函数,这些称为包装类型

JS中的对象(Object)、数组(Array)、正则(RegExp)、时间(Date)称为引用类型

5.1 Object

三个常用的静态方法

方法解释
Object.keys(对象名)获取对象中的所有属性名
Object.values(对象名)获取对象中的所有属性值
Object.assign(对象名1,对象名2)将对象2拷贝到对象1中
<script>
        const Person = {name : 'WXR',age : 24}

        // 获取Person的所有属性名
        console.log(Object.keys(Person))

        // 获取Person的所有属性值
        console.log(Object.values(Person))

        // 给Person添加一个属性gender
        Object.assign(Person,{gender:'男'})
        console.log(Person)
</script>
5.2 Array

数组常见实例方法

方法作用说明
forEach遍历数组不返回数组,常用于查找遍历数组元素
filter过滤数组返回新数组,返回的是满足筛选条件的数组元素
map迭代数组返回新数组,返回的是处理之后的数组元素
reduce累计器返回累计处理的结果。常用语数组求和
<script>
  			// reduce方法实现数组累加
        let arr = [1, 7, 2, 5, 3]
       let sum = arr.reduce((prev,current) => prev + current , 12)
        console.log(sum)
</script>
  • 实例方法join(): 将数组元素拼接为字符串,然后返回字符串
  • 实例方法find():查找元素,返回符合条件的第一个数组元素值,如果没有符合条件的则返回undefined
  • 实例方法every():检测数组中所有元素是否都符合指定条件,若所有元素均满足返回true,否则返回false
  • 实例方法some():检测数组中是否有元素满足指定条件,若数组中有元素满足条件返回true,否则返回false
  • 实例方法concat(): 合并两个数组,返回生成的新数组
  • 实例方法sort(): 对原数组的元素值排序
  • 实例方法splice(): 删除或替换原数组元素值
  • 实例方法reverse(): 反转数组
  • 实例方法findIndex(): 查找元素的索引值
  • 静态方法:伪数组转换为真数组 Array.from(伪数组名)
5.3 String
  • 实例属性length: 用来获取字符串的长度
  • 实例方法split(‘分隔符’): 用来将字符串拆分成数组--------和array.join()相反
  • **实例方法substring(需要截取的第一个字符索引,[结束的索引号]):**用于字符串截取
  • 实例方法startsWith(检测的字符串,[检测位置索引号]): 检测是否以某字符串开头
  • 实例方法includes(搜索的字符串,[检测位置索引号]): 判断一个字符串是否包含在另一个字符串中,根据情况返回true或者false
  • 实例方法toUpperCase(): 用于将字母转换成大写
  • 实例方法toLowerCase(): 用于将字母转换成小写
  • 实例方法indexOf(): 检测是否包含某字符
  • 实例方法endsWith(): 检测是否以某字符串结尾
  • 实例方法replace(): 用于替换字符串,支持正则匹配
  • 实例方法match(): 用于查找字符串,支持正则匹配
5.4 Number
  • 实例方法toFixed(数字): 保留多少位小数

六、深入面向对象

6.1 两种编程思想

面向过程: 分析出解决问题需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再调用就可以了。

面向对象: 是把问题分解为一个个对象,然后由对象之间分工与合作完成。

面向对象的特性:封装、继承、多态

6.2 构造函数

补充: 公共的属性写到构造函数里,公共的方法写到原型对象里

6.2 原型对象(构造函数名.prototype)

作用: 共享方法

  • 构造函数通过原型分配的函数是所有实例对象所共享的
  • JS中,每一个构造函数都会有一个prototype属性,指向另一个对象,所以我们也称为原型对象
  • 原型对象可以挂载函数,使用构造函数创建对象时不会多次创建原型上的函数,节约内存
  • 构造函数和原型对象中的this都指向实例化的对象
6.3 constructor

作用: 指向该原型对象的构造函数

<script>
        function Person(){

        }
        console.log(Person.prototype)
        console.log(Person.prototype.constructor === Person)

        Person.prototype = {
            constructor : Person,                                                // 指回原来的对象
            sing : function() {console.log('唱歌');},
            dance : function() {console.log('跳舞');}
        }

        console.log(Person.prototype);
</script>
6.4 对象原型

语法:实例对象._proto_

  • [[prototype]]和__proto__意义相同
  • __proto__是只读属性,不能赋值
  • 用来表明当前实例对象指向哪个原型对象prototype
  • 对象原型(_proto_)指向原型对象prototype

构造函数、实例对象、原型对象三者之间的指向关系
在这里插入图片描述

6.5 原型继承

原型继承格式: Boy.prototype = new Person()

<script>
        function Person(){
            this.eyes = 2
            this.hands = 2
            this.mouth = 1
        }
        function Boy(){}
        function Girl(){}

        Boy.prototype = new Person()     // 原型继承的格式
        Boy.prototype.constructor = Boy  // 指回原实例对象

        Girl.prototype = new Person()
        Girl.prototype.constructor = Girl

        Girl.prototype.hair = function (){console.log('通常是长头发');}      // 给一个实例对象添加方法不影响其他实例对象

        const ldh = new Boy()         // 实例化
        const ym = new Girl()

        console.log(ldh)
        console.log(ym)
</script>
6.6 原型链
  • 只要是原型对象就有constructor
  • 只要是对象,就有 _proto_
    在这里插入图片描述
    原型链是一种查找规则,提供了一条查找属性和方法的路径,先从自身的原型开始查找,若没有找到,则去上一层查找,直到对象为空为止。

instanceof 运算符用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上。

七、拷贝

拷贝只针对引用类型**,浅拷贝地址的拷贝,深拷贝对象的拷贝。

7.1 浅拷贝
  • 拷贝对象:Object.assign() / 展开运算符 {…obj}
  • 拷贝数组:Array.prototype.concat() / 展开运算符 […arr]
<script>
        const person = {
            gender : '男',
            year : 24,
        }
        const person1 = person    // 直接赋值是将person的地址复制给person1,改变person1的属性值,person的属性值也会一起变化
        console.log(person1 === person)        // true

        // 两种浅拷贝方法
        const person2 = {...person}
        console.log(person2 === person)        // false

        const person3 = {}
        Object.assign(person3, person)
        console.log(person3 === person)        // false
</script>

浅拷贝总结:拷贝对象之后,若里面的属性值是简单数据类型则直接拷贝值;若属性值是引用数据类型则拷贝地址。

7.2 深拷贝

深拷贝目的: 使拷贝得到的对象不会影响原始的对象。

1.递归实现

<script>
        const person = {
            uname : 'ZGR',
            age : 24,
            hobby : ['sing','dance'],
            song : {
                first : '千千阙歌',
                second : '沉默是金'
            }
        }

        const per = {}
        function deepCopy(newObj, oldObj){
            for(let k in oldObj){
                if (oldObj[k] instanceof Array){    //先数组,再对象
                    newObj[k] = []
                    deepCopy(newObj[k], oldObj[k])
                }
                else if(oldObj[k] instanceof Object){
                    newObj[k] = {}
                    deepCopy(newObj[k], oldObj[k])
                }
                else{
                    newObj[k] = oldObj[k]
                }
            }
        }
        
        deepCopy(per,person)
        per.hobby[0] = 'act'
        console.log(per)
        console.log(person)
</script>

函数递归步骤: 1、普通值拷贝直接赋值 2、如果遇到数组,再次调用递归函数 3、若遇到对象,再次调用递归函数 4、先处理数组,再处理对象。

2.利用js库ladash里面的_.cloneDeep()

3.利用JSON字符串转换(有函数时,用递归)

<script src="./lodash.min.js"></script>
    <script>
        const person = {
            uname : 'ZGR',
            age : 24,
            hobby : ['sing','act'],
            song : {
                first : '千千阙歌',
                second : '沉默是金'
            }
        }
        const per1 = _.cloneDeep(person)                  // 方式二
        const per2 = JSON.parse(JSON.stringify(person))   // 方式三

        console.log(per1)
        console.log(per2)
</script>

八、异常处理

8.1 throw抛异常
  • throw抛出异常,程序也会终止运行
  • throw后面跟的是错误提示信息
  • Error对象配合throw使用,能够设置更详细的错误信息
<script>
        function getSum(x,y){
            if(!x || !y){
                throw new Error('参数不能为空')
            }
            return x + y
        }
        getSum()
</script>
8.2 try/catch捕获异常
  • try…catch用于捕获错误信息
  • 将预估可能发生错误的代码写在try代码段中
  • 若try代码段中出现错误,则会执行catch代码段,并截获到错误信息
  • finally不管是否有错误,都会执行
<script>
   function try_catch(){
     try{
        const tag = document.querySelector('.div')
        tag.style.color = 'red'        
      }
      catch(err){
          console.log(err.message)
       }
       finally{
         	alert('程序执行完毕')
       }
        }
       try_catch()
</script>
8.3 debugger

写在JS程序中,在调试时不用另外打断点即可调试。

九、处理this

9.1 this指向

普通函数中,谁调用函数this就指向谁;严格模式下,指向undefined;

箭头函数内不存在this。它的this是沿用上一层函数的。过程:向外层作用域中一层一层查找this,直到找到this的定义为止。

  • 箭头函数不适用于构造函数、原型函数、字面量对象中的函数、DOM事件函数
  • 箭头函数适用于需要使用上层this的地方
9.2 改变this指向

call():调用函数,同时指定被调用函数的指向(语法:fn.call(this指向,参数1,参数2,…))

apply():调用函数,同时指定被调用函数的指向(语法:fn.apply(this指向,[参数1,参数2,…])) 用数组传递参数(可用于求数组最大最小值)

bind():不调用函数,只改变this指向(语法:fn.bind(this指向,参数1,参数2,…))

总结:

  • call()、apply()都会调用函数,返回值为函数执行结果,call()传递参数与普通函数传参一样,apply()传递的参数要放在数组里。
  • bind()不会调用函数,返回值为改变this指向的原函数。

十、性能优化

10.1 防抖–debounce

防抖: 单位时间内,频繁触发事件,只执行最后一次

实现方法:(1) lodash提供的防抖函数(_.debounce(fn, 延迟时间)) (2) 手写防抖函数

body>
    <div class="box"></div>
    <script src="./lodash.min.js"></script>
    <script>
        const box = document.querySelector('.box')

        let i = 0
        function mouseMove(){
            box.innerHTML = i ++
        }
        // box.addEventListener('mousemove', _.debounce(mouseMove, 500))    // 方式一:调用lodash库中的防抖函数

        function debounce(fn, t){                                           // 方式二:用定时器实现
            let timer
            return function (){
                if(timer) clearTimeout(timer)
                timer = setTimeout(fn, t)
            }
        }
        box.addEventListener('mousemove', debounce(mouseMove, 500))
    </script>
</body>

核心思路: 用定时器(setTimeout)实现

  1. 声明一个定时器
  2. 当有事件触发时,先判断是否已经有定时器,如果有定时器,则先清除
  3. 如果没有定时器则开启定时器,将其存到变量里
  4. 定时器里调用要执行的函数
10.2 节流–throttle

节流: 单位时间内,频繁触发事件,只执行一次

实现方式:(1) lodash提供的节流函数(_.throttle(fn, 间隔时间)) (2) 手写节流函数

<body>
    <div class="box"></div>
    <script src="./lodash.min.js"></script>
    <script>
        const box = document.querySelector('.box')

        let i = 0
        function mouseMove(){
            box.innerHTML = i ++
        }
        // box.addEventListener('mousemove', _.throttle(mouseMove, 3000))   // 方式一:调用lodash库中的节流函数
        function throttle(fn, t){                                           // 方式二:使用延时函数实现
            let timer = null
            return function (){
                if(!timer){
                    timer = setTimeout(function(){
                        fn()
                        timer = null                             // 此处用这种方式清除定时器计时器,setTimeout内部不能使用clearTimeout清除
                    }, t)
                    
                }
            }
        }
        box.addEventListener('mousemove', throttle(mouseMove, 3000))
    </script>

核心思路: 用定时器(setTimeout)实现

  1. 声明一个定时器

  2. 当事件触发时都先判断是否有定时器了,如果有新的定时器则不开启新的定时器

  3. 如果没有定时器则开启定时器,将其存放到变量里

    • 定时器里调用要执行的函数

    • 执行完后要把定时器清空

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值