JavaScript
基础题型
1.JS的基本数据类型有哪些
基本数据类型:String,Number,Boolean,Nndefined,NULL,Symbol,Bigint
引用数据类型:object
NaN是一个数值类型,但不是一个具体的数字
Symbol 是一种不可变且唯一的原始数据类型,它可以用作对象的键,以创建私有属性或在某些情况下避免命名冲突。每个 Symbol值都是唯一的,即使它们具有相同的描述。
javascript
let sym1 = Symbol('mySymbol');
let sym2 = Symbol('mySymbol');
console.log(sym1 === sym2); // false,因为每个Symbol都是唯一的
let obj = {};
obj[sym1] = 'Hello';
console.log(obj[sym1]); // "Hello"
console.log(obj[sym2]); // undefined,因为sym1和sym2是不同的Symbol
Bigint 是一种新的数据类型,用于当整数值大于 Number 数据类型支持的范围时。这种数据类型允许我们 安全地对 大整数 执行算术操作,表示高分辨率的时间戳,使用大整数 id ,等等,而不需要使用库。
javascript
console.log(999999999999999); //=>10000000000000000
9007199254740992 === 9007199254740993; // → true 居然是true!
//创建方式
//1.在数字末尾追加 n 即可
console.log( 9007199254740995n ); // → 9007199254740995n
console.log( 9007199254740995 ); // → 9007199254740996
//2. BigInt() 构造函数
BigInt("9007199254740995"); // → 9007199254740995n
注意:
1.Symbol 和 Bigint 都是原始数据类型,但它们都是对象包装器类型(Symbol 和 Bigint )的实例。当你使用 typof 操作符时,Symbol 会返回
'
Symbol'
,而 Bigint 会返回'Object'2. Bigint 和普通的数字(Number)类型是不同,并且它们之间的算术运算需要显式转换
Object 在JS中Array,Founction,Map,Set等都是对象(Object)的一种,具有自己的属性和方法,但同时它们的原型对象也都继承自原型链顶点的Object.prototype
2.null和undefined的区别
1.作者在设计JS时先设计的null(在设计时借鉴了Java)
2.null会被隐式的转换成0,很不容易发现错误。
3.后设计undefined,是为了填之前的坑
具体区别:
null是一个表示"无"的对象(空指针对象),转化数值时为0;
undefined表示一个"无"的原始值,转为数值时为NaN
3.==和===的区别
== 相等运算符 :如果两边的数据类型不同,它会先尝试进行类型转换,再进行比较
=== 严格相等运算符或恒等运算符 :不会进行类型转换,它会先比较两边的数据类型是否相同,如果数据类型不同,则直接返回false,如果相同在比较值是否相等
javascript
console.log(5 == '5') //true
console.log(5 === '5') //false
4.for...in和for...of的区别
都是用来遍历对象(Object)的
for...in 循环:
遍历对象的可枚举属性 (包括对象原型链上继承的,除非用 Object.prototype.hasOwnProperty() 来过滤)。对数组来说也就是其索引
javascript
let obj = {
0: 'a',
1: 'b',
2: 'c'
}
for (const variable in obj) {
console.log(variable) //输出0 1 2
}
for...of 循环:
遍历可迭代对象 的值。 如 Array
、Map
、Set
、arguments
等
javascript
let iterable= ['a', 'b', 'c']
for (const value of iterable) {
console.log(value) //输出a b c
}
5 .setTimeout 和 setInterval
- setTimeout 接收一个回调函数和一个时间参数,创建一个滞后执行的任务,并返回一个Id,可通过clearTimeout取消任务
- setInterval 接收一个回调函数和一个时间参数,创建一个周期执行的任务,并返回一个Id,可通过clearInterval取消任务
javascript
let intervalId = setInterval(() => {
console.log('每隔2s执行一次')
}, 2000)
let timeotId = setTimeout(() => {
clearInterval(intervalId) //根据id取消setInterval
console.log('滞后8s,根据id取消setInterval')
}, 8000)
// clearTimeout(timeotId) //取消定时器
JavaScript数组
1.JS数组常用方法及其区别
推荐参考 JavaScript数组的常用函数
2.判断变量是不是数组
1.isArray
- 注意 :isArray() 方法并不是原型Array.prototype上的方法,它属于构造函数本身。这意味着不能通过数组实例来调用,,而是,应该通过 Array 构造函数来调用它。
javascript
const testArray= []
console.log(Array.isArray(testArray)) //true
2.constructor
- 原型Array.prototype上的constructor,它指向构造函数
javascript
const testArray= []
console.log(testArray.constructor === Array) //true
3.isPrototypeOf
- 原型object.prototype上的方法isPrototypeOf(object):检查一个对象是否存在于另一个对象的原型链中
javascript
const testArray= []
console.log(Array.prototype.isPrototypeOf(testArray)) //true
4.toString
- 使用 Object.prototype.toString.call(testArray) 来获取 testArray 的类型字符串
- 接着使用 indexOf('Array') 来检查该字符串是否包含 'Array'
- 如果包含,则 indexOf 返回的值会大于 -1,条件为真,输出 true
- 如果不包含,则 indexOf 返回 -1,条件为假,输出 false
javascript
const testArray = []
console.log(
Object.prototype.toString.call(testArray).indexOf('Array') > -1 //true
)
5.注意:typeof()
javascript
const testArray = []
console.log(typeof testArray) //object
console.log(typeof Array) //Founction
JavaScript对象
**1.**对象的常用方法
推荐参考 JavaScript对象
2.new操作符具体做了什么
**1.创建一个空对象:**并赋予其一个特殊内部属性__proto__( 或[[ptototype]] )
javascript
function Person() {}
console.log(typeof new Person())//Object
**2.设置原型链:**将空对象的原型(proto)指向构造函数的原型(prototype),以便新对象能够访问构造函数原型上定义的属性方法
javascript
function Person() {}
console.log(Person.prototype === new Person().__proto__) //true
**3.调用构造函数:**将空对象作为构造函数this的上下文(即构造函数内的this指向这个新对象)
javascript
function Person(name) {
this.name = name
}
console.log(new Person('张三').name)//张三
4.返回新对象: 对构造函数的返回值处理判断,如果构造函数没有返回或返回基本数据类型,则返回步骤1中创建的那个对象。如果构造函数返回引用数据类型,则 new
表达式的结果就是这个被返回的Object
javascript
function Person(name) {
this.name = name
return { name: '李四' }
}
console.log(new Person('张三').name) //李四
使用自定义函数模拟new操作符行为
javascript
function Person(name, age) {
this.name = name
this.age = age
}
function create(fn, ...args) {
// 1.创建一个空对象
var obj = {}
// 2.将空对象的原型指向构造函数的原型
Object.setPrototypeOf(obj, fn.prototype)
// 3.改变this指向
// args剩余参数数组
var result = fn.apply(obj, args)
//4.对构造函数返回值处理判断
// 检查 result 是否为 Object 的实例
return result instanceof Object ? result : obj
}
//简单检查两种方式创建的对象内容是否一样
console.log(
JSON.stringify(create(Person, '张三', 18)) ===
JSON.stringify(new Person('张三', 18))
) //true
JavaScript函数
1.推荐参考
2.JS继承的方式有哪些
1.ES6的class和extend关键字
清晰,易于理解。更接近传统面向对象编程语言的语法。但实际上,class
的底层仍然是基于ES5的原型继承机制。
javascript
class Parent {
constructor(name) {
this.name = name
}
sayName() {
console.log(`hello! my name is ${this.name}`)
}
}
class Child extends Parent {
constructor(name, age) {
super(name) // 调用父类的构造函数
this.age = age
}
sayAge() {
console.log(`I am ${this.age} years old`)
}
//可覆盖父类方法
}
// 实例化
const childInstance = new Child('张三', 18)
//子类原型上的方法
childInstance.sayAge() //I am 18 years old
//父类原型上的方法
childInstance.sayName() //hello! my name is 张三
特点:
-
通过class关键字定义类。在类中,可以定义构造函数(使用constructor方法)、实例属性、实例方法、静态属性和静态方法(static关键字声明,静态方法属于类本身,不属于实例)
-
可以封装对象的属性和方法,使得外部不能直接访问或修改对象的内部状态,只能通过类提供的方法来进行交互
-
通过extends关键字实现继承。相比ES6以前的继承机制,简化了继承的语法,使得继承变得更加直观和简单。
-
在子类的构造函数中,必须首先调用
super()
方法,它表示父类的构造函数。这是因为子类在实例化时会先创建自身的this
,然后继承父类的属性和方法。如果不调用super()
,子类就得不到this
对象,从而导致错误。
2.原型链继承
主要是通过将子类的原型设置为父类的一个实例对象,来继承父类的属性和方法。
javascript
function Parent() {
this.name = '张三' //name无法随子类实例需求而变化
}
//父类原型方法
Parent.prototype.sayName = function () {
console.log(`hello! my name is ${this.name}`)
}
function Child(name, age) {
this.age = age
}
// 设置Child的原型为Person的一个实例
Child.prototype = new Parent()
//子类原型方法
Child.prototype.sayAge = function () {
console.log(`I am ${this.age} years old`)
}
// 实例化
const childInstance = new Child('张三', 18)
//子类原型上的方法
childInstance.sayAge() //I am 18 years old
//父类原型上的方法
childInstance.sayName() //hello! my name is 张三
特点:
- 使用Child.prototype = new Parent(),会创建一个新的Parent实例,并执行Parent构造函数中的所有代码。Parent()中定义的任何绑定到this的属性都将存在于Child.prototype,其通过这种方式来实现Parent()内实例属性的继承。
- 在使用这种继承方式时,无法直接通过子类的构造函数向父类构造函数传递参数。这限制了父类构造函数根据子类实例的不同需求进行初始化的能力。
3.借用构造函数
主要是通过在子类的构造函数中使用call()
或apply()
方法调用父类的构造函数,并将this
作为第一个参数传递给父构造函数,从而实现子类继承父类的属性和方法。
javascript
function Parent(name) {
this.name = '张三' //name无法随子类实例需求而变化
//父类实例方法
this.sayName = function () {
console.log(`hello! my name is ${this.name}`)
}
}
//父类原型方法
Parent.prototype.testSayName = function () {
console.log(`hello! my name is ${this.name}`)
}
function Child(name, age) {
Parent.call(this, name) // 借用构造函数继承Parent
this.age = age
}
//子类原型方法
Child.prototype.sayAge = function () {
console.log(`I am ${this.age} years old`)
}
// 实例化
const childInstance = new Child('张三', 18)
//子类原型上的方法
childInstance.sayAge() //I am 18 years old
//父类实例方法
childInstance.sayName() //hello! my name is 张三
// 父类原型上的方法
childInstance.testSayName() //TypeError: childInstance.sayName is not a function
特点:
- 使用Parent.call(this, name)可以通过子类的构造函数向父类构造函数传递参数,增强了父类构造函数随子类需求初始化的能力
- 无法继承父类原型上的方法,只能继承父类构造函数Parent()内定义的实例属性和方法
- 父类Parent()内定义的实例方法会在每个实例内重复创建。因为该继承承是通过在子类的构造函数中调用父类的构造函数来实现的,所以创建的多个子类实例,虽然功能可能相同,但实例内存的方法却不同(内存地址不同,浪费空间)
4.组合式继承
结合了原型链继承和借用构造函数继承的继承模式。它解决了,原型链继承父类初始化能力有限制 ,而借用构造函数继承会导致方法在每个实例上重复创建 的问题。它主要通过使用借用构造函数继承属性,使用原型链继承方法来实现
javascript
function Parent(name) {
this.name = '张三' //name无法随子类实例需求而变化
}
//父类原型方法
Parent.prototype.SayName = function () {
console.log(`hello! my name is ${this.name}`)
}
function Child(name, age) {
Parent.call(this, name) // 借用构造函数继承Parent
this.age = age
}
//指定一个Parent.prototype作为Child.prototype的原型
Child.prototype = Object.create(Parent.prototype)
//子类原型方法
Child.prototype.sayAge = function () {
console.log(`I am ${this.age} years old`)
}
// 实例化
const childInstance = new Child('张三', 18)
//子类原型上的方法
childInstance.sayAge() //I am 18 years old
//父类原型上的方法
childInstance.sayName() //hello! my name is 张三
特点:
- 用原型链来继承父类原型上的方法,用借用构造函数来继承父类属性。
- 用 Child.prototype = Object.create(Parent.prototype)代替Child.prototype = new Parent(),创建一个新的空对象,其原型设置为Parent.prototype,并让Child.prototype指向这个对象。这并不会创建父类的实例,也不会执行Parent()内的代码,Child.prototype上也就不会存在Parent()内定义的实例属性。
- 在子类中调用父类构造函数,依然会在每个子类实例中都创建父类的实例属性和方法,但属性通常会被子类实例初始化的属性覆盖。而方法,只要将其定义在父类原型上,便不会在子类实例中重复创建。也就不会造成内存浪费。
5.其他继承
除了上述4种继承方式,还有寄生组合式继承,寄生式继承,混合继承等其他继承方式,此处不再一一列举。
其他题型
1.var,let和const
声明提升
- **声明提升(包括变量声明和函数声明):**JS中,代码执行时,浏览器或会首先处理所有的声明,并将它们提升到它们所在作用域(全局作用域或函数作用域)的顶部。这个过程就是声明提升。
- 变量提升(特指变量声明的提升): 在变量提升中,只有变量的声明部分被提升,而变量的赋值不会提升。这意味着,如果你在声明变量之前就尝试访问它,你将得到一个undefined的值,因为此时变量已经被声明,但还没有被赋值。
- 需要注意,虽然函数声明也会被提升,但与变量提升不同的是,函数声明的整个定义(包括函数体和变量名)都会被提升到作用域顶部,而不仅仅是函数的声明部分。这意味着你可以在函数声明之前的代码中调用该函数。
javascript
// function函数声明定义提升
fun() //输出 fun执行
// 箭头函数fn2变量还未被赋值
fun2() //抛出错误
function fun() {
console.log('fun执行')
}
// 箭头函数是一个赋值的过程
var fun2 = () => {
console.log('fun2执行')
}
var,const,let 的区别
1.作用域区别
const和let有块级作用域. var没有块级作用域,可以跨块访问, 不能跨函数访问
javascript
// var可以跨块访问
if (true) {
var x = 10
}
console.log('var可以跨块访问', x) //10
// var 变量在函数内部声明时,它们是函数作用域的,故不能跨函数访问(闭包)
function foo() {
var y = 20
console.log(y) // 输出 20
}
console.log(y) //ReferenceError: y is not defined,因为y是函数foo的作用域内的局部变量
2.变量提升
var存在变量的声明提升 let和const不存在变量提升(会抛出错误)
在使用let、const命令声明变量之前,该变量都是不可用的。这在语法上,称为暂时性死区。使用var声明的变量不存在暂时性死区
javascript
// '声明提升,赋值未提升'
console.log(num1) //输出666
var num1 = 666
// name 在一个特殊的环境中(如window.name在浏览器环境中)可能是已经存在的
// console.log(name);//这里不会报错
3.全局对象属性
浏览器的全局对象是window。var在函数外声明的变量为全局变量,并且会将该变量添加为全局对象的属性。
let和const在script标签下最外层声明的变量虽然也是全局的,但不会在window对象的枚举属性中出现
javascript
if (true) {
var varNum = 0
}
const constNum = 0
let letNum = 0
console.log(window.varNum, window.constNum, window.letNum)
//输出 0 undefined undefined
4.重复声明
var 会声明覆盖(不会提示) let const 不可重复声明(会抛出错误)
javascript
var num2 = 31
var num2 = 10
console.log('声明覆盖为最后一次', num1) //10
5.初始值设置
const声明的变量必须设置初始值(否则会抛出错误)
而let和var声明的变量则不必
6.指针指向
var和let创建的变量是可以更改指针指向(可以重新赋值)
const声明的变量是不允许改变指针的指向(不可以重新赋值),但const定义的引用数据类型Object的内容可变(因为这并不会改变变量存储的地址值)
总结:声明变量用let,声明常量用const,尽量不用var
2. 解构赋值
在JS中,传统的属性访问方法可能会涉及到大量的点操作符(.)或方括号([]),并且可能需要多行代码来声明和初始化变量。
而解构,它是一种表达式,允许你快速地将数组或对象的属性值提取到不同的变量中。这减少了编写重复和冗余代码的需要,使代码更加简洁和易读。
数组解构:
javascript
const arr1 = [1, 2, 3]
const [a, b, c] = arr1
console.log(a, b, c)//输1 2 3
对象解构:
javascript
const obj = {
firstName: 'Alice',
lastName: 'Bob',
age: 18
}
const { firstName, lastName, age } = obj
console.log(firstName, lastName, age) //输出Alice Bob 18
// 将firstName重命名为aa
const { firstName: aa } = obj
console.log(aa) //输出Alice
**例题:**如何在不借助其他变量的情况下交换1和2的位置
javascript
let arr = [1, 2]
;[arr[0], arr[1]] = [arr[1], arr[0]]
console.log(arr) // 输出: [2, 1]
3.防抖与节流
防抖(Debouncing)和节流(Throttling)是两种常用的优化高频率触发事件的策略,它们主要用于控制函数执行的频率,以减少不必要的计算或DOM操作,从而提高页面性能。尽管它们的目的相似,但实现方式和应用场景有所不同。
防抖
**概述:**防抖是指在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
防抖(Debounce) => 将多次操作变成一次(合并一定时间内的请求)
使用场景: 按钮防连点,搜索框实时搜索建议,文本输入的实时验证(验证(如检查邮箱、手机号格式等)),窗口大小调整时的重绘操作(文心一言窗口:页面大小不同时会有不同布局方案)
javascript
<input type="text" placeholder="搜索框实时搜索建议"id="frequencyInput" value=""/>
<script>
const input = document.querySelector('#frequencyInput')
// 防抖封装
// 闭包私有环境-拥有各自独立的词法作用域
const debounce = (fun, wait) => {
let timeoutId = null
return () => {
if (timeoutId) clearTimeout(timeoutId)
timeoutId = setTimeout(fun, wait)
}
}
const debounceTest = () => {
console.log('发起请求')
}
//监听键盘输入事件 => 每次输入均触发debounce(debounceTest, 2000)
// => 停止输入 2S 后 发起请求
input.addEventListener('input', debounce(debounceTest, 2000))
</script>
节流
**概述:**节流是指规定在单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次能生效。(常见的节流实现方法包括使用定时器或时间戳)
节流(Throttle) => 在规定周期内仅执行一次
**使用场景:**页面滚动(懒加载图片或内容、无限滚动加载更多内容等),鼠标移动(鼠标跟随效果、鼠标悬停提示等),按钮点击(防止表单的重复提交、防止用户频繁点击导致的无效操作等)
javascript
<style>
#box {
width: 200px;
height: 200px;
margin-top: 20px;
background-color: rgba(99, 181, 193, 0.5);
border-radius: 10px;
box-shadow: 2px 2px 2px 2px rgb(163, 181, 185);
transition: 0.6s;
}
#box:active {
scale: 0.8;
background-color: rgba(83, 133, 141, 0.8);
}
</style>
<div id="box"></div>
<script>
const triggerBox = document.getElementById('box')
// 节流封装
// 闭包私有环境-拥有各自独立的词法作用域
const throttle = (fun, time) => {
let timer = null
return () => {
// 如果timer为null则创建一个n s后执行的任务
if (!timer) {
timer = setTimeout(() => {
fun()
timer = null
}, time)
}
}
}
const throttleTest = () => {
console.log('发起请求')
}
//2s内,无论点击多频繁,都只执行一次
triggerBox.addEventListener('click', throttle(throttleTest, 2000))
triggerBox.addEventListener('click', () => {
console.log('触发事件')
})
</script>
4.深克隆与浅克隆
JavaScript中,浅克隆和深克隆的区分主要是针对引用数据类型(Object)。
对于基本数据类型(如Number, String, Boolean, Null, Undefined, Symbol, BigInt),赋值操作实际上是进行值的复制。因为基本数据类型存储的是值本身,所以当你将一个基本数据类型的变量赋值给另一个变量时,实际上是创建了一个新的变量,并将原始变量的值复制给了新变量。这个过程中,每个变量都拥有自己的内存空间,互不影响。
对于引用数据类型(如Object, Array, Function等),赋值操作实际上是复制了引用地址,而不是真正的数据。这意味着,如果你修改了新变量所指向的数据,原始变量所指向的数据也会发生变化,因为它们指向的是同一个内存地址。
javascript
let person = {
name: 'IKUN',
age: 18,
intereste: ['唱', '跳', 'rap']
}
浅克隆(Shallow Clone)
浅克隆只复制对象的第一层属性,如果对象的属性是基本类型,复制的就是基本类型的值;如果属性是引用类型,复制的就是内存地址,所以如果其中一个对象改变了这个地址的内容,就会影响到另一个对象。
1. 使用Object.assign()
方法
javascript
//只复制了顶层属性
const newPerson = Object.assign({}, person)
newPerson.age = 20
newPerson.interest.push('篮球')
console.log(person.age) //18
//引用数据类型
console.log(person.interest) //['唱', '跳', 'rap','篮球']
只会复制顶层属性,如果属性中存在引用数据类型,则只会复制其地址,克隆的新对象会和原对象共享该引用数据的内存
2. 使用扩展运算符(...)
javascript
//复制数组或对象的顶层属性
const newPerson = { ...person }
newPerson.age = 20
newPerson.interest.push('篮球')
console.log(person.age) //18
//引用数据类型
console.log(person.interest) //['唱', '跳', 'rap','篮球']
将person(对象)扩展到一个新的对象中,但只会复制顶层属性,如果属性中存在引用数据类型,则只会复制其地址,克隆的新对象会和原对象共享该引用数据的内存
深克隆(Deep Clone)
深克隆会复制对象的所有层级,这样,无论多少层的对象,每一层的属性都是完全复制的,不会共享内存地址,从而实现了真正的独立复制。
1.使用JSON.parse()和JSON.stringify()
javascript
const newPerson = JSON.parse(JSON.stringify(person))
newPerson.age = 20
newPerson.interest.push('篮球')
console.log(person.age) //18
//引用数据类型
console.log(person.interest) //['唱', '跳', 'rap']
JSON.stringify()返回一个由对象转换的 JSON 字符串,JSON.stringify()再将JSON字符串转换成对象;这种方法能处理绝大多数深克隆问题。但
注意 :这种方法不能复制函数、undefined
、symbol
等,且不能处理循环引用。
2.手动实现深克隆函数
javascript
function deepCopy(source) {
const targetObj = source.constructor === Array ? [] : {}
for (let keys in source) {
//检查属性是直接定义在 source 对象上的,而非原型链继承的
if (source.hasOwnProperty(keys)) {
//引用数据类型
if (source[keys] && typeof source[keys] == 'object') {
//维护层代码
targetObj[keys] =
source[keys].constructor === Array ? [] : {}
// console.log(source[keys]);
//递归
targetObj[keys] = deepCopy(source[keys])
} else {
//基本数据类型,直接 赋值
targetObj[keys] = source[keys]
}
}
}
// console.log(targetObj);
return targetObj
}
let newPerson = deepCopy(person)
newPerson.interest.push('GEGE')
console.log(person) //输出['唱', '跳', 'rap']
使用递归函数来逐层复制来实现深拷贝。
3.使用一些第三方库,如lodash的_.cloneDeep()方法。
5.原生JS获取DOM
1.通过ID
这是获取单个元素的最快方式,因为ID在HTML文档中应该是唯一的。
javascript
let element = document.getElementById("myElementId");
2.通过类名
注意,这会返回一个DOM集合,即使只有一个元素匹配。
javascript
let elements = document.getElementsByClassName("myClassName");
3.通过标签名
和getElementsByClassName()
一样,会返回一个DOM集合
javascript
let elements = document.getElementsByTagName("div");
4.通过css选择器
document.querySelector()
和document.querySelectorAll()
允许你使用CSS选择器来获取元素。querySelector()
返回匹配选择器的第一个元素,而querySelectorAll()
返回所有匹配选择器的元素的一个NodeList。
javascript
// 获取第一个匹配的元素
let firstElement = document.querySelector(".myClassName");
// 获取所有匹配的元素
let allElements = document.querySelectorAll(".myClassName");
5.通过name属性
虽然不常用,但也可以通过元素的name
属性来获取元素集合,这主要用于获取表单元素,如<input>
、<select>
等。
javascript
let elements = document.getElementsByName("myElementName");
6.Dom修改元素样式
javascript
// 获取元素
let elem = document.getElementById("myElement");
1.dom.style
直接设置样式属性,长一点的属性名字用驼峰命名法
javascript
// 修改背景颜色
elem.style.backgroundColor = "blue";
// 修改字体大小
elem.style.fontSize = "20px";
// 隐藏元素
elem.style.display = "none";
2. setAttribute和removeAttribute
通常不推荐用于样式修改,因为它会覆盖元素的所有内联样式,而不是单独修改一个属性。
javascript
// 设置样式(不推荐)
elem.setAttribute("style", "background-color: red; color: white;");
// 移除style属性(不推荐)
// elem.removeAttribute("style");
3.className / id
这种方式很有可能会覆盖之前写好的样式(前面写的不生效)
javascript
//style
.addClass{
//.....样式
}
#addId{
//.....样式
}
section.className = 'addClass'
section.id = 'addId'
4.classList 推荐使用
javascript
// 添加CSS类
elem.classList.add("newClass");
// 移除CSS类
elem.classList.remove("oldClass");
// 切换CSS类(如果存在则移除,如果不存在则添加)
elem.classList.toggle("toggleClass");
// 检查元素是否包含某个类
if (elem.classList.contains("someClass")) {
console.log("元素包含someClass类");
}
7.Promise
推荐参考请参考异步编程(Promise详解)
8.微任务和宏任务
请参考 浏览器渲染基本原理
常见的微任务来源Promise的.then()或catch()方法
常见的宏任务来源setTimeout的回调函数
javascript
// 立即把一个函数添加到微队列
// Promise.resolve().then(函数)
// Promise的.then()方法中的回调是异步的
// 代码解析顺序
console.log('script start'); // 1. 同步代码,直接执行
setTimeout(function() { // 2. setTimeout()内宏任务,推入宏队列,等待执行
console.log('setTimeout'); //# 9.宏任务执行
}, 0);
Promise.resolve().then(function() { //3. .then()内微任务,推入微队列等待执行
console.log('promise1'); //# 6.微任务队列内立即执行
setTimeout(function() { // 7. 宏任务,推入任务队列,等待执行
console.log('setTimeout in promise'); //# 10.宏任务执行
}, 0);
}).then(function() { //4. .then()内微任务,推入微任务队列等待执行
console.log('promise2'); //# 8.微任务队列内立即执行
});
console.log('script end'); // 5. 同步代码,直接执行
// 输出顺序:script start, script end, promise1, promise2,
// setTimeout, setTimeout in promise
- 先执行全局代码(同步,解析时遇到就执行),再执行异步代码(微队列优先,其他队列靠后)
- 解析顺序 ==> 任务执行顺序
若有错误或描述不当的地方,烦请评论或私信指正,万分感谢 😃