js高级

41 阅读9分钟

1. 作用域分为局部作用域和全局作用域, 局部作用域分为 函数作用域 和 块作用域

1.1 函数作用域 在函数内部声明的变量只能在函数内部使用

'script'标签和 .js文件的最外层 就是所谓的全局作用域,在此声明的变量在函数内部也可以访问

(1).给window对象动态添加的属性默认也是全局的,不推荐

(2).函数中未使用任何关键字声明的变量为全局变量,不推荐

(3).尽可能减少声明全局变量,防止全局变量被污染

1.2 块作用域 在js中使用{}包裹的代码块,代码块内部声明的变量外部将有可能无法被访问

for(var i=0;i<3;i++){
            console.log(i)
        }
        console.log(i)  //let声明i会产生块级作用域  i is not defined
        console.log(i)  //var声明i不会    3
        if(true){
            let a=10
        }
        // console.log(a)  //a is not defined 
        // const声明的常量也会产生块作用域
        // 不用代码块之间的变量无法相互访问
        // 推荐使用let 或 const
        console.log("---------------")
        let arr=[]
        for(var i=0;i<2;i++){
            arr[i]= ()=>{
                console.log(i)
            }
        }
        // var声明
        arr[0]()  // 2        arr[1]()  // 2
        // let声明
        arr[0]()  // 0        arr[1]()  // 1

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

在函数执行时,会优先从当前函数作用域中查找变量,

如果当前作用域中查找不到,会依次逐级查找父级作用域直到全局作用域

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

3 什么是垃圾回收机制?

答: js中内存的分配和回收都是自动完成的,内存不使用的时候会被垃圾回收器自动回收

如果不了解js的内存管理机制,我们同样非常容易内存泄漏

3.1 什么是内存泄漏?

不再用到的内存,没有及时释放,就叫做内存泄漏

3.2 内存生命周期是什么样的? 内存分配,内存使用,内存回收

  • 1.声明变量\函数\对象时,系统会自动为他分配内存
  • 2.读写内存,也就是使用变量函数
  • 3.使用完毕,由垃圾回收自动回收不再使用的内存

全局变量一般不会自动回收(关闭页面回收)   可以手动释放 让值为空 null 

 一般情况下, 局部变量的值,不用了,会被自动回收掉

3.3 两种浏览器垃圾回收算法:引用计数法 和 标记清除法

核心思想 如何判断内存是否已经不再会使用 ,如果是就视为垃圾释放掉

  • (1) 引用计数法

//1.跟踪记录每个值被引用的次数

// 2.如果这个值被引用一次,那么就记录次数1

// 3.多次引用会累加

// 4.如果减少一个引用就减1

// 5.如果引用次数是0 则释放内存

// 但是他存在一个致命问题: 嵌套引用

// 如果两个对象相互使用,尽管他们已经不再使用,垃圾回收器不会进行回收,导致内存泄漏

  • (2) 标记清除法

//现代浏览器已经不在使用引用计数算法 大多是基于标记清除法的某些算法,总体思想都是一致的

//标记清除法将不再使用的对象定义为无法到达的对象

//从根部(在js中就是全局对象)出发定时扫描内存中的对象,凡是能从根部到达的对象,都是还需要使用的

//那些无法由根部出发触及到的对象标记为不再使用,稍后进行回收

4 什么是闭包?

1.概念:一个函数对周围状态的引用捆绑在一起,内层函数中访问到其外层函数的作用域

简单理解 : 闭包=内层函数 +外层函数的变量

2.闭包的作用: 封闭数据 ,提供操作,外部也可以访问函数内部的变量

3.闭包的应用: 统计函数调用的次数,  封闭数据,实现数据私有

4.闭包的缺点:  闭包可能会引起内存泄漏

//闭包的基本格式
 function outer(){
     let a=2
     function inner(){
            console.log(a)
     }
     return inner
 }
 outer()  //  调用函数outer()  函数返回值 inner函数  还没被调用
 const fn = outer()
 fn()  //调用函数 inner
//简约写法 function outer(){      let i = 3      return function(){          console.log(i)      } } const fun = outer()  fun() //外层函数使用内存函数的变量

5. 变量提升, 函数提升

  • ES6映入块级作用域,let /const 声明的变量不存在变量提升, 变量提升是js的缺陷 他允许在变量声明之前即被访问(仅存在于var声明变量)

  • 会把所有函数声明提升到当前作用域最前面,  只提升函数声明,不调用函数, 函数提升能够使函数的声明调用更灵活 , 函数表达式不存在提升的现象

    // 1.把所有var声明的变量提升到 当前作用域的最前面 // 2.只提升声明,不提升赋值操作
    // 3.然后一次执行代码
    // console.log(num+"件") //undefined件
    // var num =10 // 1.变量在未声明即被访问时会报语法错误 Uncaught ReferenceError: num is not defined// 2.变量在var 声明之前即被访问,变量的值为 underfined// 3.let /const 声明的变量不存在变量提升// 4.变量提升出现在相同的作用域中// 5.实际开发中推荐先声明再访问变量 // 调用函数fn()// 声明函数function fn(){ console.log('声明之前调用')}// 函数提升能够使函数的声明调用更灵活// 函数表达式不存在提升的现象// fun() //fun is not a function// var fun = function(){// console.log("函数表达式不存在提升现象")// }// 相当于执行以下代码var fun;console.log(fun) //undefinedfun()fun=function(){ console.log("函数表达式不存在提升现象")}

6. 动态参数, 剩余参数

开发中推荐使用剩余参数

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

动态参数 是一个(伪数组) ,只存在于函数里面   arguments 的作用是动态获取函数的实参,   可以通过for循环依次得到传递过来的实参

2. 剩余参数 ...other 写在形参最后面,用于获取多余的实参

...other 获取的剩余实参,是个真数组

1.剩余参数主要的使用场景?

用于获取多余的实参

2.剩余参数和动态参数区别是什么?

动态参数arguments 是伪数组

剩余参数是真数组,可以使用数组的方法

7. 箭头函数

目的: 引入箭头函数的目的是更简洁的函数写法并且不绑定this ,箭头函数比函数表达式更简洁

使用场景: 箭头函数适用于那些本来需要匿名函数的地方, 箭头函数属于表达式函数,因此不存在函数提升

箭头函数里面的this , 箭头函数不会创建自己的this ,他会从自己的作用域链的上一层沿用this 

8. 执行上下文

1. 全局执行上下文

在执行全局代码前将window确定为全局执行上下文

对全局数据进行预处理

var 定义的变量==> undefined ,添加为window的属性

function声明的全局函数==>赋值(fun),添加为window的属性

this==>赋值(window)

开始执行全局代码

2.函数执行上下文

在调用函数,准备执行函数体之前,创建对应的函数执行上下文对象

对局部函数进行预处理

形参变量==>赋值(实参)

arguments==>赋值(动态参数)

var定义局部变量==>undefined

function声明的函数==>赋值(fun)

this==>赋值(调用函数的对象)

*开始执行函数体代码

9. 数组解构, 对象解构?

1.数组解构作用

数组解构是将数组单元值快速批量赋值给一系列变量的简介语法

那两种情况需要加分号;  1.立即执行函数 2.数组解构,以数组开头的,使用数组的时候

2.对象解构的基本语法

赋值运算符= 左侧的{}用于批量声明变量,右侧对象的属性值将被赋值给左侧的变量

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

对象属性的值将被赋值给与属性名相同的变量

注意解构的变量名不要与外面的变量冲突,否则会报错

对象中找不到与变量名一致的属性时,变量值为undefined

10. 创建对象, 构造函数

1.new实例化执行过程

1.创建新的对象

2.构造函数this指向新的对象

3.执行构造函数里面的代码,添加新的属性

4.返回新的对象

2. 实例成员, 静态成员

        // 1.通过构造函数创建的对象称为实例对象,实例对象中的属性和方法称为实例成员
        function Dog(name,price){
            this.name = name;
            this.price = price;
        }
        const xm = new Dog("实例",12);
        // 实例成员:实例对象可以访问的就是实例成员
        // 为构造函数传入参数,动态创建结构相同但值不同的对象
        // 构造函数创建的实例对象彼此相互独立不影响
        // 2.构造函数里的属性和方法被称为静态成员
        // 在js中底层函数本质上也是对象类型
        // 一般公共特征的属性或方法设置为静态成员
        // 静态成员中的this指向构造函数本身
        // 静态属性
        Dog.dname = "构造"        // 静态方法
        Dog.sing= ()=> console.log(Dog.dname)
        Dog.sing()        
        console.log(xm.dname)  //undefined

3.内置构造函数

在js中内置了一些构造函数,绝大部分数据处理都是基于这些构造函数实现的

js基础阶段学习的Date就是内置函数的构造函数普通对象

字符串,数值,布尔 ,数组,也都有专门构造函数,用于创建对应类型的数据

4. 基本包装类型

// 在js中字符串,数值,布尔,具有对象的使用特征,如具有属性和方法

const str = "hello world";

console.log(str.length)

// 之所以具备对象的特征的原因,js底层使用了Object构造函数包装起来的,被称为包装类型

// 把简单数据类型包装为引用数据类型

const str1 = new String("pink")

5.object静态方法

const obj ={uname:"pink",age:12}

// 获得所有属性名

console.log(Object.keys(obj)) //返回数组

// 获得所有属性值

console.log(Object.values(obj)) //

// 3.对象拷贝

const newObj ={};

Object.assign(newObj,obj);

// 4.合并对象

let NEWObj = Object.assign({},obj,{name:"合并"})