JavaScript进阶 学习记录

前言

这篇笔记,是根据B站黑马的JS网课学习整理的,用来学习的

如果有错误,还请大佬指正

作用域

(再记)

概念:

        变量能够被访问使用的 “ 范围 ” 

分类:

  • 局部作用域
  • 全局作用域

局部作用域

局部作用域分为 函数作用域块作用域

函数作用域

在函数内部的作用域

函数内部声明的变量只能在函数内部被访问,外部无法直接访问

块作用域

代码块:用 { } 引起来的范围就是一个代码块

在代码块中形成的作用域,为块级作用域

不同代码块之间的变量无法互相访问

注:

  • let 和 const 声明的变量会产生块作用域
  • var 不会产生块作用域

全局作用域

 在<script>标签内和JS文件中,书写范围即全局作用域

 全局作用域中定义的变量称为全局变量

全局作用域中声明的变量,任何其它作用域都可以被访问

作用域链

本质:底层的变量查找机制

  1. 作用域是可以嵌套的
  2. 在嵌套的作用域中,当函数访问变量,会优先在当前作用域找到变量
  3. 若在当前作用域未找到变量,则会到上一级作用域寻找

这样的有嵌套关系的作用域串联成 作用域链

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

JS垃圾回收机制(了解)

概念

JS中内存的分配和回收都是自动完成的

内存在不使用的时候会被垃圾回收器自动回收

全局变量一般不会回收(关闭页面回收)

内存泄漏

不再用到的内存,无法被回收的情况 叫做 内存泄漏

内存的生命周期

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

JS垃圾回收机制算法说明(拓展)

引用计数法

  1. 记录一个对象的使用次数
  2. 如果引用次数是0 ,则释放内存

缺陷:

        如果两个对象相互引用,尽管已不再使用,垃圾回收器不会进行回收

        因为相互引用,引用次数不为0

        会造成内存泄露

标记清除法

  1. 从根部(在JS中就是全局对象)出发定时扫描内存中的对象。
  2. 凡是能从根部到达的对象,都是还需要使用的
  3. 无法由根部出发触及到的对象被标记为不再使用

闭包

概念:

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

人话:内层函数 + 外层函数的变量

示例:

        function out(){
            let i=0    //外部函数变量
            function inn(){
                //内部函数
                console.log(i)
            }
            inn()
        }
        out()

作用 与 应用

闭包会封闭数据,但也会提供操作,使外部也可以访问函数内部的变量

        function out() {
            let i = 0
            function inn() {
                console.log(i)
            }
            return inn
        }
        //外层函数也可以使用内部函数变量
        const outFun = out()
        outFun()

闭包也会实现数据的私有

对比:

        //未闭包代码
        let i = 0
        function out() {
            console.log(i);
        }

如上代码,其中 i 为全局变量,极其容易被修改

如果使用闭包, i 为局部变量,无法直接修改,实现数据的私有

问题:可能引起 内存泄漏

函数

函数参数

        函数参数有默认值、动态参数剩余参数

        动态参数剩余参数用于传入实参个数不确定的情况

动态参数

arguments 是函数内部内置的伪数组变量

储存了调用函数时传入的所有实参

示例:

        function test(){
            let sum=0
            for(let i=0;i<arguments.length;i++){
                sum+=arguments[i]
            }
            console.log(sum)
        }
        //可以输入多个参数
        test(1,2,3)
        test(1,2,3,4,5)

剩余参数

不太好形容 . . .

        function test(...arr){
            //...arr就是 剩余参数
            console.log(arr);
        }

剩余参数将一个不定数量的参数表示为一个数组

补充(. . . 语法符号)

        置于最末函数形参之前,用于获取多余的实参

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

        function test00(a, b, ...arr) {
            //...arr就是 剩余参数
            console.log(arr);
        }
        test00(1,2,3,4,5)
        //在函数中,a=1 ,b=2 ,其他参数存入arr中 

        使用. . . 语法符号获得的数组可以将一个数组进行展开

        示例:

        const Arr = [1, 2, 3]
        console.log(...Arr);
        //效果同 console.log(1,2,3)

运用场景:

        //求数组最大值/最小值
        const Arr01 = [1, 2, 3, 4, 5]
        console.log(Math.max(...Arr01))
        console.log(Math.min(...Arr01))

        //合并数组
        const Arr11 = [1, 2]
        const Arr12 = [3, 4]
        const ARR = [...Arr11, ...Arr12]
        console.log(ARR);

箭头函数

引入箭头函数的目的是更简短的函数写法,箭头函数的语法比函数表达式更简洁

更适用于需要匿名函数的地方

语法

语法一:

        //语法1
        //传统函数
        function test01(){
            console.log(111)
        }
        test01()

        //箭头函数
        test02=()=>{
            console.log(111)
        }
        test02()

语法二:只有一个参数可以省略小括号

        //语法二
        //传统函数
        function test11(x) {
            console.log(x)
        }
        test11(222)

        //箭头函数
        test12 = x => {
            console.log(x)
        }
        test12(222)

语法三:如果函数体只有一行代码,可以写到一行上,并且无需写 return 直接返回

        //语法三
        //传统函数
        function test21(x, y) {
            return x + y
        }
        console.log(test21(1, 2));

        //箭头函数
        test22 = (x, y) => x + y
        console.log(test22(2, 3));

语法四:加括号的函数体返回对象字面量表达式

        //语法四
        test31 = (name) => ({ Name: name })
        console.log(test31('zhao'));

参数 和 this

参数:

        箭头函数没有 arguments 动态参数,但是有 剩余参数 

箭头函数 this:

        箭头函数不会创建自己的this

        在箭头函数中会沿用作用域链上一层中的 this

解构赋值

解构赋值是一种快速为变量赋值的简洁语法

本质上仍是变量赋值

分为 数组解构对象解构

数组解构

概念:

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

数组解构语法示例:

        //数组解构
        const arr = [4, 5, 6]
        const [x, y, z] = arr
        console.log(x, y, z)
        //更简略写法
        const [i, j, k] = [7, 8, 9]
        console.log(i, j, k)

 常规赋值,用于对比:

        //常规赋值对比
        const arrRoutine = [1, 2, 3]
        const a = arrRoutine[0]
        const b = arrRoutine[1]
        const c = arrRoutine[2]
        console.log(a, b, c)

其他情况:

情况一(变量多 单元值少)

        const [a1, b1, c1, d1] = [1, 2, 3]
        console.log(a1, b1, c1, d1)
        // 1 2 3 undefined

可以赋初值解决

        const [A1, B1, C1, D1 = 4] = [1, 2, 3]
        console.log(A1, B1, C1, D1)
        // 1 2 3 4

情况二(变量少 单元值多)

        const [a2, b2] = [1, 2, 3]
        console.log(a2, b2)
        // 1 2

可以使用剩余参数解决

        const [a3, b3,...Arr] = [1, 2, 3,4]
        console.log(a3, b3,Arr)

情况三(按需导入)

        const [a4, , c4, d4] = [1, 2, 3, 4]
        console.log(a4, c4, d4)
        // 1 3 4

 情况四(多维数组套娃)

        const [a5, [b5, c5]] = [1, [2, 3]]
        console.log(a5, b5, c5)
        // 1 2 3

 应用之一(交互变量

        let n = 10
        let m = 40
        ;[m, n] = [n, m] // ; 必加
        console.log(n, m)
        // 40 10

补充(两种必加分号情况)

情况一:立即执行函数

        ;(function name(){
            console.log(111)
        })()

情况二:数组解构,数组开头

        ;[m, n] = [n, m]

对象解构

概念:

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

语法示例:

        const user = {
            name: 'zhao',
            age: 19
        }
        const { name, age } = user //{ }里面必须同名
        console.log(name, age)

        //更简略写法
        const { x, y } = {
            x: 'zhao',
            y: 19
        } //{ }里面必须同名
        console.log(name, age)

        // zhao 19

常规赋值,用于对比:

        const User = {
            Name: 'biao',
            age: 19
        }
        const Name = User.Name
        const Age = User.age
        console.log(Name, Age)
        // biao 19

各种情况与数组解构相同

数组对象解构(又是套娃)

        const [{ i, j }] = [{
            i: 1,
            j: 2
        }]
        console.log(i, j)
        // 1 2

对象

创建对象三种方式:

  • 利用对象字面量创建对象
  • 利用 new Object 创建对象
  • 利用构造函数创建对象
        //利用对象字面量创建对象
        const test01={
            name:'zzz',
            age:18
        }        

        //利用 new Object 创建对象
        const test02=new Object({
            name:'zzz',
            age:18
        })

构造函数

(个人感觉好像C++里面的类啊~~)

构造函数是一种用来 初始化对象 的特殊函数

特点:

        可以通过构造函数来 快速创建 多个类似的对象

        function Person(name,age,school){
            this.name=name
            this.age=age
            this.school=school
        }
        const zhao=new Person('zhao',19,'清华')
        const liu=new Person('liu',20,'山农')
        const gao=new Person('gao',18,'北大')
        //使用构造函数,直接生成三个对象
        //而常规语法,只能允许创建一个对象

注:构造函数内部无需写return

约定(一些重要,但又不重要的东西,手动狗头)

  • 构造函数的命名以大写字母开头。
  • 构造函数只能由 "new" 操作符来执行。

实例化

使用 new 关键字调用函数的行为被称为实例化

实例化执行过程:

        const zhao=new Person('zhao',19,'山农')

1.创建新的 空对象

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

 3.执行构造函数代码,因为 this指向新对象,所有会为新对象添加新的属性

4.返回新对象

实例成员 与 静态成员

实例成员:

        通过构造函数创建的对象称为实例对象

        实例对象中的属性和方法称为实例成员

        构造函数创建的实例对象彼此独立互不影响

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

        静态成员方法中的 this 指向构造函数本身

内置构造函数

内置构造函数与内置对象的关系

内置对象:

        JavaScript中的内置对象通常指的是那些在JavaScript环境中预定义的对象,这些对象提供了很多内置的方法和属性

内置构造函数则是用于创建这些内置对象的函数

例:Array、Date、Function、RegExp等内置构造函数,它们分别用于创建数组、日期对象、函数对象和正则表达式对象

部分内置构造函数

内置构造函数创建类型
Object普通对象
Array数组
String字符串
Number数值

补充:在JS中几乎所有的数据都基于构造函数创建

示例:

        const str='sdau'
        //底层运行
        const str=new String('sdau')

Object 部分静态方法

Object 是内置的构造函数,用于创建普通对象

静态方法就是只有构造函数Object可以调用的

方法一:

Object.keys 静态方法获取对象中所有属性

        const Person = {
            name: 'zhao',
            age: 18
        }
        const arr01 = Object.keys(Person) //返回为数组
        console.log(arr01);

方法二:

 Object.values 静态方法获取对象中所有属性值

        const Person = {
            name: 'zhao',
            age: 18
        }
        const arr02 = Object.values(Person) //返回为数组
        console.log(arr02);

方法三:

Object. assign 静态方法常用于对象拷贝

        const Person = {
            name: 'zhao',
            age: 18
        }

        const test={}
        Object.assign(test,Person)
        //将()后面的对象拷贝给前面的对象
        console.log(test);

也可以用来添加成员

        Object.assign(Person,{school:'SDAU'})
        console.log(Person);

 其他:

至于其他的方法和属性,实属繁多,就不打算记了

一方面因为懒,一方面效率不高

零零碎碎的东西,不是听一遍,记一遍,写一遍就能记住的

所有我感觉怎么一陀全记下来,效率着实堪忧

授人以鱼不如授人以渔,挂个MDN的网址比什么都强

MDN Web Docs (mozilla.org)

原型

构造函数的使用, 存在浪费内存的问题

构造函数实例创建的对象彼此独立、互不影响,即使数据相同,但仍会占用多份内存

示例,如下代码:

        function Person(name,age,school){
            this.name=name
            this.age=age
            this.school=school
            this.wobulijie=function(){
                console.log('wobulijie')
            }
        }
        const test01=new Person('zhao',19,'山农')
        const test02=new Person('zhao',19,'山农')
        console.log(test01===test02)
        //返回 false

因为 test01 与 test02 占不同的两份内存,所有返回为 false

但是二者数据相同,导致浪费内存

原型对象(原型)

  • 在JS中,每一个构造函数都有一个 prototype 属性,指向一个对象
  • 这个对象,被称为 原型对象(原型)
  • 构造函数通过原型 设置的函数方法 是所有对象所共享
  • 所有对象的实例可以共享这些方法,节约内存
        function Person(name, age, school) {
            this.name = name
            this.age = age
            this.school = school
        }

        //通过原型设置方法
        Person.prototype.wobulijie=function(){
            console.log('wobulijie')
        }
        const test01 = new Person('zhao', 19, '山农')
        const test02 = new Person('zhao', 19, '山农')
        console.log(test01.wobulijie===test02.wobulijie)
        //返回为 true

综上,原型作用:

        把不变的方法,直接定义在 prototype 对象上,从而实现 共享方法

注:构造函数和原型对象中的this 都指向 实例化的对象

constructor 属性

原型对象里面都有个constructor 属性,指向该原型对象的构造函数

        function Person(name, age, school) {
            this.name = name
            this.age = age
            this.school = school
        }

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

 对象原型

顾名思义,对象指向的原型(对象指向的对象原型)

对象都会有一个属性 __proto__,指向构造函数的 prototype 原型对象

注:

  • __proto__ 是JS非标准属性
  • [[prototype]]和__proto__意义相同
  • __proto__对象原型里面也有一个 constructor属性,指向创建该实例对象的构造函数

关系图:

 原型继承

可以通过构造函数来实现JS中的原型继承

方法:

        将要继承的属性赋值给原型对象,使可以共享这些属性和方法

        让constructor指回基础属性对象的构造函数

        //要继承的 属性与方法
        //被继承者
        function School(){
            this.schoolName='SDAU'
            this.schoolAddress='泰安'
            this.work=()=>{
                console.log('study')
            }
        }

        //继承者
        function Student(ID,name){
            this.ID=ID
            this.name=name
        }

        //继承过程
        Student.prototype=new School()
        Student.prototype.contructor=Student

        const zhao=new Student('123','zhao')
        zhao.work()
        console.log(zhao)

使用构造函数来继承,可以在给对象(继承者)添加新属性时,不影响其他对象

        //要继承的 属性与方法
        //被继承者
        function School(){
            this.schoolName='SDAU'
            this.schoolAddress='泰安'
            this.work=()=>{
                console.log('study')
            }
        }

        //继承者
        function Student(ID,name){
            this.ID=ID
            this.name=name
        }
        function Teacher(wages,name){
            this.wages=wages
            this.name=name
        }

        //继承过程
        Student.prototype=new School()
        Student.prototype.contructor=Student
        Teacher.prototype=new School()
        Teacher.prototype.contructor=Teacher


        //给Student构造函数添加新属性
        Student.prototype.love=()=>{
            console.log('天天摸鱼')
        }

        const zhao=new Student('123','zhao')
        const tea=new Teacher('10000','teach')
        zhao.love()
        console.log(zhao)
        console.log(tea)

 原型链

本质:查找规则

        基于原型对象的继承使得不同构造函数的原型对象关联在一起,

        并且这种关联的关系是一种链状的结构,

        我们将原型对 象的链状结构关系称为原型链

查找规则:

  1. 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
  2. 如果没有就查找它的原型(也就是 __proto__指向的 prototype 原型对象)
  3. 如果还没有就查找原型对象的原型(Object的原型对象)
  4. 依此类推一直找到 Object 为止(null)
  5. __proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线
  6. 可以使用 instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上

深拷贝

通过递归实现

递归函数

如果一个函数在内部可以调用其本身,那么这个函数就是递归函数

注:必须要加退出条件 return

示例,利用递归写循环:

        let i=0
        function test(){
            console.log(i)
            i++
            //必须要加退出条件 return
            if(i>=10){
                return
            }
            //递归
            test()
        }
        test()

实现深拷贝

代码示例:

        //递归函数实现深拷贝
        //被拷贝函数
        const Person = {
            name: 'zhao',
            age: 18,
            love: [1, 2, 3],
            shool: {
                sname: 'sdau',
                address: '泰安'
            }
        }

        const o = {}
        //深拷贝递归函数
        //传入两个对象,将 old 拷贝给 new
        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(o, Person)

        console.log(Person)
        console.log(o)

 通过js库实现

Lodash 是一个一致性、模块化、高性能的 JavaScript 实用工具库(官网原话)

js库lodash里面cloneDeep内部可以实现深拷贝

        const test=_.cloneDeep(Person)

通过JSON实现

        const test01=JSON.parse(JSON.stringify(Person))
        console.log(test01)

异常处理

异常处理是指预估代码执行过程中可能发生的错误

最大程度的避免错误的发生导致整个程序无法继续运行

(我要是能预判错误,我还写这个错误干什么,再说我也没那个能力啊)

throw 抛异常

  • 终止程序
  • 抛出所写提示信息
    <script>
        throw '抛出一个异常'
    </script>

  •  可配合Error 对象使用
    <script>
        throw new Error('抛出一个异常')
    </script>

 try /catch 捕获异常

  1. 将预估错误的代码写在 try 代码段中
  2. 如果 try 代码段出现错误,则执行 catch 代码段
  3. finally 不管是否有错误,都会执行
        try {
            const p = document.querySelector('.p')
            p.style.color='blue'
        } 
        catch (error){
            console.log(1111)
            console.log(error.message)
        }
        finally {
            console.log(2111)
        }

debugger

作用:cy 插眼

改变this指向

call()

作用:调用使用call方法的函数,同时指定被调用函数中 this 的值

本质:调用函数

语法:fun . call ( 指定this值 , 参数 )

示例:

        const i=[1,2]
        function fn(x){
            console.log(x)
            console.log(this)
        }
        fn.call(i,111)
        //this指向了i

 apply()

作用:调用使用apply方法的函数,同时指定被调用函数中 this 的值(同call)

与call区别:apply()中参数传入必须是数组

语法:fun . apply( 指定this值 , [ 参数 ] )

示例:

        function fnn(x,y){
            console.log(x+y)
            console.log(this)
        }
        fnn.apply(i,[1,2])
        //this指向了i

 bind()

作用:能改变函数内部this 指向,但不会调用函数

语法:语法:fun . bind ( 指定this值 , 参数 )

当只改变this指向,不调用函数时使用

性能优化

防抖

一个计时为n秒的函数,若在n秒内又触发了事件,则重新计时

例子:MOBA游戏中的回城

代码:

当事件触发时,先清除定时器,再重新开启定时器

        //设置防抖函数
        function AntiShake(fn, t) {
            //fn为事件触发时执行的函数,t为时间
            let timer
            //返回为一个函数,因为绑定事件需要函数
            return function () {
                //判断有无计时器,有则关闭
                if (timer) {
                    clearTimeout(timer)
                }
                //重新开启定时器
                timer = setTimeout(function () {
                    fn()
                }, t)
            }
        }

案例:

<body>
    <div class="box"></div>
    <script>
        const box = document.querySelector('.box')
        let i = 1

        //设置防抖函数
        function AntiShake(fn, t) {
            //fn为事件触发时执行的函数,t为时间
            let timer
            //返回为一个函数,因为绑定事件需要函数
            return function () {
                //判断有无计时器,有则关闭
                if (timer) {
                    clearTimeout(timer)
                }
                //重新开启定时器
                timer = setTimeout(function () {
                    fn()
                }, t)
            }
        }

        //设置事件触发时执行的函数
        function fn() {
            box.innerHTML = i++
        }

        box.addEventListener('mousemove', AntiShake(fn, 500))
    </script>
</body>

 

节流

一个计时为n秒的函数,在n秒内无论怎么触发事件,都不影响计时

例子:技能CD

代码:

记录时间差,小于差值则不执行,大于则执行

        //设置节流函数
        function Throttle(fn, t) {
            //fn为事件触发时执行的函数,t为时间
            let timer=0
            //返回为一个函数,因为绑定事件需要函数
            return function(){
                //记录当前时间
                let now=new Date()
                //时间差=当前时间-触发事件时的时间
                let timeDifference=now-timer
                //判断,如果时间差>t,则执行
                if(timeDifference>t){
                    fn()
                    //记录触发事件时的时间
                    timer=now
                }
            }
        }

案例:

<body>
    <div class="box"></div>
    <script>
        const box = document.querySelector('.box')
        let i = 1

        //设置节流函数
        function Throttle(fn, t) {
            //fn为事件触发时执行的函数,t为时间
            let timer=0
            //返回为一个函数,因为绑定事件需要函数
            return function(){
                //记录当前时间
                let now=new Date()
                //时间差=当前时间-触发事件时的时间
                let timeDifference=now-timer
                //判断,如果时间差>t,则执行
                if(timeDifference>t){
                    fn()
                    //记录触发事件时的时间
                    timer=now
                }
            }
        }

        //设置事件触发时执行的函数
        function fn() {
            box.innerHTML = i++
        }

        box.addEventListener('mousemove',Throttle(fn,500))
    </script>
</body>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值