复杂类型引用
函数参数
函数的参数赋值问题
先套用js高程一句话——ECMAScript 中所有函数的参数都是按值传递
什么叫做按值传递???
可以简单理解为生成一个新的变量和需要传递的参数的值一样,就像把值从一个变量复制到另一个变量一样。
var a = 1;
function num(o){
o = 2;
return o;
}
num(a);
console.log(a);//1
看上面的代码把a(值为1)当做变量运行函数num,函数内更改变量的值为2,但其实更改的不是a,而是按值传递生成的新的变量,所以a并不会产生改变。
那么再看当一个对象传递进去的情况呢??
var a = {b:1};
function obj(o){
o.b = 2;
return o;
}
obj(a);
console.log(a);
//{b:2}
为什么它变了,不应该按值传递本身不发生改变吗????/
这里我们就需要理解对象在js中的引用与赋值了,我们一开始定义a={b:1},这里把他拆分为两部分可以,首先创建一个对象{b:1}并保存在内存堆里面,然后我们可以知道对象{b:1}在内存中的地址(假设为000000),那么我就把这个地址000000给a。这样a就指向这个对象{b:1}咯。
理解了对象的引用方式再来看上面的代码,再来理解此处的按值传递。这里并不是生成一个新的{b:1}当做参数,而是生成一个新的引用地址(假设111111),它和原先的引用地址(000000)同时指向这个对象。所以不管在哪里更改这个对象的属性,最终都导致最终的结果变了 为{b:2}。
js对象克隆
具体看《克隆》章节
对象引用
let obj = { name: "张三" }
let objNew = obj
obj = { name: "李四" }
console.log(obj); // { name: "李四" }
console.log(objNew); // 还是指向原来的内容 { name: "张三" }
一定要注意:引用类型赋值给的是堆内存中的引用值;
宏任务、微任务
宏任务分为:
- 同步任务:按照顺序执行,只有前一个任务完成后,才能执行后一个任务
- 异步任务:不直接执行,只有满足触发条件时,相关的线程将该异步任务推进任务队列中,等待JS引擎主线程上的任务执行完毕时才开始执行,例如异步Ajax、DOM事件,setTimeout等。
异步任务又分为宏任务和微任务两种。
-
异步宏任务包括:
setTimeout,setInterval,setImmediate,I/O,UI Rendering,XmlHttpRequest,DOM事件 -
异步微任务包括:Promise 的回调、node 中的 process.nextTick 、对 Dom 变化监听的 MutationObserver,Object.observe。
微任务先于宏任务执行
微任务微任务是ES6和Node环境下的,主要 API 有:Promise,process.nextTick。
微任务的执行在宏任务的同步任务之后,在异步任务之前。
console.log('1'); // 宏任务 同步
setTimeout(function() {
console.log('2'); // 宏任务 异步
})
new Promise(function(resolve) {
console.log('3'); // 宏任务 同步
resolve();
}).then(function() {
console.log('4') // 微任务
})
console.log('5') // 宏任务 同步
// 13542
console.log('1');
setTimeout(function() {
console.log('2');
})
new Promise(function(resolve) {
console.log('3');
setTimeout(function(){
console.log('6')
})
resolve();
}).then(function() {
console.log('4')
})
console.log('5')
//135426
注意promise的then里面才能微任务,new Promise本身是个同步任务
练习:
console.log(1) // 宏任务 同步
setTimeout(()=>{ // 宏任务 异步
console.log(2) // 宏任务 同步
Promise.resolve().then(data=>{
console.log(3) // 微任务
})
console.log(4) // 宏任务 同步
},0)
new Promise((resole,reject)=>{
console.log(5) // 宏任务 同步
resole(6)
}).then(data=>{
console.log(data) // 微任务
console.log(7) // 微任务
})
console.log(8) // 宏任务 同步
// 158 67 243
async/await
这玩意和异步任务有啥关系哈
-
参考:www.cnblogs.com/rainbowLove…
async function async1(){ console.log("1"); await async2(); // async2(); console.log("2"); } async function async2(){ console.log("3"); } console.log("4"); setTimeout(() => { console.log("5"); Promise.resolve().then(function(){ console.log("6"); }); }, 0); setTimeout(() => { console.log("7"); Promise.resolve().then(function(){ console.log("8"); }); }, 0); async1(); new Promise(function(resolve){ console.log("9"); resolve(); }).then(function(){ console.log("10"); }); console.log("11");
//结果 // 4 => 1 => 3 => 9 => 11 => 2 => 10 => 5 => 6 => 7 => 8
过程分析:
- 4
- 异步宏任务塞入
- 执行async1
- 1
- 执行async2
- 3
- 将async1中await后面的代码放入微任务中(2)
- 9
- 将promise的then放入微任务中(10)
- 11
- 执行微任务
- 2
- 10
- 执行异步宏任务
- 5
- 6
- 7
- 8
注意点:
- await后面的代码会放入微任务中,就算有Promise的then里的微任务,是按上下文顺序加入栈的,先加入的先执行
- setTimeout中宏任务是当前setTimeout的上下文中的,不必和外界联系
再来看几个简单例子:
function test2(){
async function async1(){
console.log("1");
await fn();
console.log("2");
}
function fn(){
console.log("3");
}
async1();
new Promise((resolve,reject)=>{
console.log("4");
resolve();
}).then(_=>{
console.log("5");
});
}
// test2();
// 结果13425
function test3(){
async function async1(){
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2(){
console.log('async2')
}
console.log('script start')
setTimeout(function(){
console.log('setTimeout')
},0)
async1();
new Promise(function(resolve){
console.log('promise1')
resolve();
}).then(function(){
console.log('promise2')
})
console.log('script end')
}
test3();
// 输出
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
node中
console.log('1');
setTimeout(function() {
console.log('2');
process.nextTick(function() {
console.log('3');
})
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
console.log('5')
})
})
process.nextTick(function() {
console.log('6');
})
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
console.log('8')
})
setTimeout(function() {
console.log('9');
process.nextTick(function() {
console.log('10');
})
new Promise(function(resolve) {
console.log('11');
resolve();
}).then(function() {
console.log('12')
})
})
1、7、6、8 2、4、3、5 9、11、10、12
promise
事件循环机制-event loop
资料
- Vue的$nextTick的原理juejin.im/post/688177…
- df-web:interview\Front-End-Interview-Notebook-master\JavaScript\JavaScript.md 【95】
事件循环机制是指浏览器还会js的,这个问题不应该分开说,是两者一起的;比如说浏览器里面有GUI线程也有http线程,js里面有宏任务微任务,都是掺杂在一起的;
Event Loop是由javascript宿主环境(像浏览器)来实现的;
我们都知道,JavaScript是单线程的,也就是说,JavaScript在执行代码的时候,只有一个主线程来处理所有的任务。 那么要怎么处理异步代码(如I/O操作)呢?那就是我们即将要说的事件循环机制啦。
执行栈和任务队列
JavaScript在执行代码的时候,是从上往下按顺序执行代码的;
当遇到同步任务的时候,会把同步任务加入执行栈中执行;
遇到异步任务的时候,不会立刻执行,而是加入到任务队列中,等待栈中同步任务都执行完之后,再执行任务队列中的异步任务;
执行栈、web apis代表一些浏览器异步处理进程、callback queue代表事件队列;
宏任务(Macro Task)和微任务(Micro Task)
异步任务又分为宏任务和微任务两种。
异步宏任务包括:setTimeout, setInterval, setImmediate(Node.js), I/O, UI Rendering。
异步微任务包括:process.nextTick(Node.js), Promise, MutationObserver, Object.observe
window.setImmediate(非标准)。
微任务先于宏任务执行;
事件循环过程
-
处理同步任务,依次把同步任务加入到执行栈中执行
-
等待执行栈中的同步任务都处理完成,处理任务队列
-
按顺序执行所有微任务
-
进行必要的UI渲染
-
执行异步宏任务
-
开始下一轮事件循环
this.$nextTick() 的作用:
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,该方法回调内可以获取更新后的 DOM。
Vue 是异步执行 DOM 更新。只要观察到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作上非常重要。
注意点:
- 规范允许浏览器自己选择是否更新视图。也就是说可能不是每轮事件循环都去更新视图,只在有必要的时候才更新视图。
- 执行栈和事件队列是不同的概念。执行栈是同步代码执行时执行上下文存在的空间。事件队列是异步事件返回结果时按先后顺序排列所形成的。
做题理解
var start = new Date()
setTimeout(function() {
console.log('时间间隔:', new Date() - start + 'ms')
}, 5000)
while (new Date() - start < 3000) {} // 输出 50xx
// or
while (new Date() - start < 6000) {} // 输出 60xx
注意:这就证明定时器的时间是加入到异步宏任务队列时就已经在计时了,和js主线程是一起,而并不是等js主线程执行完再计时;相当于setTimeout就是等待固定时候后,就把任务添加到执行栈中,当然了要等前面的js主线程执行完了,才会去执行异步的setTimeout;
js中六种错误类型
1.SyntaxError(语法错误) 解析代码时发生的错误
例子:var 1a; //Uncaught SyntaxError: Invalid or unexpected token
2.ReferenceError(引用错误)
1.未声明不存在的量
例子:console.log(a);//Uncaught ReferenceError: a is not defined
2.将变量赋值给一个无法被赋值的对象
例子:console.log() = 1;//Uncaught ReferenceError: Invalid left-hand side in assignment
3.RangeError(范围错误) 超出有效范围
例子:var a = new Array(-1);//长度为-1的数组 Uncaught RangeError: Invalid array length
4.TypeError(类型错误)
1.变量或参数不是预期类型,比如 对字符串、布尔值、数值等原始类型的值使用new命令,就会抛出错误,因为new命名的参数应该是一个构造函数
例子:var a = new 123;//Uncaught TypeError: 123 is not a constructor
5.URIError(URI错误)
与url相关函数参数不正确,主要是encodeURI()、decodeURI()、encodeURIComponent()、decodeURIComponent()、escape()和unescape()这六个函数
6.EvalError(eval错误)
eval函数没有被正确执行
垃圾回收机制
- JavaScript深入理解之垃圾收集
- 极客时间 阿里云 《浏览器工作原理与实践-13 | 垃圾回收:垃圾数据是如何自动回收的?》
关键字
-
标记清除(并不是js的垃圾回收机制)
-
原理:变量标记、根据执行情况将还可以访问到的变量去掉标记、清除还带有标记的变量
-
缺点:
-
效率不高,在标记清除阶段,整个程序会等待
-
会产生内存碎片
-
引用计数
-
原理:引用了次数+1,没有引用了次数-1,释放引用次数为0的内存
-
缺点:
-
循环引用,不能被清除,必须手动赋值null