Skip to content

JS

JS数据类型

  • 简单数据类型:number string boolean undefined null 栈内存
  • 复杂数据类型:object function array 堆内存

数组转字符串-字符串转数组

js
var msg = '12345'
const revMsg = msg.split('').reverse().join('')//字符串-数组-反转-字符串
console.log(revMsg)// 54321

事件捕获事件冒泡---事件流

html
    <div class="box1">
        <div class="box2">
            <div class="box3">
            </div>
        </div>
    </div>

    <script>
        const box1 = document.querySelector('.box1');
        const box2 = document.querySelector('.box2');
        const box3 = document.querySelector('.box3');
        box1.addEventListener('click', (e) => {
            console.log('我是box1');
        }, true);//此处配置了事件捕获
        box2.addEventListener('click', (e) => {
            console.log('我是box2');
        });
        box3.addEventListener('click', (e) => {
            console.log('我是box3');
        });
        // 默认是冒泡顺序 点击3则是3 2 1
        // 如果配置了true则是捕获阶段就触发 现最大的配置了true,继续点击3 则是1 3 2
    </script>
  • 事件冒泡是默认顺序,点击内层元素则会默认由内到外执行函数
  • 事件捕获是需要配置第三个参数为true,则在捕获阶段就触发

事件委托/事件代理

  • 是一种设计模式,用于简化处理事件的方式。它利用事件冒泡的特性,将事件处理程序绑定到一个父元素而不是每个子元素上。通过这种方式,可以减少事件处理程序的数量,并提高性能。
  • 在事件处理函数中,可以通过事件对象的属性(如 event.target)来确定是哪个子元素触发了事件
  • 可以优化性能,只用写一个监听器
  • 动态生成的元素也可以通过事件委托来监听
  • 简化代码

常见数组方法

迭代就是通过循环重复执行某个操作,直到满足特定条件为止,它是处理数据集合或执行重复操作的一种常见方式。

影响原数组的方法

push后增、unshift前增、shift删除第一个、pop删除末尾、reverse反转、sort排序、splice截取、替换

js
let arr = [1, 2, 3, 4, 5];
// 数组后增
arr.push(6);
console.log("使用 push 后:", arr); // 输出: [1, 2, 3, 4, 5, 6]

// 数组前增
arr.unshift(0);
console.log("使用 unshift 后:", arr); // 输出: [0, 1, 2, 3, 4, 5, 6]

// 删除数组第一个元素
arr.shift();
console.log("使用 shift 后:", arr); // 输出: [1, 2, 3, 4, 5]

// 删除数组最后一个元素
arr.pop();
console.log("使用 pop 后:", arr); // 输出: [1, 2, 3, 4]

// 反转数组
arr.reverse();
console.log("使用 reverse 后:", arr); // 输出: [4, 3, 2, 1]

// 数组排序
arr.sort((a, b) => a - b);
console.log("使用 sort 后:", arr); // 输出: [1, 2, 3, 4]

// 删除/替换数组中的元素 
arr.splice(1, 2, 6, 7);// 从下标为 1 的元素开始删除 2 个元素, 然后插入 6, 7
console.log("使用 splice 后:", arr); // 输出: [1, 6, 7, 4]

返回新数组的方法

concat合并、slice截取、filter过滤、map每个元素执行某个操作、reduce归并方法、flat扁平化

  • map会返回新数组 forEach没有返回值,当数组里是引用数据类型(数组、对象)时,会影响到原数组
  • forEach适合于你并不打算改变数据的时候-(打印每个元素,更新 DOM 元素的状态等); map()适用于你要改变数据值的时候,返回一个新的数组.
  • forEach不支持链式调用,map支持链式调用,例如map(...).reverse()
js
// 示例数组
const originalArray = [1, 2, 3, 4, 5];
// 使用 concat 方法合并数组
const concatArray = originalArray.concat([6, 7]);
console.log("使用 concat 合并数组:", concatArray); // 输出: [1, 2, 3, 4, 5, 6, 7]

// 使用 slice 截取数组
const slicedArray = originalArray.slice(1, 4);
console.log("使用 slice 截取数组:", slicedArray); // 输出: [2, 3, 4]

// 使用 filter 过滤数组
const filteredArray = originalArray.filter(num => num % 2 === 0);
console.log("使用 filter 过滤数组:", filteredArray); // 输出: [2, 4]

// 使用 map 对每个元素执行操作
const mappedArray = originalArray.map(num => num * 2);
console.log("使用 map 对每个元素执行操作:", mappedArray); // 输出: [2, 4, 6, 8, 10]

// 使用 reduce 进行数组归并
const reducedValue = originalArray.reduce((acc, num) => acc + num, 0);
console.log("使用 reduce 进行数组归并:", reducedValue); // 输出: 15

// 使用 flat 进行数组扁平化
const nestedArray = [1, [2, [3, [4, 5]]]];
const flattenedArray = nestedArray.flat(Infinity);
console.log("使用 flat 进行数组扁平化:", flattenedArray); // 输出: [1, 2, 3, 4, 5]

// 使用 forEach 打印每个元素,不改变原数组
originalArray.forEach(num => console.log("forEach 打印:", num));

// 使用 map 改变每个元素并返回新数组
const mappedArray2 = originalArray.map(num => num * 3);
console.log("使用 map 改变每个元素:", mappedArray2); // 输出: [3, 6, 9, 12, 15]

reduce案例

js
const arr = [1, 2, 3, 4]
const a = arr.reduce((sum, item, index) => {
     //sum:初始值(未传初始值默认数组第一项),item:当前项,index:当前索引
    console.log('和为:', sum, '当前项为:', item, '当前索引为:', index)
//    执行了三次。因为数组长度为4,初始值就是第一项,所以执行了三次
//    和为: 1 当前项为: 2 当前索引为: 1
//    和为: 3 当前项为: 3 当前索引为: 2
//    和为: 6 当前项为: 4 当前索引为: 3
    return sum + item
 })
console.log(a) // 10

// 传入初始值
const arr2 = [1, 2, 3, 4]
const b = arr.reduce((sum, item, index) => {
    //sum:初始值10,item:当前项,index:当前索引
    console.log('和为:', sum, '当前项为:', item, '当前索引为:', index)
    //    执行了四次。因为数组长度为4,初始值为10,所以执行了四次
    //    和为: 10 当前项为: 1 当前索引为: 0
    //    和为: 11 当前项为: 2 当前索引为: 1
    //    和为: 13 当前项为: 3 当前索引为: 2
    //    和为: 16 当前项为: 4 当前索引为: 3
    return sum + item
 },10)//传入初始值10
console.log(b) // 20

indexOf和findIndex:返回索引

indexof传入具体值,findIndex传入回调函数,函数内写查找条件

js
const arr = [1, 2, 3, 4]
//indexOf和findIndex找不到的话都会返回 -1
const indexOf = arr.indexOf(3)//indexOf只关注具体值
console.log(indexOf) // 2
const findIndex = arr.findIndex(item => item === 3)//findIndex可以自定义查找条件,返回第一个符合条件的索引,可用大于小于等于
console.log(findIndex) // 2

数组去重

js
#方法1:使用Set
const arr = [1, 2, 3, 1, 2, 3];
const uniqueArr = [...new Set(arr)];
console.log(uniqueArr); // [1, 2, 3]

#方法2:使用filter搭配indexOf
const uniqueArr2 = arr.filter((item, index) => {
    //检查item在数组中第一次出现的位置是否等于当前遍历元素的索引位置,如果等于的话那么就是第一次出现,就会保留在uniqueArr数组中
	return arr.indexOf(item) === index;
});
console.log(uniqueArr2); // [1, 2, 3]

判断是否是数组

js
const arr = [1, 2, 3];

// 使用 Array.isArray 判断对象是否为数组
console.log(Array.isArray(arr)); // 输出: true

// 使用 Object.prototype.toString.call 获取对象类型
const typeString = Object.prototype.toString.call(arr);
console.log(typeString); // 输出: [object Array]
// 判断对象是否为数组
const isArray = typeString === '[object Array]';
console.log(isArray); // 输出: true

new执行期间做了四件事件

  1. 创建一个空对象

  2. 将this指向这个空对象

  3. 执行构造函数中的代码,为对象赋值

  4. 返回新对象

this的指向

  • 普通函数 =》window;js严格模式(函数内写'use strict';开启严格模式) 指向undefined
  • 函数调用 =》调用者
  • 构造函数 =》new出来的实例
  • 箭头函数 =》没有this,寻找上一层作用域的this

更改this指向

  • call(指向的函数,值1,值2...)
  • apply(指向的函数,['值1','值2'...])
  • bind方法:let newFun = bind(指向的函数,值1,值2...) newFun() 必须要调用
js
//对象字面量方式
let dog = {
    name: 'dog',
    say(food1, food2) {
        console.log('我是' + this.name + ',我喜欢吃' + food1 + '和' + food2)
    }
}
let cat = {
    name: 'cat'
}
// 更改dog的this指向为cat
dog.say.call(cat, '鱼', '肉') //我是cat,我喜欢吃鱼和肉
dog.say.apply(cat, ['鱼', '肉']) //我是cat,我喜欢吃鱼和肉
let newDog = dog.say.bind(cat, '鱼', '肉') //bind返回一个新函数,这个函数的this指向cat
newDog() //我是cat,我喜欢吃鱼和肉 必须调用才能执行

闭包

闭包是两个嵌套的函数,内层函数可以访问外层函数的变量,变量不会被垃圾回收机制回收,长期驻扎在内存中,避免全局变量污染,缺点是增大内存的使用量,使用不当会造成内存的泄露,解决方法是执行完的变量赋值为null,·让垃圾回收机制进行回收释放

js
//闭包方式1
function fnA(a) { 
    return function (b) {
        return a + b
     }
}
let funB = fnA(1) //funB = function (b) { return 1 + b; }
let result = funB(2) //result 是 3   1+2
let result2 = funB(4) //result2 是 5  1+4

//闭包方式2
function fnC() { 
    let count = 0
    return function () {
        count++
        console.log(count)
    }
}
let funD = fnC() //funD = function () { return count++ }
funD()//1
funD()//2
funD()//3

JS 里垃圾回收机制是什么,常用的是哪种,怎么处理的?

  • JS 的垃圾回收机制是为了以防内存泄漏,内存泄漏的含义就是当已经不需要某块内存时 这块内存还存在着,垃圾回收机制就是间歇的不定期的寻找到不再使用的变量,并释放掉它们所指向的内存。引用计数法在处理循环引用时可能会有问题。
  • 最常见的垃圾回收方式是标记清除法,浏览器会定期检查在JavaScript程序中哪些对象不再被引用,然后执行标记和清除操作来释放不再需要的内存。
    • 工作流程:
    • 标记阶段:从根对象开始,标记所有仍然被引用的对象。
    • 清除阶段:清除所有未标记的对象,释放它们占用的内存。

js中的变量提升

  • 是一种语言特性,它使得在用var声明变量或用function声明函数之前可以引用它们,但在var声明之前使用值的话会导致undefined
  • 而let和const没有变量提升的特性,还有作用域的限制,若在定义前使用则会报错ReferenceError,不能在初始化前访问
  • const person = Object.freeze({ name: 'John' }); 通过Object.freeze来禁止修改const声明的引用类型的数据,修改时不会报错,但是不起作用

const、let、var

  • var声明的变量是挂载在window上的,可以通过window.a进行访问,这也是为什么var可以重复声明同一个变量,本质是修改对象的值
  • const声明的简单数据类型不能修改,复杂数据类型可以修改里面的值,但是不能重新赋值,重新赋值就会改变内存地址

Promise

  • Promise 是一个对象,它提供了一种处理异步代码的优雅方式,避免了回调地狱的问题。Promise 有三种状态:pending(等待态)、fulfilled(成功态)、rejected(失败态)。当一个 Promise 被创建时,它的初始状态为 pending。在异步任务成功后,Promise 变为 fulfilled 状态;在异步任务失败后,变为 rejected 状态。

  • 处理 Promise 的状态变化通常使用 .then().catch() 方法。.then() 方法接受两个参数,第一个用于处理成功的回调函数,第二个用于处理失败的回调函数,功能与.catch相同。.catch() 方法用于捕获 Promise 内部的错误或拒绝情况。

  • 静态方法 .all() 接受一个 promise 对象组成的数组,只有当所有的 promise 对象都成功时才返回一个成功状态的 Promise,返回值是一个包含所有 promise 结果的数组。静态方法 .race() 接受一个 promise 对象组成的数组,只要有一个 promise 对象变为 fulfilled 或 rejected 状态,就会返回对应的结果或错误。

  • promise的等价写法:

    • Promise.reject(new Error('123')) ==》 new Promise((resolve,reject)=>{reject(new Error('123'))})
    • Promise.resolve('1') ==》 new Promise((resolve,reject)=>{resolve('1')})

ES6新特性

  • 箭头函数、模板字符串、解构赋值、let和const声明、promise、模块化、展开运算符、数组API,比如findIndex、includes等等

浅拷贝和深拷贝

浅拷贝只复制第一层,修改第二层数据会相互影响, 深拷贝是完全独立开来两个不同存储,修改不会相互影响

  • 浅拷贝:扩展运算符、Object.assign({},对象)、Array.from(数组)

  • const a = JSON.parse(JSON.stringify(数据)) 注意:不能拷贝函数、undefined lodash库cloneDeep方法

  • js
    //通过MessageChannel方法
    function deepClone(obj) {
        return new Promise((resolve) => { 
            //通过MessageChannel 克隆数据   messageChannel是用来在不同页面或者不同代码块之间传递数据的
            const { port1, port2 } = new MessageChannel();
            port1.postMessage(obj);//传送需要克隆的数据
            port2.onmessage = ev => resolve(ev.data);//接收克隆后的数据,通过resolve返回出去
         })
    }
    const arr = [[1, 2, 3], [111, 22]]
    deepClone(arr).then(res => {
        console.log(res)// [[1, 2, 3], [111, 22]]
        console.log(arr === res)// false
    })

防抖和节流

  • 防抖的核心思想是事件触发后设置一个定时器,没有继续触发就让定时器等待一段时间后再执行函数。如果在等待时间内继续触发事件,则清除之前的定时器,开始一个新的定时器进行倒计时,场景:搜索框输入框
  • 当事件触发时,立即执行相应的操作,并在指定的时间间隔内忽略后续触发的事件,直到时间间隔过去后才允许下一次事件触发。场景:高频率触发事件(例如滚动、鼠标移动)
js
document.querySelector('.ipt').addEventListener('input', debounce(function () {
    console.log(this.value);//拿到输入框的值
}, 500));
/**
 * 防抖函数
 * 先定义一个timer为null 然后return一个函数,如果timer存在,就清除timer,
 * 然后再设置一个新的timer赋值个定时器,然后必须使用箭头函数,
 * 函数内部调用fn.call(this),这样就可以保证this指向调用者
 */
function debounce(fn, delay) {
    let timer = null; // 定义一个计时器变量,初始值为 null
    return function () { // 返回一个函数
        if (timer) clearTimeout(timer); // 如果计时器存在,清除之前的计时器
        timer = setTimeout(() => fn.call(this), delay);
        // 设置一个新的计时器,将计时器赋值为新的 setTimeout,在延时结束后调用 fn,并使用 call(this) 以确保函数内部的 this 指向调用者
    };
}


document.querySelector('.box1').addEventListener('scroll', throttle(function () {
    console.log(this);
}, 500));
/**
 * 节流函数
 * 先定义一个 timer 为 null,然后返回一个函数。如果 timer 不存在,就设置一个定时器,
 * 然后使用箭头函数,在函数内部调用 fn.call(this),之后将 timer 置为 null。
 * 这样可以保证 this 指向调用者,且在延时结束后仍然能够继续调用。
 */
function throttle(fn, delay) {
    let timer = null; // 先定义一个 timer 为 null
    return function () { // 返回一个函数
        if (!timer) { // 如果 timer 不存在
            timer = setTimeout(() => { // 设置一个定时器
                fn.call(this);
                timer = null; // 定时器执行完毕后,将 timer 置为 null
            }, delay);
        }
    };
}

事件循环(eventloop)、js异步

  • JavaScript是一门单线程语言,运行在浏览器的渲染主线程中。 主线程负责执行很多任务,比如渲染页面、解析HTML/CSS、执行JavaScript等。同步执行所有操作会导致主线程阻塞,页面就无法及时响应用户操作,甚至可能导致页面假死。为了解决这个问题,浏览器就采用了异步机制。当涉及到请求、计时器等异步任务时,主线程将这些任务委托给其他线程去处理,以便能够继续执行后续代码。
  • 异步机制的实现依赖于事件循环(Event Loop)。 事件循环是浏览器渲染主线程的工作方式。在每一次循环时,主线程从消息队列中取出第一个任务执行。其他线程负责在适当的时候将任务追加到消息队列末尾,等待主线程调度执行。异步任务分为宏任务和微任务,微任务具有较高的优先级,排在宏任务之前执行。
  • 微任务(Promise、MutationObserver) 宏任务(setTimeoutsetIntervalpostMessage、点击、键盘、滚动事件等)

js计时器能做到精确计时吗?为什么?不能

  • 操作系统的计时函数本身有少许偏差,不同操作系统之间也会有偏差
  • 受事件循环影响,计时器回调函数只能在主线程空闲时运行,调度又会带来偏差
  • w3c标准如果计时器嵌套超过5层,则会有最少4毫秒的偏差

高阶函数

满足函数作为参数返回一个函数任意其一都属于高阶函数

常见:数组方法,定时器延时器,bind,防抖节流函数、或者自己封装

JS设计模式

单例设计模式

保证一个类仅有一个实例,并提供一个访问它的全局访问点。实现的方法为先判断实例存在与否,如果存在则直接返回,如果不存在就创建了再返回,这就确保了一个类只有一个实例对象

发布订阅模式

基于一个事件中心,接收通知的对象是订阅者,需要先订阅某个事件,触发事件的对象是发布者,发布者通过触发事件,通知各个订阅者。vue中的事件总线就是使用的发布订阅模式。

观察者模式

目标者对象和观察者对象有相互依赖的关系,观察者对某个对象的状态进行观察,如果对象的状态发生改变,就会通知所有依赖这个对象的观察者。观察者模式相比发布订阅模式少了个事件中心,订阅者和发布者不是直接关联的。Vue中响应式数据变化是观察者模式。

输出代码执行所需时间

js
console.time('服务器启动时间');
server.listen(3000, () => { 
    console.log('express服务器启动成功');
})
console.timeEnd('服务器启动时间');

冒泡排序

js
//冒泡排序
let arr = [4, 2, 1,3,5,0]

function bubbleSort(arr) {
    //第一层循环控制比较轮数
    for (var i = 0; i < arr.length - 1; i++) {
        //第二层循环控制每轮比较次数
        for (var j = 0; j < arr.length - 1 - i; j++) {
            //此处减i是因为每轮比较后最大的数都会放在最后,所以每轮比较后都可以少比较一次,优化性能
            console.log('我是第' + i + '轮第' + j + '次')
            if (arr[j] > arr[j + 1]) {//相邻元素两两对比 如果前一个比后一个大
                let temp = arr[j]//定义个变量来存大的那个值
                arr[j] = arr[j + 1]//把小的值赋给前一个
                arr[j + 1] = temp//把大的值赋给后一个
            }
        }
    }
    return arr
}
console.log(bubbleSort(arr))