文章目录
- ES6新增的proxy
- 手写,proxy访问某对象输出别的数字
- 深度拷贝,为啥无法使用JSON.parse(JSON.stringify(obj))
- 异步编程有哪些,async await来由,本质原理是什么
- 事件队列输出题
- 第一题
- 第二题
- 第三题
- 粘性布局的原理,以及要自己实现应该怎么办
- this指向题
- `window.onload` 和 `jquery`的`$(document).ready()`的区别
- 浏览器在渲染页面的时候到底做了些什么?
ES6新增的proxy
ES6引入了Proxy(代理)对象,它提供了一种拦截和自定义操作对象行为的机制。Proxy允许你在目标对象的基础上封装一个代理对象,通过定义各种拦截器来拦截对目标对象的访问和修改。
get(target, property, receiver): 拦截对目标对象属性的读取操作。
set(target, property, value, receiver): 拦截对目标对象属性的设置操作。
apply(target, thisArg, argumentsList): 拦截对目标对象的函数调用。
construct(target, argumentsList, newTarget): 拦截对目标对象的new操作符。
更多内容请参考
把es6 proxy 和 vue3.0的proxy一起学了
手写,proxy访问某对象输出别的数字
const handler = {
get: function (target, key) {
return Math.random() * 10;
},
};
const obj = {
name: "dx",
age: "18",
};
const p = new Proxy(obj, handler);
console.log(p.name)
深度拷贝,为啥无法使用JSON.parse(JSON.stringify(obj))
因为转换过后,很多js的数据会发生变化,无法与原数据完全一致。
哪些js中的数据类型或者哪些场景下,JSON无法转化?
new Date()
转变后一个Date对象变成了时间字符串
JSON.parse(JSON.stringify({a: new Date()})); // {a: '2023-11-11T07:13:19.302Z'}
- 对象中出现循环引用
// 循环引用
const jsonStr = '{ "key": {} }';
const obj = JSON.parse(jsonStr);
obj.key.circularReference = obj; // 循环引用
JSON.stringify(obj); // 在调用 JSON.stringify 时,会抛出 TypeError
- function
转变后是一个空对象
JSON.parse(JSON.stringify({a: function(){}})); // {}
undefined
转变后是一个空对象
JSON.parse(JSON.stringify({a: undefined})); // {}
NaN Infinity -Infinity
转变后都会变为null
JSON.parse(JSON.stringify({a: NaN})); // {a: null}
- new RegExp()
属性还在,但值变为空对象
JSON.parse(JSON.stringify({a: new RegExp('\\ww')})); // {a: {}}
- new Error()
属性还在,但值变为空对象
JSON.parse(JSON.stringify({a: new Error('xxxx')})); // {a: {}}
所以,为了保证深度拷贝的万无一失,需要考虑各种情况,不是简单的JSON就能完成的,即使以上情况都没有,为了确保 JSON.parse() 不会失败,提供的 JSON 字符串应该是有效的、符合规范的,并且不包含不支持的数据类型。在处理可能导致异常的情况时,最好使用 try...catch
来捕获异常并进行相应的处理。
深度拷贝 leader:深拷贝有这5个段位,你只是青铜段位?还想涨薪?
异步编程有哪些,async await来由,本质原理是什么
异步编程的方式:回调函数,Promise,async await
async/await 的来由:
它的目标是简化和改善异步代码的可读性和可维护性,使开发者更容易理解和编写异步操作。
async/await 的本质原理:
async 函数: 使用 async 关键字声明的函数始终返回一个 Promise 对象。在 async 函数内部,通过 await 关键字等待异步操作的结果。当 await 后面的表达式解决时,函数将会从暂停的地方继续执行。
await 表达式: await 用于等待一个 Promise 解决,然后获取解决的值。在等待期间,async 函数会被暂停,允许其他代码执行。如果 Promise 解决为拒绝,await 将抛出一个异常,可以通过 try…catch 捕获。
async/await 的本质是基于 Promise,它提供了一种更直观的语法来编写异步代码,使得异步操作更容易理解和维护。在底层,async/await 仍然是依赖于 Promise 的实现。
promise成功时
async function fetchDataAsync() {
const data = await new Promise((res,rej) => {res(1)});
console.log(data); // 1
}
const a = fetchDataAsync()
console.log(a) // Promise {<fulfilled>: undefined} 一个promise对象,状态是 fulfilled
promise报错时
async function fetchDataAsync() {
const data = await new Promise((res,rej) => {rej('出错了')});
console.log(data); // Uncaught (in promise) 出错了
}
const a = fetchDataAsync()
console.log(a) // Promise {<pending>} promise 对象,状态是rejected
如果await后根本就不是promise
async function fetchDataAsync() {
const data = await console.log('不是promise');
console.log(data); // undefined
}
const d = fetchDataAsync()
console.log(d) // Promise {<pending>} 一个promise对象,状态是 fulfilled
事件队列输出题
第一题
console.log('Start');
setTimeout(function() {
console.log('Timeout');
}, 0);
Promise.resolve().then(function() {
console.log('Promise');
});
new Promise((res,rej) => {
console.log('Promise2')
res(1)
}).catch((e) => {
console.log('出错了')
})
console.log('End');
Start 最先,
setTimeout属于宏任务,往宏任务队列里放,
Promise then里面的回调,属于微任务,往微任务队列里放,
Promise2是同步的,它第二,
res(1) 这儿是resolve了,所以catch不会执行,不用放到微任务队列里
End 第三
所有同步执行完了,开始执行异步,先将微任务队列全部执行(队列先进先出)。
Promise 第四
微任务队列执行完了,执行一个宏任务队列里的任务
Timeout 最后
第二题
async function asyncFunction() {
console.log('Async Start');
const promise = new Promise((resolve) => {
setTimeout(() => {
resolve('Async Timeout');
}, 0);
});
const result = await promise;
console.log(result);
console.log('Async End');
}
console.log('Script Start');
asyncFunction();
console.log('Script End');
Script Start 第一,
asyncFunction执行函数体 Async Start第二,
setTimeout 放入宏任务队列,先不执行
遇到await ,不执行asyncFunction函数体await后的内容,跳出函数体
Script End 第三
同步执行完,开始执行异步,微任务队列全部执行,没有,执行一个宏任务
宏任务 resolve('Async Timeout')
执行,result拿到了值,接着执行asyncFunction未执行完的函数体
Async Timeout 第四
Async End 第五
第三题
async function async1() {
console.log('async1 start');
await async2();
console.log('asnyc1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(() => {
console.log('setTimeOut');
}, 0);
async1();
new Promise(function (reslove) {
console.log('promise1');
reslove();
}).then(function () {
console.log('promise2');
})
console.log('script end');
同步任务
// script start
// async1 start
// async2
// promise1
// script end
微任务
// asnyc1 end
// promise2
宏任务
// setTimeOut
粘性布局的原理,以及要自己实现应该怎么办
原理就是监听滚动条的变化,每一次滚动条变化后,就计算dom距离顶部或者底部的距离,如果距离小于我们设计的值,就让dom 的position改为fixed 或者absolute(当粘性布局不是对于window来说时)。
请参考Affix组件 vue3 组件篇 affix
this指向题
var name = 1
var obj = {
name: 2,
getName: function () {
console.log(this.name)
}
}
setTimeout(obj.getName, 0)
输出结果是什么? 1 怎么改才能是2
var name = 1
var obj = {
name: 2,
getName: function () {
console.log(this.name)
}
}
setTimeout(function(){ obj.getName() }, 0)
原题不改的情况下,严格模式会发生什么? // 会报错,严格模式,this在全局作用域中指向undefined。
JavaScript 的严格模式(strict mode)是一种在语言层面上的约束,它被设计用来提供更强的错误检测和更安全的代码。启用严格模式的方式是在脚本或函数的开头添加 'use strict';
。
以下是严格模式的一些主要特性:
-
变量声明必须使用
var
、let
或const
:- 在严格模式下,未经声明直接赋值给变量会导致引发错误。
-
全局变量显式声明:
- 在严格模式下,全局变量必须使用
var
关键字显式声明。
- 在严格模式下,全局变量必须使用
-
删除不可删除的属性时会引发错误:
- 在严格模式下,尝试删除不可删除的属性会引发错误。
-
函数参数命名唯一性:
- 在严格模式下,函数的参数不能有重复的名称。
-
禁止使用
with
语句:- 在严格模式下,使用
with
语句会导致引发错误。
- 在严格模式下,使用
-
禁止给只读属性赋值:
- 在严格模式下,给只读属性(如
Math.PI
)赋值会引发错误。
- 在严格模式下,给只读属性(如
-
禁止删除变量:
- 在严格模式下,使用
delete
操作符删除变量、函数或函数参数会引发错误。
- 在严格模式下,使用
-
保留字的限制:
- 在严格模式下,一些在 ECMAScript 5 中被保留但没有特定用途的关键字变得不能用作变量名或函数名。
-
this
在全局作用域中为undefined
:- 在严格模式下,全局作用域中函数的
this
值为undefined
,而不是全局对象。
- 在严格模式下,全局作用域中函数的
-
禁止使用
arguments.callee
和arguments.caller
:- 在严格模式下,
arguments.callee
和arguments.caller
都会引发错误。
- 在严格模式下,
-
eval
在其自己的词法作用域中运行:- 在严格模式下,
eval
不再在调用时共享变量环境,它有自己的词法作用域。
- 在严格模式下,
-
eval
和arguments
不能被重新赋值:- 在严格模式下,
eval
和arguments
不能被重新赋值。
- 在严格模式下,
-
不允许给
eval
和arguments
传递字符串:- 在严格模式下,给
eval
和arguments
传递字符串会创建新的变量,而不是使用当前作用域中的变量。
- 在严格模式下,给
使用严格模式有助于减少一些常见的编码错误,提高代码的可维护性和安全性。
window.onload
和 jquery
的$(document).ready()
的区别
window.onload
浏览器所有的资源(图片,视频等多媒体文件)加载后触发load事件
$(document).ready()
是在dom解析完成后就触发。$(document).ready()
在 window.onload
之前触发。
浏览器在渲染页面的时候到底做了些什么?
这里从建立tcp链接之后开始讲起,之前的过程中,浏览器经历了什么暂时不讨论,如果关心这一部分的同学,可以查阅一下,在浏览器输入url后,浏览器做了。
- 客户端向服务器请求下载index.html
- 下载完成后,解析index.html文件,从上至下。解析的同时,创建Document对象,没错就是平时用的
document
,比如document.createElement('div')
, 解析HTML元素,和它们的文本内容,添加Element对象和Text节点到文档中, 这个阶段,document.readyState = 'loading'
,我们也称之为开始创建DomTree。 - 如果遇到link外部css,创建线程加载,我们称之为创建CssTree,与此同时继续解析文档。
- 遇到
script
外部js,并且没有设置async
和defer
,浏览器停止解析html,开始下载对应的js,下载完成后,执行js,等执行完成后,继续解析html。 - 遇到
script
外部js,设置了async
, 浏览器创建新的线程下载js,于此同时继续解析html,等js下载完成后,暂停html的解析,开始执行下载的js,js执行完成后,继续解析html。(异步禁止使用document.write()
这会清空之前解析的dom)。 - 遇到
script
外部js,设置了defer
(只对ie9之前的版本有效),浏览器创建新的线程下载js,于此同时继续解析html,等js下载完成后,js不会执行,浏览器继续解析html。(异步禁止使用document.write()
这会清空之前解析的dom) - 遇到
img
,video
,audio
,iframe
等多媒体标签时,浏览器会创建新的线程,去异步加载src,同时继续解析html。 - html文档全部解析完成后,
document.readyState='interactive'
所有设置defer
的js脚本会按照顺序执行。 - document对象触发DOMContentLoader事件,这也标志着程序执行从同步脚本阶段,转化为事件驱动阶段。
- 当所有的异步脚本加载完成并执行后,
img
等多媒体资源加载完成后,document.readyState = 'complete'
,window
对象会触发load事件。 - 从此,异步响应方式处理用户输入,网络事件等。
<!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>
</body>
</html>
<script>
console.log(document.readyState) // loading
document.onreadystatechange = function () {
console.log(document.readyState) // 先后打印 interactive, complete
}
// 当dom解析完成时执行
document.addEventListener('DOMContentLoaded', function () {
console.log('DOMContentLoaded') // 在interactive之后打印
}, false)
window.addEventListener('load', function () {
console.log('load') // 在complete之后打印,最后
})
</script>
loading
interactive
DOMContentLoaded
complete
load
DOMContentLoaded
是比较重要的监听事件,会在dom解析完成时执行,与jquery的$(document).ready(function(){})
功能一致。
而上文第10步也提到了,load事件是在所有资源加载完成后才触发。