defer和async的区别
script是会阻碍HTML解析的,只有下载好并执行完脚本才会继续解析HTML
相同点:
- 都是异步加载,用于解决加载脚本时造成页面阻塞的问题
- 都只适用于外部脚本
不同点:
defer
- 脚本加载与html的解析并行,待html解析完后再运行脚本
- 在DOMContentLoaded事件处理程序之前执行defer脚本
- 多个脚本,会严格按照顺序从上到下执行
async
- 脚本加载与html解析并行,在脚本加载完成后立即执行
- async脚本可能在DOMContentLoaded事件处理程序之前执行,也可能在其后执行
- 多个脚本,哪个先加载完就先执行哪个
注意:默认情况下,动态脚本表现为async,可以设置script.async = fasle,这样脚本会比表现为defer
let script = document.createElement('script');
script.src = "/article/script-async-defer/long.js";
document.body.append(script);
同步和异步的区别
同步:下面代码会等待上面同步代码执行完毕,浏览器访问服务器请求,用户看得到页面刷新,重新发送请求,等请求完,页面刷新,新内容出现,用户看到新内容,进行下一步操作
异步:下面代码不会等待上面异步代码执行完毕,浏览器访问服务器请求,用户正常操作,浏览器后端进行请求,等请求完,页面不刷新,新内容也会出现,用户看到新内容
script标签中的属性
- async
- defer
- crossorigin配置请求相关的CROS(跨域资源共享)设置
默认值 crossorigin= "anonymous"配置文件请求不必设置凭据标志
crossorigin="use-credentials"设置凭据标志,意味着出站请求会包含凭据 - integrity允许比对接收到的资源和指定的加密签名以验证子资源完整性(SRI, Subresource Integrity)。如果接收到的资源的签名与这个属性指定的签名不匹配,则页面会报错,脚本不会执行。这个属性可以用于确保内容分发网络(CDN,Content Delivery Network)不会提供恶意内容
- src
内存泄漏
- 意外声明全局变量(没有声明的变量)
- 定时器/延时器没有清理
- 闭包使用不当
- 没有清理的DOM元素引用(dom清空或删除时,事件未清除)
闭包
使用闭包主要是为了设计私有的方法和变量
特性:
- 函数嵌套函数
- 函数内部可以引用外部的参数和变量
- 变量长期占用内存,不会被垃圾回收机制回收
优点:可以避免全局变量的污染
缺点:内层函数引用外层函数的变量,内层函数占用内存,如果不释放内存,占用过多时,容易引起内存泄漏
解决方法:无法自动销毁,就及时手动销毁,将函数的引用手动赋值为null
深拷贝
深拷贝:每一层数据的改动都不会影响原对象和新对象,在内存中把原对象复制一份
- JSON.stringify()和JSON.parse()处理的缺点:
如果对象中有属性是function或undefined,处理后会被过滤掉
如果属性值是对象,且由构造函数生成的实例对象,会丢弃对象的constructor - $.extend()
深度拷贝:$.extend({}, targetObject) // targetObject是需要复制的对象
深度合并:$.extend(true, {}, targetObject1, targetObject2) // 可以将两个对象深度合并后再返回出一个新对象 - loash第三方组件库
- assign()
- 手写遍历递归赋值
// 递归实现一个深拷贝
let obj={
name:'wushikang',
arr:[12,13,14],
strObj:{
weight:'52kg',
height:'180'
},
fn:function(){
console.log('你最棒');
}
}
function deepCopy(obj){
let newObj = null;
if(typeof(obj) === 'object' && obj !== null){
newObj = obj instanceof Array ? [] : {};
for(let key in obj){
newObj[key] = deepCopy(obj[key])
}
}else{
newObj = obj
console.log(newObj);
}
return newObj;
}
let obj1 = new deepCopy(obj);
浅拷贝
浅拷贝:只有第一层的变动不互相影响,深层的数据变动还会互相影响,拷贝的是原对象的内存地址
- 直接变量赋值
- Object.assign(),但目标对象只有一层时是深拷贝
- 扩展运算符(...),但目标对象只有一层时是深拷贝
字符串操作方法
截取字符串
slice(a, b)
a表示子字符串的开始位置
b表示子字符串的结束位置
将所有负值参数都当成字符串长度加上负参数值
substring(a, b)
a表示子字符串的开始位置
b表示子字符串的结束位置
将所有负参数值都转换为 0
substr(a, b)
a表示子字符串的开始位置
b表示子字符串的长度
将第一个负参数值当成字符串长度加上该值,将第二个负参数值转换为 0
以上方法,省略第二个参数都意味着提取到字符串末尾
let stringValue = "hello world";
console.log(stringValue.slice(-3)); // "rld"
console.log(stringValue.substring(-3)); // "hello world"
console.log(stringValue.substr(-3)); // "rld"
console.log(stringValue.slice(3, -4)); // "lo w"
console.log(stringValue.substring(3, -4)); // "hel"
console.log(stringValue.substr(3, -4)); // "" (empty string)
在字符串中定位子字符串的位置
从字符串中搜索传入的字符串,并返回位置(如果没找到,则返回-1)
indexOf()
从字符串开头开始查找子字符串
接收可选的第二个参数,表示开始搜索的位置,从这个 参数指定的位置开始向字符串末尾搜索,忽略该位置之前的字符
lastIndexOf()
lastIndexOf()方法从字符串末尾开始查找子字符串,从这个参数指定的位置开始向字符串开头搜索,忽略该位置之后直到字符串末尾的字符
let stringValue = "hello world";
console.log(stringValue.indexOf("o")); // 4
console.log(stringValue.lastIndexOf("o")); // 7
let stringValue = "hello world";
console.log(stringValue.indexOf("o", 6)); // 7
console.log(stringValue.lastIndexOf("o", 6)); // 4
在字符串中找到所有的目标字符串
let stringValue = "Lorem ipsum dolor sit amet, consectetur adipisicing elit";
let positions = new Array();
let pos = stringValue.indexOf("e");
while(pos > -1) {
positions.push(pos);
pos = stringValue.indexOf("e", pos + 1);
}
console.log(positions); // [3,24,32,35,52]
判断字符串中是否包含另一个字符串
从字符串中搜索传入的字符串,并返回一个表示是否包含的布尔值
startsWith()
检查开始于索引 0 的匹配项
接收可选的第二个参数,表示开始搜索的位置
endsWith()
检查开始于索 引(string.length - substring.length)的匹配项
接收可选的第二个参数,表示应该当作字符串末尾的位置。如果不提供这个参数, 8那么默认就是字符串长度。如果提供这个参数,那么就好像字符串只有那么多字符
includes()
检查整个字符串
接收可选的第二个参数,表示开始搜索的位置
let message = "foobarbaz";
console.log(message.endsWith("bar")); // false
console.log(message.endsWith("bar", 6)); // true
match()
本质上跟RegExp对象的exec()方法相同
match()接收一个参数,可以是一个正则表达式字符串,也可以是一个RegExp对象
match()返回的数组与RegExp对象的exec()方法返回的数组是一样的
如果正则表达式使用了全局标志`\g`,将返回所有匹配项组成的数组,如果没有使用全局标志,将返回第一个匹配项及其相关信息
let text = "cat, bat, sat, fat";
let pattern = /.at/;
// 等价于pattern.exec(text)
let matches = text.match(pattern);
console.log(matches.index); // 0
console.log(matches[0]); // "cat"
search()
与match()的参数一样,返回模式第一个匹配的位置索引,如果没找到则返回-1
let text = "cat, bat, sat, fat";
let pos = text.search(/at/);
console.log(pos); // 1
match()和search()的区别
match()用于返回匹配的字符串或匹配的部分
search()用于返回匹配的位置索引
substr()、slice()、substring()
都是用于提取字符串中的子字符串的方法
substr(n, m) 字符串中索引为n开始,截取m位
substring(n, m) 从索引为n的位置开始截取,到索引为m的位置结束,不包含m这一项
slice(n, m) 和substring()一样,但它可以支持负数索引,表示从字符串末尾开始计算位置
区别:
`substr()` 使用起始位置和长度作为参数,而 `slice()` 使用起始位置和结束位置作为参数
`slice()` 允许使用负数作为参数,表示从字符串末尾开始计算位置,而 `substring()` 不支持负数参数
数组操作方法
改变原数组:push、pop、shift、unshift、sort、splice、reverse
不改变原数组:concat、every、some、join、map、forEach、filter、slice、indexOf、toString、lastIndexOf、valueOf
slice和splice的区别
slice,根据传入的起始和中止下标,获取该数组范围
splice,根据传入参数个数的不同,实现删除、插入操作,直接操作原数组;第一个参数为起始下标,第二个参数为删除个数,第三个为要增加的数据
replace()
接收两个参数,第一个参数可以是一个RegExp对象或一个字符串(这个字符串不会转换伟正则表达式),第二个参数可以是一个字符串或一个函数,如果第一个参数是字符串,那么只会替换第一个子字符串,要想替换所有子字符串,第一个参数必须为正则表达式并且带全局标记
let text = "cat, bat, sat, fat";
let result = text.replace("at", "ond");
console.log(result); // "cond, bat, sat, fat"
result = text.replace(/at/g, "ong");
console.log(result); // "cond, bond, sond, fond"
map()和forEach()
Array.map() 将数组中的每个元素调用一个提供的函数,结果作为一个新的数组返回,并没有改变原来的数组
Array.forEach() 将数组中的每个元素执行提供的函数,没有返回值,直接改变原数组,与map方法区分
数组去重
- 方法一:new Set生出来的数据是Set数据结构,需要自行转换成对应结构
let array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 2, 3, 3, 3, 9, 8, 9, 's', 'o', 'o'];
Array.from(new Set(array));
- 方法二:扩展运算符
let array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 2, 3, 3, 3, 9, 8, 9, 's', 'o', 'o'];
...new Set(array);
// 或
const a = '12345678971100';
const b = [...new Set(a)].join('');
- 方法三:使用indexOf()在字符串中查找指定字符串,并返回第一个匹配项的索引
let array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 2, 3, 3, 3, 9, 8, 9, 's', 'o', 'o'];
let newarr = [];
for (var i = 0; i < array.length; i++) {
if (newarr.indexOf(array[i]) == -1) {
newarr.push(array[i]);
};
};
- 方法四:排序后进行比对
let array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 2, 3, 3, 3, 9, 8, 9, 's', 'o', 'o'];
let newarr2 = [];
array.sort(); //先将数组排序
for (var i = 0; i < array.length; i++) {
if (array[i] !== array[i + 1]) { //判断后一项 是否跟前一项一样 将后一项放入新数组
newarr2.push(array[i]);
};
};
split()和join()
split() 字符串方法,以指定的字符分割字符串,返回一个数组
join() 数组方法,以指定的字符连接数组中元素,返回一个字符串
encodeURI和encodeURIComponent
URI代表统一资源标识符(Uniform Resource Identifier)
URI包括两个主要的子集:
URL(统一资源定位符)用于定位资源
URN(统一资源名称)用于命名资源
`encodeURI` 函数用于编码整个 URI,但它不会编码属于URI组件的特殊字符,比如冒号、斜杠、问号、井号
`encodeURIComponent` 用于对 URI 中的单独组件进行编码,它会编码它发现的所有非标准字符
所以encodeURIComponent比encodeURI编码的范围更大
对应的是decodeURI()和 decodeURIComponent()
let uri = "http://www.wrox.com/illegal value.js#start";
// "http://www.wrox.com/illegal%20value.js#start"
console.log(encodeURI(uri));
// "http%3A%2F%2Fwww.wrox.com%2Fillegal%20value.js%23start"
console.log(encodeURIComponent(uri));
如何判断空对象
- JSON.stringify()转换成字符串后,跟'{}'对比
- ES6,判断Object.keys(targetObject)返回值数组的长度是否为0
- ES5,判断Object.getOwnPropertyNames(targetObject)返回的数组长度是否为0
- for...in循环判断
var、let、const
var
- 没有块的概念,可以跨块访问,不能跨函数访问
let
- 不存在变量提升
- 如果变量在let声明前使用,会报错(暂存性死区)
- 块级作用域
- 不允许重复声明
const
- 块级作用域
- 不允许重复声明
- 定义的是常量,不能修改,如果定义的是对象,可以修改对象内部的数据
- 定义的时候必须初始化(赋值)
数据类型
基本数据类型:string、number、boolean、null、undefined、symbol、bigInt
引用数据类型:object
基本数据类型存储在栈内存中,空间小,操作频繁
引用数据类型存储在堆内存中,它的地址在栈内存中,一般我们访问的就是它的地址
如何区分数据类型
let a = [1,2];
Object.prototype.toString.call(a) // '[object Array]'
this指向
- 全局作用域中的函数:非严格模式下其内部this指向window
- 对象内部的函数:其内部this指向对象本身
- 构造函数:其内部this指向生成的实例
- 由apply、call、bind改造的函数:其this指向第一个参数
- 箭头函数:箭头函数没有自己的this,看其外层是否有函数,如果有,外层函数的this就是内部箭头函数的this,如果没有,则this是window,(函数定义时的this,而不是调用时的this)
改变this指向
let a = {
name: 'sunq',
fn: function(action) {
console.log(this.name + 'love' + action);
}
};
let b = {name: 'sunLi'};
a.fn('basketball'); // sunq love basketball
a.fn.apply(b, ['basketball']); // sunLi love basketball
a.fn.call(b, 'basketball'); // sunLi love basketball
a.fn.bind(b)('basketball'); // sunLi love basketball
// bind会返回一个新的函数,如果需要传参,需要再调用该函数并传参
DOM事件流的顺序
当一个元素被点击时,先从document向下一层层捕获到该元素,然后再向上冒泡,一层层触发
事件委托
事件委托:将事件写在父级元素上,e.target是事件捕获的最小元素,即选中的元素;所以可根据e.target操作选中的元素,这样不需要给每个子元素绑定事件,代码更加简洁
获取元素的属性值
e.getAttribute(propName) 可以获取元素的所有属性的值,包括自定义属性的值
e.propName 只能获取元素本身具有的属性的值,不能获取自定义属性的值
offsetWidth、clientWidth、scrollWidth
offsetWidth/offsetHeight 包含content+padding+border+滚动条 与getBoundingRect()相同
clientWidth/clientHeight 包含content+padding
scrollWidth/scrollHeight 包含content+padding+溢出内容的尺寸
检测浏览器和设备类型
检测浏览器和设备类型 navigator.userAgent
声明式和命令式
声明式,抽象,表达做什么,而不是怎么做,如map()、filter()等高阶函数
命令式,具体,详细说明如何做,如使用循环和条件语句
检测一个变量是String类型
1. typeof 变量名
2. 变量名.constructor === String
3. Object.prototype.toString.call(变量名)
检测一个变量是Array类型
- 变量名 instanceof Array
- 变量名.constructor === Array
- Object.prototype.toString.call(变量名) === '[object Array]'
- Array.isArray(变量名)
null和undefined
null:代表一个空对象指针,使用typeof运算得到object
undefined:一个声明的变量未初始化赋值时得到的值,使用typeof运算得到undefined
target和currentTarget
target:表示触发事件的实际目标元素
currentTarget:表示事件绑定的当前元素,即事件处理程序所在的元素
原型链
每一个实例对象上有一个__proto__属性,指向构造函数的原型对象,构造函数的原型对象也是一个对象,也有__proto__属性再指向原型对象的原型对象,这样一层一层往上找,直到顶端(null)的过程就形成了一条完整的原型链
原型继承:每一个构造函数都有prototype原型属性,通过构造函数创建出来的对象都继承自改原型属性,所以可以通过更改构造函数的原型属性来实现继承,在js中继承的方式有多种,可以一个对象继承另一个对象,也可以通过原型继承的方式进行继承
new操作符具体做了什么
1. 在内存创建一个新对象
2. 把构造函数中this指向新建的对象
3. 会在构造函数上添加一个__proto__属性,指向函数的原型对象prototype
4. 判断函数返回值,如果值是引用类型就直接返回值,否则返回this(创建的新对象)
垃圾回收机制
https://juejin.cn/post/6981588276356317214
第三方库
xml-js
xml-js库中的xml2json方法是用于处理xml数据的,作用是将xml数据转换成json格式
compact: true 作用是生成的json对象会是紧凑型的,即不会有多余的空格或换行符,这样可以减少生成的json大小,使其更易于传输和处理
ignoreDeclaration: true 作用是忽略xml中的声明部分,例如`<?xml version="1.0" encoding="UTF-8"?>`
let data = xml2json(this.data.content, {
compact: true,
ignoreDeclaration: true,
})
插件
Tree shaking
在打包的过程中通过分析代码的依赖关系,识别和移除未被使用的部分,以确保最终的代码包含的内容仅限于实际使用的部分,以减少最终生成的包的大小,提高应用程序的性能
IIFE
IIFE(立即调用函数),定义时就会立即执行的函数
函数柯里化
把一个接收多个参数的函数变成接收单一参数,并且返回能够接收新参数的函数
add(1)(2)(3)(4) = 10;
function add(num){
var sum=num;
var fn=function(v){
sum+=v;
return fn
};
fn.toString=function(){
return sum
};
return fn
}
console.log(add(1)(2)(3)(4)) // 10
高阶函数
高阶函数就是对其他函数进行操作的函数
接受函数作为参数或将函数作为输出返回的函数
如:map()、filter()、reduce()
浏览器的渲染过程
- 解析HTML,构建DOM树,并行请求css/image/js
- css文件下载完成,开始构建css树
- css树构建结束后和DOM一起生成Render Tree(渲染树)
- 布局(layout):计算出每个节点在屏幕中的位置
- 显示(painting):通过显卡把页面画到屏幕上
实现继承
- 实例继承:将子构造函数的prototype指向父构造函数的一个实例
- 原型继承:将子构造函数的prototype指向父构造函数的prototype
- 构造函数绑定:使用call()或apply(),将父对象的构造函数绑定在子对象上
- 拷贝继承:把父对象的所有属性和方法,拷贝进子对象
- ES6语法extends:class ColorPoint extends Point {}
作用域链
如果当前作用域没有找到属性或方法,会向上层作用域查找,直至全局函数,这种形式就是作用域链
eval
eval的功能是把对应的字符串解析成js代码并运行
因该避免使用eval,不安全,非常耗性能(先解析成js语句,再执行)
由JSON字符串转换为JSON对象的时候可以用eval('('+ str +')')
docuent.write和innerHTML
document.write只能重绘整个页面
innerHTML可以重绘页面的某一部分
把<script>放在</body>之前和之后有什么区别
按照HTML标准,在结束后出现的<script>或任何元素的开始标签都会解析错误
虽然不符合HTML标准,但浏览器会自动容错,使实际效果和写在</body>之前没有区别
浏览器的容错机制会忽略<script>之前的,视作<script>仍在body体内
为什么js是单线程,而不是多线程
单线程是指js在执行的时候,有且只有一个主线程来处理所有的任务
目的是为了实现与浏览器交互
假如js是多线程的,我们在浏览器中同时操作一个DOM,一个线程要求浏览器在这个DOM中添加节点,而另一个线程却要求浏览器删除这个DOM节点,那么浏览器就不知道该以哪个线程为准,所以js选择只用一个主线程来执行代码,以此来保证程序执行的一致性
节流和防抖
区别:防抖只会在最后一次事件后执行函数,节流不管事件多么频繁,都会保证在规定时间段内触发事件函数
防抖:原理是维护一个定时器,将很多相同的操作合并成一个,规定在depay后触发函数,如果在此之前触发函数,则取消之前的计时重新计时,只有最后一次操作能被触发,例如:实时搜索的input,一直输入就不发送
let input = document.querySelector("input");
let time = null;//time用来控制事件的触发
input.addEventListener('input',function(){
//防抖语句,把以前的定时删除,只执行最后一次
if(time !== null){
clearTimeout(time);
}
time = setTimeout(() => {
console.log(this.value);//业务实现语句,这里的this指向的是input
},500)
})
节流:原理是判断是否达到一定的时间来触发事件,某个时间段内只能触发一次函数
function throttle(fn, time) {//连续触发事件 规定的时间
let flag = false;
return function () {
//使用标识判断是否在规定的时间内重复触发了函数,没有就触发,有就不触发
if (!flag) {//不为假时 执行以下
fn();//触发事件
flag = true;//为真
setTimeout(() => {//超时调用(在规定的时间内只执行一次)
flag = false;
}, time);
}
}
}
mybtn.onclick = throttle(btn, 3000);//单击事件 节流(btn,3s时间)
箭头函数
- 箭头函数的this指向是固定的,普通函数的this指向是可变的
let a = {
name: 'sunq',
fn:function(action){
console.log(this.name + ' love ' + action);
}
}
let b = {name:'sunLi'}
// 正常的this指向调用他的对象
a.fn('basketball'); // sunq love basketball
// 改变this指向
a.fn.apply(b,['football']); // sunLi love football
// 如果将a对象的fn函数改成箭头函数,this.name会是undefined
// 箭头函数的this指向不会改变,且总是指向函数定义生效时所在的对象。
- 不可以当作构造函数,不可以对箭头函数使用new命令,否则会抛出一个错误
var Person = function(name){
this.name = name;
}
let sunq = new Person('sq'); // {name: 'sq'}
var Person = (name) => {
this.name = name;
}
let sunq = new Person('sq'); // 报错 Person is not a constructor
- 无aruments对象
- 不可以使用yield命令,因此箭头函数不能用作Generator函数
- 箭头函数没有原型,原型是undefined
递归
递归:函数在内部调用其本身
优点:结构清晰,可读性强
缺点:效率低,调用栈可能溢出,每一次函数调用会在内存栈中分配空间,而每个进程的栈内存是有限的,当调用的层次太多时,就会超出栈的容量,从而导致栈溢出
promise
抽象的说法:Promise是js中对异步编程进行操作的新的解决方法
具体的说法:
- 从语法上说,Promise是一个构造函数
- 从功能上说,Promise对象可以封装一个异步操作,并获取最后异步操作得到的结果
可以解决回调地狱的问题,也就是异步深层嵌套问题
Promise的构造函数接收一个参数(函数类型的参数),并要求该函数类型的参数接收两个参数resolve和reject,分别表示异步操作执行成功后的回调函数和执行失败后的回调函数,resolve将Promise的状态从pending置为fullfilled,reject将Promise的状态从pending置为rejected
先并行请求两个接口后,再请求第三个接口,如何处理
使用Promise.all(),将两个promise传入all方法,拿到异步结果再请求第三个
for in和for of
for in适合遍历对象,遍历数组拿到的是索引(即键名)
for of适合遍历数组,遍历数组直接拿到数组的项,只能遍历具备iterator接口的类型
多个数据请求,如何顺序执行
使用promise的then方法,或者写多个promise,async中使用await顺序执行
async和await
async用于声明一个函数,await用于等待一个异步方法执行完成,async函数返回的是一个promise对象,可以用.then方法添加回调函数,在函数执行过程中,一旦遇到await就先返回,等到这个异步操作完成之后,它再进行函数体内后面的这个语句
async是一个修饰符,async定义的函数会默认的返回一个Promise对象resolve的值,因此对async函数可以直接进行then操作,返回的值即为then方法的传入函数
==和===的区别
- ==是非严格意义上的相等,值相等就相等,所以会先转换为同一类型再进行比较
- ===是严格意义上的相等,会比较两边的数据类型和值大小,值和引用地址都相等才相等,会先比较数据类型,数据类型不一致直接返回false
严格模式的限制
- 变量必须声明后再使用
- 函数的参数不能有同名属性,否则报错
- 不能使用with语句
- 禁止this指向全局对象
虚拟dom实现原理
- 用js对象模拟真实DOM树,对真实DOM进行抽象
- diff算法:比较两颗虚拟树的差异
- patch算法:将两个虚拟DOM对象的差异应用到真实的DOM树
call、apply、bind
都是改变this指向的方法
apply:和call基本一致,传参方式不同,call传递的是实参列表,apply传递的是数组
bind:语法和call一模一样,但bind是等待执行的,call是立即执行的
获取原型
__proto__
事件冒泡
同一事件,自子元素冒泡向父元素(自底向上)
事件捕获
同一事件,自父元素捕获至子元素(事件源)(自顶向下),IE没有捕获事件
触发顺序:先捕获,后冒泡
事件循环机制
js是单线程语言,在执行js代码时所有同步任务都在主线程上执行,形成一个执行栈,除了主线程外,还存在一个任务队列存放异步任务,当执行完所有同步任务后,就会从任务队列中逐个取异步任务放入执行栈中执行,而异步任务又分为宏任务和微任务
异步任务执行顺序:
- 先执行微任务,执行完全部的微任务再执行宏任务
- 执行宏任务结束后查看有没有产生新的微任务,若产生新的微任务就把新的微任务执行完
- 开启下一轮事件循环
也就是有同步任务就先执行同步任务,同步任务执行完就执行异步任务,异步任务先执行微任务,执行完全部微任务再执行宏任务,如此循环,就是事件循环机制
实现重载和多态
- 重载是函数特征之一,表现为在一个类中,同名不同参的方法会分别被调用产生不同的结果,js本身不支持重载,所以只能通过其它方式实现,arguments检测传参的个数,然后再执行不同的方式
- 多态是面向对象的特征之一,表现为不同对象调用相同方法会产生不同的结果
空位合并运算符??和可选链式取值?.
空位合并运算符,仅会在exp1的值为null或undefined时计算并返回exp2的值
本质上都是帮助我们减少一些判断值是否是null或undefined的条件语句的书写
computed: {
message() {
return this.data.msgBody ?? {};
},
},
// 这段代码使用了空值合并运算符 `??`。它的作用是判断`this.data.msgBody`是否为null或undefined,如果是,就返回空对象 `{}`,否则返回`this.data.msgBody`的值
// 可选链式取值?.
console.log(obj !== null && obj !== undefined ? obj.prop : undefined)
console.log(obj?.prop);
Set集合、Map集合
Set集合
- 类似于数组,key和value值相同
- 特性:不允许重复值出现
- 应用:数组去重Array.form(new Set(arr))
Map集合
- 类似于对象,key-value对应的集合
- 特点:key值不局限于字符串,可以是任意数据类型
let map = new Map();
map.set({name: '张三'}, [1, 2, 3]);
// forEach方法内部回调函数形参位置,先是value,再是k
import和require
- commonjs用于nodejs,同步加载模块,运行时加载,输入的是一个值的拷贝
- ES6的import为了不卡顿,异步加载模块,编译时输出接口,输出的是值的引用
新版Nodejs也支持使用import,但是需要修改文件后缀名.mjs,或者在package.json中,制定type字段为module
JSON
JSON(JavaScriptObject Notation,JS对象简谱)是一种轻量级的数据交换格式
- JSON可以将JavaScript对象中表示的一组数据转换为字符串,然后就可以在函数之间轻松地传递这个字符串,或者在异步程序中将字符串从Web客户端传递给服务器端程序
- JSON可以表示比名称/值对更复杂的结构,可以表示数组和复杂的对象,而不仅仅是键和值的简单列表
Object.freeze()
使用Object.freeze()设置为不可配置之后的对象属性时,不会为对象加上setter、getter等数据劫持的方法
addEventListener和onclick绑定点击事件的区别
1. 多次绑定
使用 onclick 绑定事件时,每次给元素设置 onclick 属性时,都会覆盖之前的点击事件处理程序,只会执行最后一个设置的事件处理函数
使用 addEventListener 绑定事件时,可以绑定多个事件处理程序,它们会依次执行,而不会相互覆盖
2. 移除事件
使用 onclick 绑定的事件无法直接移除,需要将 onclick 属性设置为 null
使用 addEventListener 绑定的事件可以使用 removeEventListener 来移除事件
3. 事件冒泡和捕获
使用 addEventListener 可以指定事件是在捕获阶段执行还是在冒泡阶段执行,而 onclick 只能在冒泡阶段执行
标签:知识,console,函数,对象,javascript,let,字符串,前端开发,log From: https://blog.csdn.net/qq_45937484/article/details/142514841