JavaScript 进阶

JavaScript 进阶

let 与 const

let 和 const 都不会造成变量提升, 解决了 var 的覆盖与泄露问题

let
let a = 1
a = 3

console.log(a); // 3

let 用来声明本作用域的变量,可以被重新赋值

const
const name = '佩奇'

console.log(name); // 佩奇

const 用来声明本作用域的常量,不可以被重新赋值(修改变量指向的内存地址)和重复声明

箭头函数

const getNumber = function(num){
    console.log(num) // 10
}

getNumber(10)

上面是普通的匿名函数的书写方式,所谓的箭头函数就是简化了匿名函数

const getNumber = (num) => {
    console.log(num) // 10
}

getNumber(10)

当只有一个形参时可以省略括号,当代码块里只有一行代码时可以省略大括号:

const getNumber = num => console.log(num) // 10
getNumber(10)

箭头函数简写时也会自动加上默认的 return

const getNumber = (x,y) => x + y // 这里自动加上了默认的 return
console.log(getNumber(1,2)) // 3

数组解构

以前的方式给数组中所有数据赋值

const phone = ["华为","苹果","小米","OPPO"]

const hw = phone[0]
const pg = phone[1]
const xm = phone[2]
const op = phone[3]

es6数组解构

const phone = ["华为","苹果","小米","OPPO"]

const [hw,pg,xm,op] = phone
console.log(hw); // 华为
console.log(op); // OPPO

在数组解构时会遇到如下问题:

// 接收的比较多
// const [a,b,c,d] = [1,2,3]
// console.log(a); // 1
// console.log(b); // 2
// console.log(c); // 3
// console.log(d); // undefined

// 接收的比较少
// const [a,b] = [1,2,3]
// console.log(a); // 1
// console.log(b); // 2

// 使用展开符
// const [a,...b] = [1,2,3]
// console.log(a); // 1
// console.log(b); // [2, 3] 真数组

// 防止undefined,初始默认值
// const [a = 0, b = 0, c = 0] = [1,2]
// console.log(a); // 1
// console.log(b); // 2
// console.log(c); // 0

// 忽略,按需赋值
// const [a, ,c] = [1,2,3]
// console.log(a); // 1
// console.log(c); // 3

// 多维数组也支持,格式一一对应就行了
const [a,[b,c]] = [1,[2,3]]
console.log(a);
console.log(b);
console.log(c);

对象解构

// obj 对象
const obj = {
    uname : "xin",
    age : 16
};
console.log(obj); // {uname : "xin",age : 16}

// // 解构的语法,在解构的时候变量与属性名要一致
// const {uname,age} = obj
// console.log(uname); // xin
// console.log(age); // 16

// 如果出现同属性名
const uname = "ccx" // uname 在上面用过的情况下

// { 旧属性名 : 新属性名 }, 旧属性名与对象中的属性保持一致
const {uname : username,age} = obj

console.log(username); // xin
console.log(age); // 16

数组对象解构

const pig = [
    {
        uname : '佩奇',
        age : 6
    }
]

// 一层一层解构就好了, 先解构数组然后解构对象
const [{uname,age}] = pig

console.log(uname,age); // 佩奇 6

多级对象解构

const pig = {
    name: '佩奇',
    family: {
        mother: '猪妈妈',
        father: '猪爸爸',
        brother: '乔治'
    },
    age: 6
}

// 这里的 先解构 family 然后解构 family 的属性
const { name, family: { mother, father, brother }, age } = pig

console.log(name); // 佩奇
console.log(mother); // 猪妈妈
console.log(age); // 6

构造函数

构造函数:一种特殊的函数,可以用来初始化对象

使用场景:例如下方创建了佩奇对象,又继续创建了乔治等对象,里面属性是一样的,只是值不一样,这时我们就可以使用构造函数

// 创建佩奇
const Peppa = {
    name : '佩奇',
    age : 6,
    gender : '女'
}

// 创建乔治
const George = {
    name : '乔治',
    age : 3,
    gender : '男'
}

// 创建猪妈妈
const Mum = {
    name : '猪妈妈',
    age : 30,
    gender : '女'
}

// 创建猪爸爸
const Dad = {
    name : '猪爸爸',
    age : 32,
    gender : '男'
}

使用构造函数

/** 
* 使用构造函数的两个约定
* 函数命名首字母大写
* 它们只能通过 'new' 来操作执行
* function 构造函数名(形参){
*	this.属性名 = 形参
* }
*
* 通过 new 实例化对象
* new 构造函数名(实参)
*
* 接收创建的对象
* const peppa = new 构造函数名(实参)
*
* 构造函数没有 return,但是有默认的返回值:创建的新对象
*/

function Pig(name,age,gender){
    this.name = name
    this.age = age
    this.gender = gender
}

const Peppa = new Pig('佩奇',6,'女')
const George = new Pig('乔治',3,'男')
const Mum = new Pig('猪妈妈',30,'女')
const Dad = new Pig('猪爸爸',32,'男')

console.log(Peppa); // Pig { name: '佩奇', age: 6, gender: '女' }
console.log(George); // Pig { name: '乔治', age: 3, gender: '男' }
console.log(Mum); // Pig { name: '猪妈妈', age: 30, gender: '女' }
console.log(Dad); // Pig { name: '猪爸爸', age: 32, gender: '男' }

数组常见的方法

map

用于对数组中的每个元素进行操作,并返回一个新的数组,该数组包含了对原始数组中每个元素操作的结果

const arr = ['red','green','pink']

// map 可以更改原数组并且创建一个新的数组
const list = arr.map((item,index) => {
    console.log(item); // red green pink
    console.log(index); // 0 1 2
    return item // map 有 return
})

console.log(list); // 打印了新的数组 ['red','green','pink']

const list2 = arr.map((item,index) => {
    console.log(item); // red green pink
    console.log(index); // 0 1 2
    return item='你好' // map 有 return
})

console.log(list2); // 打印了新的数组 ['你好','你好','你好']
forEach

用于遍历数组并对每个元素执行指定的操作

// forEach 只是进行了遍历没有返回值
arr.forEach((item,index) => {
    console.log(item); // red green pink
    console.log(index); // 0 1 2
})
filter

用于创建一个新的数组,在返回时如果为 true 则会被添加到新的数组中,如果为 false 则会被忽略

const arr = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]

// const list = arr.filter(item => {
//     return item >= 50 // 返回新的数组
// })

// 简写
const list = arr.filter(item => item >= 50) // 返回新的数组

console.log(list); // [ 50, 60, 70, 80, 90, 100 ] 大于等于50的

// const list2 = arr.filter(item => {
//     return item >= 30 && item <= 60  // 返回新的数组
// })

// 简写
const list2 = arr.filter(item => item >= 30 && item <= 60) // 返回新的数组

console.log(list2); // [ 30, 40, 50, 60 ] 大于等于30 并且 小于等于60
reduce

用于对数组中的每个元素进行操作,并将操作结果合并为一个最终值

const arr = [1,2,3]

// arr.reduce((上一次的值, 当前的值) => 上一次的值 + 当前的值), 从多少开始累加)
const total = arr.reduce((prev,current) => prev+current, 10)

console.log(total) // 16 => 10 + 1 + 2 + 3 = 16
join

​ 用于对数组的拼接,可以将数组里的内容进行指定的拼接转换为字符串

const arr = ['I', 'Love', 'You']

const str = arr.join(' ') // 和空格进行拼接

console.log(str) // 'I Love You'
reverse

用于对数组的顺序反转

const arr = [1, 2, 3]
arr.reverse()

console.log(arr); // [3, 2, 1] 

对象常见的方法

Object.keys

一个静态方法,用于获取对象所有的键值

const obj = { name : '佩奇', age : 6 }

const arr = Object.keys(obj) 

console.log(arr) // [name, age]
Object.values

一个静态方法,用于获取对象所有的值

const obj = { name : '佩奇', age : 6 }

const arr = Object.values(obj) 

console.log(arr) // [佩奇, 6]
assign

用于对象拷贝

const obj = { name : '佩奇', age : 6 }
const o = {}

Object.assign(o,obj) // 将 obj 的内容拷贝到 o 里面
console.log(o) // { name : '佩奇', age : 6 }

// 也可以实现对象属性的追加
Objecr.assign(obj, {gender : '女'})
console.log(obj) // { name : '佩奇', age : 6, gender : '女'}

字符串常见的方法

length

用于获取字符串的长度

const str = 'ccx'
console.log(str.length); // 3
split

用于将字符串分割为数组

const str = 'ccx,yxr'
const str2 = str.split(",")

console.log(str2); // ['ccx', 'yxr']

原型

在 JavaScript 中,每个函数都有一个 prototype 属性,该属性是一个对象(也称它为原型对象),它包含了可以被函数的实例对象访问的属性和方法。

当创建一个函数时,该函数的 prototype 属性会被自动创建,并包含一个 constructor 属性,该属性指向函数本身。

构造函数
function Pig(name,age,gender){
    // 函数中的this指向实例化的对象
    this.name = name
    this.age = age
    this.gender = gender
    // 在这里创建了构造方法 eat
    this.eat = function(){
        console.log("在吃饭")
    }
}

// 每次实例化一个对象都会新创建一个对象内存
const Peppa = new Pig('佩奇',6,'女')
const George = new Pig('乔治',3,'男')

// 创建了两个对象的构造方法,这样做会很浪费内存
console.log(Peppa.eat === George.eat); // false
原型对象 prototype
function Pig(name,age,gender){
    this.name = name
    this.age = age
    this.gender = gender
}

// 直接在原型对象身上创建一个eat函数,函数中的this指向实例化的对象,这里是 Peppa / George
Pig.prototype.eat = function(){
    console.log("在吃饭")
}

const Peppa = new Pig('佩奇',6,'女')
const George = new Pig('乔治',3,'男')

console.log(Peppa.eat === George.eat); // true
Peppa.eat(); // 在吃饭
对象原型 __proto__
function Pig(name,age,gender){
    this.name = name
    this.age = age
    this.gender = gender
}

// 直接在原型对象身上创建一个eat函数,函数中的this指向实例化的对象
Pig.prototype.eat = function(){
    console.log("在吃饭")
}

const Peppa = new Pig('佩奇',6,'女')
const George = new Pig('乔治',3,'男')

console.log(Peppa.eat === George.eat); // true

console.log(Pig.prototype); // 原型对象
console.log(Peppa.__proto__); // 对象原型 又指向了 原型对象,这样的话创建的 对象就可以使用 原型对象 prototype 中的方法了

原型对象 与 对象原型 都有一个 constructor 属性,同时指向了 pig 对象,说明了靠谁创建了它

三者的关系网:

构造函数.prototype 原型对象
constructor
__proto__ 对象原型
constructor
new出来的实例对象
构造函数
prototype对象
实例对象
继承

原型继承是一种实现继承的方式,通过原型对象来实现属性和方法的继承。

下面是没有继承的两个对象实现方式

// 女人的对象,两只眼睛,一个头
function Woman(){
    this.eyes = 2
    this.head = 1
}
const ru = new Woman();

console.log(ru); // Woman { eyes: 2, head: 1 }

// 男人的对象,两只眼睛,一个头
function Man(){
    this.eyes = 2
    this.head = 1
}
const xin = new Man()

console.log(xin); // Man { eyes: 2, head: 1 }

​ 在上面对象实现过程中我们发现 眼睛 和 头 是每一个都有的属性,所以可以利用继承的方式,将这两个属性提取出来

function People(){
    this.eyes = 2
    this.head = 1
}

function Woman(){}

// 给 Woman 的原型对象添加父亲的属性
Woman.prototype = new People()

// 给女生添加了特有的方法,虽然都是继承了 People 但是 ru 和 xin 是两个完全不一样的对象,所以 xin 就不会有 hair 方法
Woman.prototype.hair = function(){
    console.log('女生有长头发');
}

const ru = new Woman();

console.log(ru);

function Man(){}

// 给 Man 的原型对象添加父亲的属性
Man.prototype = new People()

const xin = new Man()

console.log(ru === xin) // false
原型链

原型链是 JavaScript 中实现继承的一种机制,它基于原型对象和原型链的概念。每个对象都有一个原型对象,该原型对象可以是另一个对象,从而形成一个链

构造函数.prototype 原型对象
constructor
__proto__ 对象原型
constructor
new出来的实例对象
prototype原型对象.__proto__
Object 原型对象 prototype
constructor
Object原型对象prototype.__proto__
构造函数
prototype原型对象
实例对象
Object.prototype 原型对象
Object
Null

在上面图中 一串串的 __proto__ 我们称它为原型链,像链子一样链接起来,Object 是所有对象的最顶端,它的原型对象的对象原型为 Null 因为已经封顶了。

原型链存在的意义就是为都西昂成员查找机制提供了一个方向。

instanceof

instanceof 运算符用于检查一个对象是否是另一个对象的实例。

function Person() {
    this.name = 'John';
}

Person.prototype.sayHello = function () {
    console.log('Hello, my name is ' + this.name);
}

let person = new Person();

console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(person.__proto__ === Person.prototype); // true
console.log(Object.prototype.__proto__); // null

console.log(person instanceof Person); // true
console.log(person instanceof Object); // true
console.log(person instanceof Array); // false

深浅拷贝

JavaScript 基础中我们都知道,直接赋值对象的时候是直接指向栈中同一个内存地址,所以赋值的去更改对象的时候,原对象也会跟着变化,显然是多此一举

这节学习了深浅拷贝之后我们就可以很好的解决这个问题

浅拷贝

拷贝地址中的内容

拷贝对象:展开运算符 { …obj } 、Object.assign( )

const obj = {
    'uname': 'xin',
    age: 18
}

// 复制 obj 的值到 o
const o = { ...obj }

// 改变 o 的值
o.age = 16

// 打印对比看看
console.log(o); // { uname: 'xin', age: 16 } 很明显已经改变了也没有影响以前的值
console.log(obj); // { uname: 'xin', age: 18 }
const obj = {
    'uname': 'xin',
    age: 18
}

// 复制 obj 的值到 o
const o = {}
Object.assign(o, obj)

// 改变 o 的值
o.age = 16

// 打印对比看看
console.log(o); // { uname: 'xin', age: 16 } 很明显已经改变了也没有影响以前的值
console.log(obj); // { uname: 'xin', age: 18 }

拷贝数组: 展开运算符 [ …arr ] 、Array.prototype.concat()

… 原理一样这里就不作展示了

在浅拷贝的时候会遇到一个问题:当拷贝对象里面还有一层对象,那么深层的对象在拷贝时还是直接赋值的栈的内存地址

const obj = {
    'uname': 'xin',
    age: 18,
    body : {
        'uname' : 'ccx'
    }
}

// 复制 obj 的值到 o
const o = { ...obj }

// 改变 o 的值
o.age = 16

// 打印对比看看
console.log(o); // { uname: 'xin', age: 16, body : {'uname' : 'ccx'}} 很明显已经改变了也没有影响以前的值
console.log(obj); // { uname: 'xin', age: 18, body : {'uname' : 'ccx'} }

// 改变深层的对象内容
o.body.uname = 'AAA'

// 打印对比看看
console.log(o); // { uname: 'xin', age: 16, body : {'uname' : 'AAA'}} 两个都跟着改变了
console.log(obj); // { uname: 'xin', age: 18, body : {'uname' : 'AAA'} }

所以浅拷贝只适合在只有一层对象中使用,如果比较复杂就不可以了,但深拷贝就可以解决这个问题

深拷贝

拷贝的是对象,不在是地址

常见的方法:

  1. 通过递归实现深拷贝
  2. lodash/cloneDeep
  3. 通过JSON.stringify()实现

异常处理

throw

抛出异常,可以在代码设定时自行抛出一个异常

先看一下不抛出异常

function fn(x,y){
    if(!x || !y){
        
    }

    return x + y
}

console.log(fn()); // NaN

这里我们利用 throw 抛出异常

function fn(x,y){
    if(!x || !y){
        // 这里我们呢抛出一个异常
        throw new Error('用户没有输入内容')
    }

    return x + y
}

console.log(fn()); // Error: 用户没有输入内容 会自动中断程序
tyr/catch

可以捕获异常信息

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        function fn(x, y) {
            try {
                const p = document.querySelector("p") // 这里并没有 p 标签,所以在 try 出现错误,会直接跳到 catch 中运行代码块
                p.style.color = 'red'
            } catch (error) {
                console.log(error.message); // Cannot read properties of null (reading 'style') 抛出异常
                // return // 可以加上 return 来中断程序运行
            }
            // 不管你的程序有没有错误都会去执行的代码块
            finally{
				console.log('执行了') // 执行了
            }
            
            console.log(111) // 111 try/catch 不会中断程序
        }

        fn()
    </script>
</body>

</html>
debugger

我们程序员调试代码的时候用的,可以中断程序运行然后观察程序的运行过程

this

在 JavaScript 中,this 是一个关键字,用于指向当前执行上下文的对象。它的值取决于函数的调用方式。

普通函数中的 this

普通函数的调用方式决定了 this 的值,即【指向调用者】

console.log(this); // window

function fn(){
    console.log(this); // window
}

fn() // 完整写法 window.fn() 所以 函数 里面的 this 指向 window

// setTimeout 完整写法 window.setTimeout
setTimeout(function(){
    console.log(this); // window
}, 1000)

document.querySelector("html").addEventListener('click', function(){
    console.log(this); // html 元素对象, 因为市 html 元素调用的函数,所以指向了 html元素对象
})

const obj = {
    sayHi : function(){
        console.log(this); // 指向 obj
    }
}

obj.sayHi() // obj{...} obj调用的函数,所以this指向了 obj
箭头函数中的 this

箭头函数没有自己的 this,它会捕获调用上下文中的 this

const obj = {
    name: 'xin',
    sayHi: () => {
        console.log(this); // window
    }
};
obj.sayHi(); // 因为箭头函数没有this,所以会找调用者的调用者
// obj.sayHi() 完整写法 window.obj.sayHi() 所以 sayHi 的调用者的调用者 是 window
改变 this 的指向
  1. call()
  2. apply()
  3. bind()
call

改变 this 的同时也可以调用函数,返回值是函数的 return 返回值

const obj = {
    uname : 'xin'
}

function fn(x,y){
    console.log(this); // obj 原本指向 window 的指向了 obj
    console.log(x+y); // 3 将实参相加
    return x + y
}

// 参数一:this指向的位置,参数二...:函数需要传递的参数)
console.log(fn.call(obj,1,2)) // 3 返回值是函数的返回值
apply

改变 this 的同时也可以调用函数,返回值是函数的 return 返回值,与call的区别就是在传递参数时格式不同

const obj = {
    uname : 'xin'
}

function fn(x,y){
    console.log(this); // obj 原本指向 window 的指向了 obj
    console.log(x+y); // 3 将实参相加
    return x + y
}
// 参数一:this指向的位置,参数二:数组的形式[数组里面是函数需要传递的参数])
console.log(fn.apply(obj,[1,2]));// 3 返回值是函数的返回值
bind

改变 this 的同时但是不调用函数,返回值是新的函数,在调用函数时 需要调用新的函数体

function fn(x,y){
    console.log(this); // obj 原本指向 window 的指向了 obj
    console.log(x+y); // 3 将实参相加
}

// 参数一:this指向的位置,参数二...:函数需要传递的参数)
console.log(fn.bind(obj,1,2)) // [Function: bound fn] 返回值是新的函数

let fun = fn.bind(obj,1,2)
fun() // 在调用时需调用新的函数

防抖

用于在一定时间内限制某个函数或方法的执行次数。它的主要目的是减少不必要的重复操作,以提高性能和用户体验。

防抖的基本思想是在一个时间段内,只有当最后一次触发操作的时间距离上一次触发操作的时间超过一定的间隔时,才会执行相应的操作。如果在这个时间段内有多次触发操作,只有最后一次触发操作会被执行。

在单位时间内,频繁的触发事件,只执行最后一次

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <input type="text" name="" id="">
    <script>
        function debounce(time) {
            let timer
            return function () { // 将这个函数抛出,防止只触发一次
                if (timer) clearTimeout(timer) // 如果有计时器,那么就清除
                timer = setTimeout(function () { // 重新计时
                    console.log('用户输入成功,开始搜索页面');
                }, time)
            }
        }

        document.querySelector("input").addEventListener('input', debounce(600))
    </script>
</body>

</html>

节流

用于限制某个函数或方法在一定时间内的执行次数。它的主要目的是减少不必要的重复操作,以提高性能和用户体验。

节流的基本思想是在一个时间段内,只有当最后一次触发操作的时间距离上一次触发操作的时间超过一定的间隔时,才会执行相应的操作。如果在这个时间段内有多次触发操作,只有最后一次触发操作会被执行。

在单位时间内,频繁的触发事件,只执行一次

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <input type="text" name="" id="">
    <script>
        function throttle(time) {
            let timer = null

            return function () {
                if (!timer) {
                    timer = setTimeout(function () {
                        console.log('用户输入成功,开始搜索页面');
                        timer = null
                    }, time)
                }
            }
        }

        document.querySelector("input").addEventListener('input', throttle(3000))
    </script>
</body>

</html>

节流

用于限制某个函数或方法在一定时间内的执行次数。它的主要目的是减少不必要的重复操作,以提高性能和用户体验。

节流的基本思想是在一个时间段内,只有当最后一次触发操作的时间距离上一次触发操作的时间超过一定的间隔时,才会执行相应的操作。如果在这个时间段内有多次触发操作,只有最后一次触发操作会被执行。

在单位时间内,频繁的触发事件,只执行一次

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <input type="text" name="" id="">
    <script>
        function throttle(time) {
            let timer = null

            return function () {
                if (!timer) {
                    timer = setTimeout(function () {
                        console.log('用户输入成功,开始搜索页面');
                        timer = null
                    }, time)
                }
            }
        }

        document.querySelector("input").addEventListener('input', throttle(3000))
    </script>
</body>

</html>

结束语

感谢大家的阅读,希望这篇文章给你的启发,我们的开发环境此时已经搭建完成,如果你有任何的建议或问题,欢迎在评论区我们一起讨论

无论前方等待我们的是什么,让我们保持勇气和决心,一路向前!

  • 30
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值