2022.4.8
189. vue/react为什么要在列表组件中写key,起作用是什么?
key的作用就是给每一个vNOde一个唯一的key,通过key可以更准确更快的拿到vNode.
vue和react都是采用diff算法来对比新旧虚拟节点,从而更新节点。当新节点和旧节点头尾交叉对比没有结果时,会根据新节点的key去对比旧节点数组中的key,从而找到相应的旧节点。如果没找到就认为是一个新增节点。而如果没有key,那么就会采用便利查找的方式无法找到对应的旧节点。
在不带key的情况下,节点可以进行复用,省去了操作dom的开销,只适用于简单的无状态组件的渲染。虽然带上唯一的key会增加开销,但是能保证组件的状态正确,而且用户基本感受不到差距。
190. vue开发遇到的首页加载白屏问题如何解决?
- ssr
- 使用Gzip压缩,减少文件体积,加快首屏页面打开速度
- 骨架屏
- 外链css,js文件,很多时候我们在main,js中直接import一些ui库或css文件,可以在index.html,通过script外链引入,这样就不会通过我们的webpack打包
- 原始方法-加loading
191. promise.all()的作用,什么时候会用?
promise的all()方法提供了并行执行异步操作的能力,即在所有异步操作执行完成后才执行回调,all()里面接收一个数组,数组中每个元素都返回一个promise对象。
promise.all()可以用来执行不关心执行顺序,只关心回来的结果的顺序的那种需求。
2022.4.7
186. vue的v-bind直接传入对象绑定,和v-bind:加冒号绑定有什么区别。
没什么区别,类似于一种简写方式而已
187. 请求数据一般放在created还是mounted,为什么。
如果是影响布局的放在mounted里,不影响的两者皆可。
188. 后端返回一个可执行的js方法字符串 举例 function (){console.log('打印')}。前端如何去执行
使用eval()
2022.4.6
183. 对于React和Vue的理解,以及他们的异同。
184.computed和watch的区别。
- computed和watch都是基于watcher来实现的
- computed属性是具备缓存的,依赖的值不发生变化,对其取值时计算属性方法不会重新计算
- watch则是监控值变化,当值变化时回调对应的回调函数。
185. 路由hash和history模式的区别。
2022.4.2
180.说说this
this是执行上下文中的一个属性,它指向最后一次调用这个方法的对象。在实际开发中,this的指向可以通过四种调用模式来判断。
- 函数调用模式,当一个函数不是一个对象的属性时,直接作为函数来调用时,this指向全局对象
- 方法调用模式,如果一个函数作为一个对象的方法来调用时,this指向这个对象
- 构造器调用模式,如果一个函数用new调用时,函数执行前会新创建一个对象,this指向这个新创建的对象
- apply,call,bind调用模式,这三个方法都可以显式的指定调用函数的this指向。
- apply方法接收两个参数:一个是this绑定的对象,一个是参数数组。
- call方法接收的参数,第一个是this绑定的对象,后买呢其余参数是传入函数执行的参数,也就是说,在使用call()方法时,传递给函数的参数必须逐个列举出来。
- bing方法通过传入一个对象,返回一个this绑定了传入对象的新函数。这个函数的this指向除了使用new时会被改变,其他情况下都不会改变。 这四种方式,使用构造器调用模式的优先级最高,然后是apply、call、bind调用模式,然后是方法调用模式,然后是函数调用模式。
181.说说模块化
182.数组去重
- es6方法
const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
Array.from(new Set(array)); // [1, 2, 3, 5, 9, 8]
- es5方法:使用map存储不重复的数字
const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
uniqueArray(array); // [1, 2, 3, 5, 9, 8]
function uniqueArray(array) {
let map = {};
let res = [];
for(var i = 0; i < array.length; i++) {
if(!map.hasOwnProperty([array[i]])) {
map[array[i]] = 1;
res.push(array[i]);
}
}
return res;
}
2022.4.1
177. 下列代码输出结果是?
Promise.resolve(1).then(2).then(Promise.resolve(3)).then(console.log)
- 如果then方法里传的不是函数,可以认为是then(null),相当于没有传回调函数,任务状态和前一个任务一致
Promise.resolve(1).then(Promise.resolve(3)).then(console.log) - then(Promise.resolve(3))传的是一个promise对象,也是无效的->
Promise.resole(1).then(console.log) Promise.resole(1).then(console.log)->console.log(1)
178. 使用 JavaScript Proxy 实现简单的数据绑定
<body>
hello,world
<input type="text" id="model">
<p id="word"></p>
</body>
<script>
const model = document.getElementById("model")
const word = document.getElementById("word")
var obj = {}
const newObj = new Proxy(obj,{
get:function(target,key,receiver) {
console.log(`getting ${key}!`)
},
set: function(target,key,value,receiver) {
console.log('setting',target,key,value,receiver)
if(key === "text") {
model.value = value
word.innerHTML = value
}
return Reflect.set(target,key,value,receiver)
}
})
model.addEventListener("keyup",function(e){
newObj.text = e.target.value
})
</script>
179. webpack 中是如何处理图片的?
webpack本身不处理图片,他会把图片内容仍然当作js代码来解析,结果就是报错,打包失败。如果要处理图片,需要通过loader来处理。其中,url-loader会把图片转换为base64编码,然后得到一个detaurl,file-loader则会将图片生成到打包目录中,然后得到一个资源路径。但无论是哪一种loader,他们的核心功能,都是把图片内容转换成js代码,因为只有转换成js代码,webpack才能识别。
2022.3.31
173. commonjs与esModule有什么区别
174.Vue 3.0 所采用的 Composition Api 与 Vue 2使用的Options Api 的区别
Composition Api可以将相同的业务部份的代码写在一起,可复用性更高,代码结构更清晰。composition Api 使用的api接口是通过import引入,所以未使用的api方法打包的时候不会被打包进来,减少打包体积,options会全部引入不管用没用。
175. webpack构建过程有什么优化手段?webpack优化打包体积方法
- 第三方模块缓存,下次构建取缓存中的数据,减少打包时间。
- 第三方库按需加载
- ui组件按页面按需加载
- 若第三方库有min.js版本则引入min.js版本
- moment.js去除语言包,或直接更换为day.js,他们的api是相同的,day.js只有2kb
- 代码分割,引入多次的内容可以提取到公共部分
- 小图片使用iconfont代替
- css按需加载
176.以下代码中,p元素是什么
<div class="a b c">
<p class="d">test</p>
</div>
<style>
.a .d {
color: green;
}
p.d{
color: yellow;
}
.a.b p.d{
color: red;
}
.a p.d{
color: black;
}
</style>
test为红色
2022.3.30
170. 实现一个函数柯理化
171. 使用Promise实现:限制异步操作的并发个数,并尽可能快的完成全部
有八个图片资源的url,已经存在数组urls中,已经有一个function loadImg,输入一个url连接返回一个promise,该promise在图片下载完成的时候resolve,下载失败则reject。但有一个要求,任何时刻同时下载的链接数量不可以超过3个.
var urls = [ "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting1.png", "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting2.png", "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting3.png", "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting4.png", "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting5.png", "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn6.png", "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn7.png", "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn8.png",];
//这是下载图片的代码
function loadimg(url) {
return new Promise((resolve,reject) =>{
const img = new Image()
img.loading = function(){
console.log("一张图片加载完成");
resolve(img)
}
img.onerror = function(){
reject (new Error("不能加载图片" + url))
}
img.src = url
})
}
最多三个
function limitLoad(urls,handler,limit) {
let sequence = [].concat(urls); //复制urls
//初始化promise容器
let promise = sequence.splice(0,limit).map((url,index)=>{
return handler(url).then(()=>{
//返回下标是为了知道哪一项最先完成
return index
})
})
//这里要将整个变量过程返回,这样得到的就是一个promise,可以在外面链式调用。
return sequence
.reduce((pCollect,url) =>{
return pCollect
.then(()=>{
return Promise.race(promises) //返回已经完成的下标
})
.then(fastestIndex =>{ //获取到已经完成的下标
//将容器内完成的那一项替换
promise[fastIndex] = handler(url).then(()=>{
return fastIndex //要继续将这个下标返回,以便下一次变量
})
})
.catch(err=>{
console.log(err)
})
},Promise.resolve()) //初始化传入
.then(()=>{ // 最后三个用.all来调用
return Promise.all(promises)
})
}
limitLoad(urls,loadImg,3)
.then(res=>{
console.log("图片全部加载完毕");
console.log(res);
})
.catch(err =>{
console.error(err)
})
172. ES6中的 Reflect 对象有什么用?
reflect对象不是构造函数,所以创建时不是用new来进行创建 在es6中增加这个对象的目的 将object对象的一些明显属于语言内部的方法(比如Object.definedProperty),放到Reflect对象上
- 现阶段,某些方法同时在object和reflect对象上部署,未来的新方法只部署在reflect对象上。也就是说,从reflect对象上可以拿到语言内部的方法。修改某些object方法的返回结果,让其变得更合理。
- 比如object.definedproperty(obj,name,desc)在无法定义属性时,会抛出一个错误,reflect.definedproperty(obj,name,desc)则会返回false。让object操作都变成函数行为。
- 某些object操作是命令式,比如name in obj 和
delete obj[name],而Reflect.has(obj.name)和Reflect.deleteProperty(obj.name)让他们变成了函数行为。Reflect对象的方法与proxy对象的方法一一对应,只要是proxy对象的方法,就能在Reflect读向上找到对应的方法。 - 这就让proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管proxy怎么修改默认行为,你总能在Reflect上获取默认行为。
2022.3.29
167. 关于 React hooks 的 caputre value,当连续点击 10 次时,会输出多少
function App() {
const [count, setCount] = useState(0);
const incr = () => {
setTimeout(() => {
setCount(count + 1);
}, 3000);
};
return <h1 onClick={incr}>{count}</h1>;
}
168. package-lock.json 有什么作用,如果项目中没有它会怎么样,举例说明
package.json/yarn.lock用于锁定版本号,保证开发环境与生产环境的一致性,避免出现不兼容API导致生产环境报错。
在这个问题之前,需要了解一下什么事semver:link.juejin.cn/?target=htt…
当我们在npm i 某个依赖时,默认的版本号是最新版本号^1.2.3,以^开头可以最大限度的使用新特性,但是某些库不遵循该依赖可能出现问题。
^1.2.3指 >=1.2.3 <2.0.0 ,可查看link.juejin.cn/?target=htt…
一个问题:当项目中没有lock文件时,生产环境的风险是如何产生的?
-
pkg 1.2.3: 首次在开发环境安装pkg库,为此时最新版本1.2.3,dependencies依赖中显示^1.2.3,实际安装版本为1.2.3 -
pkg 1.19.0: 在生产环境中上线项目,安装 pkg 库,此时最新版本为1.19.0,满足dependencies中依赖^1.2.3范围,实际安装版本为1.19.0,但是pkg未遵从semver规范,在此过程中引入了breakingChange,此时1.19.0有问题的话,那生产环境中的1.19.0将会导致bug,且难以调试。
而当有了lock文件时,每一个依赖的版本号都被锁死在了lock文件,每次依赖安装的版本号都从lock文件中进行获取,避免了不可测的依赖风险。
pkg 1.2.3: 首次在开发环境安装 pkg 库,为此时最新版本1.2.3,dependencies依赖中显示^1.2.3,实际安装版本为1.2.3,在lock中被锁定版本号。pkg 1.2.3: 在生产环境中上线项目,安装 pkg 库,此时 lock 文件中版本号为1.2.3,符合dependencies中^1.2.3的范围,将在生产环境安装1.2.3,完美上线。
169. 实现一个 once 函数,记忆返回结果只执行一次,类似于lodash.once
const f = (x) => x; const onceF = once(f) onceF(3); // =>3 onceF(4); // =>3
function once(f) {
let result
let revoked = false
return (...args) =>{
if(revoked) return result;
const r = f(...args);
revoked = true
result = r
return r
}
}
2022.3.28
164. 如何实现表格单双行条纹样式
通过css3中伪类:nth-child来实现。其中:nth-child(an+b)匹配下标{an + b ; n = 0 ; 1,2,...}且结果为整数的子元素
- nth-child(2n)/nth-child(even) : 双行样式
- nth-child(2n+!)/nth-child(odd) : 单行样式
其中tr在表格中代表行,实现表格中单双行样式就很简单了
tr:nth-child(2n) {
background-color: red;
}
tr:nth-child(2n + 1) {
background-color: blue;
}
165. Object.is 与全等运算符(===)有何区别
object.is可以检测以下特殊值
- +0/-0
- NaN/NaN
166.## 写一个方法,输出'597646.26'+'798461.2646'的值
2022.3.25
161. 代码输出
function wait() {
return new Promise(resolve =>
setTimeout(resolve, 10 * 1000)
)
}
async function main() {
console.time();
await wait();
await wait();
await wait();
console.timeEnd();
}
main();
default: 30.006s
162. 为什么 HTTP1.1 不能实现多路复用(腾讯)
HTTP1.1不是二进制传输,而是通过文本进行传输,由于没有流的概念,在使用并行传输(多路复用)传递数据时,接收端在接收到响应后,并不能区分多个响应分别对应的请求,所以无法将多个响应的结果重新进行组装,也就实现不了多路复用。
163. redux 为什么要把 reducer 设计成纯函数
redux三大原则 1. 单一数据流,整个应用state都被储存在一个store里面,构成一个object tree 2. state是只读的,唯一改变state的方法就是出发action,action是一个用于描述已发生事件的普通对象。 3. 使用纯函数来执行修改,为了描述action如何改变state tree 你需要编写reducers
把reducer设计成纯函数,可以实现时间旅行,记录/回放或者热加载。
2022.3.24
159. 为什么说JavaScript中函数是一等公民?
在编程语言中,一等公民有几个条件:可以作为函数参数,可以作为函数返回值,可以赋值给变量
按照上面的条件,其实JavaScript中的数据都可以认为是一等公民
无论是基础数据类型还是引用类型都是满足上面条件的。一般说JavaScript函数是一等公民实际上是相对其他编程语言而言,因为并不是所有的编程语言中函数都能满足上述条件。
160.JavaScript去重有几种方式?分别是?
附加题. 你在开发过程中最常用的设计模式是什么?简述一下。
开放题
2022.3.23
156. 预加载和懒加载的区别,预加载在什么时间加载合适
预加载是指在页面加载完成之前,提前将所需资源下载,之后使用的时候从缓存中调用;懒加载是延迟加载,按照一定的条件或者需求等到满足条件的时候再加载对应的资源。两者的主要区别一个是提前加载,一个是迟缓甚至不加载。懒加载对服务器前端有一定的缓解压力的作用,预加载则会增加服务器前端压力。
157.Mvvm和mvc的区别
- Mvc模型视图控制器,试图可以直接访问模型,所以视图里面会包含模型信息,mvc关注的是模型不变,所以在mvc中,模型不依赖视图,但是视图依赖模型。
- mvvm模型视图和vmvm是作为模型和视图的桥梁,当模型层数据改变,vm会检测到并通知视图层进行相应的修改。
158.代码输出
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(resolve => {
console.log("promise1");
resolve();
}).then(function() {
console.log("promise2");
});
console.log('script end')
script start - async1 start - async2 - promise1 - script end - async1 end- promise2- settimeout-
2022.3.22
153.什么情况下会遇到跨域,怎么解决?
跨域其实是浏览器的同源策略造成的(协议、端口号、域名必须一致)
限制了从同一个源加载的文档或脚本如何与另一个源的资源进行交互,是浏览器用于隔离潜在恶意文件重要的安全机制。
1.cors
跨域资源共享(cors) 是一种机制,它使用额外的http头来告诉浏览器,让运行在一个origin上的web应用被准许访问来自不用源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,资源就发起一个跨域http请求
cors需要浏览器和服务器同时支持,整个cors过程都是浏览器完成的,无需用户参与。因此实现cors的关键就是服务器,只要服务器实现了cors请求,就可以跨域通信了。 浏览器将cors分为简单请求和非简单请求 煎蛋清秋不会触发cors预检请求,若该请求满足以下两个条件,就可以看作是简单请求: 1.请求方法是以下三种方法之一 : HEAD/GET/POST
2.http的头信息不超出以下几种字段 :不满足以上请求属于非简单请求
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限于三个值 application/x-www-form-urlencoded、multipart/form-data、text/plain
1.简单请求过程 对于简单请求,浏览器会直接发出cors请求,他会在请求头信息中增加一个origin字段,该字段用来说明本次请求来自哪个源(协议+端口号+域名),服务器会根据这个值来决定是否同意这个请求。如果origin指定的域名在许可范围之内,服务器返回的响应就会多出以下信息头
Access-Control-Allow-Origin: http://api.bob.com // 和Orign一直
Access-Control-Allow-Credentials: true // 表示是否允许发送Cookie
Access-Control-Expose-Headers: FooBar // 指定返回其他字段的值
Content-Type: text/html; charset=utf-8 // 表示文档类型
如果origin指定的域名不在许可范围之内,服务器就会返回一个正常的http回应,浏览器发现没有上面的Access-Control-Allow-Origin的头部信息,就知道出错了。这个错误无法通过状态码识别,因为返回的状态吗可能是200
再简单请求中,在服务器内们至少需要设置字段:Access-Control-Allow-Origin
2.非简单请求 非简单请求是对服务器有特殊要求的请求,比如请求方法为DELETE或者PUT等。非简单请求的CORS请求会在正式通信之前进行一次HTTP查询请求,称为预检请求。
浏览器会询问服务器,当前所在的网页是否在服务器允许访问的范围内,以及可以使用哪些HTTP请求方式和头信息字段,只有得到肯定的回复,才会进行正式的HTTP请求,否则就会报错。
预检请求使用的请求方法是OPTIONS,表示这个请求是来询问的。他的头信息中的关键字段是Orign,表示请求来自哪个源。除此之外,头信息中还包括两个字段:
- Access-Control-Request-Method:该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法。
- Access-Control-Request-Headers: 该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段。
服务器在收到浏览器的预检请求之后,会根据头信息的三个字段来进行判断,如果返回的头信息在中有Access-Control-Allow-Origin这个字段就是允许跨域请求,如果没有,就是不同意这个预检请求,就会报错。 服务器回应的CORS的字段如下:
Access-Control-Allow-Origin: http://api.bob.com // 允许跨域的源地址
Access-Control-Allow-Methods: GET, POST, PUT // 服务器支持的所有跨域请求的方法
Access-Control-Allow-Headers: X-Custom-Header // 服务器支持的所有头信息字段
Access-Control-Allow-Credentials: true // 表示是否允许发送Cookie
Access-Control-Max-Age: 1728000 // 用来指定本次预检请求的有效期,单位为秒
只要服务器通过了预检请求,在以后每次的CORS请求都会自带一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。
在非简单请求中,至少需要设置以下字段:
'Access-Control-Allow-Origin'
'Access-Control-Allow-Methods'
'Access-Control-Allow-Headers'
2.jsonp
jsonp的原理就是利用<script>标签没有跨域限制,通过<script>标签src属性,发送带有callback参数的get请求,服务端将接口返回数据拼凑到callback函数中,返回给浏览器,浏览器解析执行,从而前端拿到callback函数返回的数据。
1. 原生js实现:
<script>
var script = document.createElement('script');
script.type = 'text/javascript';
// 传参一个回调函数名给后端,方便后端返回时执行这个在前端定义的回调函数
script.src = 'http://www.domain2.com:8080/login?user=admin&callback=handleCallback';
document.head.appendChild(script);
// 回调执行函数
function handleCallback(res) {
alert(JSON.stringify(res));
}
</script>
服务端返回如下(返回时即执行全局函数):
handleCallback({"success": true, "user": "admin"})
154.Map Object weakMap的区别?
| map | object | |
|---|---|---|
| 意外的键 | map默认情况下不包含任何键,只包含显式插入的键 | object有一个原型,原型链上的键名有可能和自己在对象上设置的键名产生冲突 |
| 键的类型 | map的键可以是任意值,包括函数、对象或任意基本类型 | object的键必须是string或者是symbol |
| 键的顺序 | map中的key是有序的,因此当迭代的时候 | object的键是无序的 |
| size | map的键值对个数可以轻易的通过size属性获取 | object的键值对个数只能手动计算 |
| 迭代 | map是iterable的,所以可以直接被迭代 | 迭代object |
| 性能 | 在频繁增删键值对的场景下表现更好 | 在频繁添加和删除键值对的场景下未作出优化 |
155. Promise为什么能链式调用?
由于每次调用,.then或者.catch都会返回一个新的promise,从而实现了链式调用,他并不像一般任务的链式调用一样return this.
2022.3.21
150. JS的各种位置,比clientHeight,scrollHeight,offsetHeight,以及scrollTop,offsetTop,clientTop的区别
clientHeight:表示的是可视区域的高度,不包含border和滚动条scrollHeight:表示了所有区域的高度,包含因为滚动被隐藏的部分offsetHeight:可视区域的高度,包含了border和滚动条scrollTop: 滚动后被隐藏的高度,获取对象相对于由offsetParent属性制定的父坐标(css定位的元素或body元素)距离顶端的高度clientTop:表示边框border的厚度,未指定的情况下一般为0
151. v-for与v-if优先级
- 不要把v-if和v-for用在同一个元素上:因为每一次都需要遍历整个数组,会影响速度,尤其是需要渲染很小一部分的时候。
- v-for比v-if具有更高的优先级
152. 应该在React组件的何处发起Ajax请求(react)
高阶组件是一个以组件为参数并返回一个新组件的函数。HOC运行你重用代码、逻辑和引导抽象。最常见的可能是Redux的connect函数。除了简单分享工具库和简单的组合,HOC最好的方式是共享React组件之间的行为。如果你发现你在不同的地方写了大量代码来做同一件事时,就应该考虑将代码重构为可重用的HOC
2022.3.18
147.promise有没有解决异步的问题。
Promise解决了callback回调地狱的问题,async/await是异步的终极解决方案
148.什么是react高阶组件,适用于什么场景?
高阶组件就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件,他只是一个组件的设计模型,这种设计模式是由react自身的组合性质必然产生的。 适用场景: - 代码复用 - 渲染劫持 - state抽象和更改 - props更改
149.怎么处理项目中的异常捕获行为?
try...catchwindow.onerrorwindow.addEventListener('error',function,boolean)window.addEventListener('unhandledrejection')
2022.3.17
143. react-router里的Link标签和a标签有啥区别
区别 从最终渲染的DOM来看,这两者都是链接,都是a标签,区别是:link标签是react-router里实现路由跳转的链接,一般配合router使用,react-router接下了其默认的链接跳转行为,区别于传统的页面跳转,link标签的跳转行为只会触发匹配的route对应的页面内容更新,更不会刷新整个页面。 link标签做的三件事:
- 有onclick那就执行onclick
- click的时候阻止a标签默认事件
- 根据跳转的href,用history跳转,此时只是链接变了,并没有刷新页面。
a标签默认事件禁掉之后做了什么才实现了跳转?
let domArr = document.getElementByTagName('a')
[...domArr].forEach(item => {
item.addEventListener('click', function() {
location.href = this.href
})
})
144.vuex和localstorage的区别是什么?
- vuex储存在内存中,localstorage以文件的方式储存在本地
- vuex能做到数据响应式,localstorage不能做到
- 刷新页面是vuex储存的值会丢失,localstorage不会丢失
145.为什么不建议使用通配符初始化css样式?
- 影响网站的性能,虽然写着简单,但是通配符会把所有的标签都经历一遍,当网站较大,样式较多的时候,大大加强了网站的负载,会使网站加载时间延长
- 由于权重问题,会影响到其他没有选择器设置样式的标签继承来自父类的样式,因为通配符权重是0,而默认情况视为null.
146.三种事件模型是什么?
-
DOM0 级事件模型,这种模型不会传播,所以没有事件流的概念,但是现在有的浏览器支持以冒泡的方式实现,它可以在网页中直接定义监听函数,也可以通过js属性来指定监听函数,所有浏览器都兼容这种方式。直接在dom对象上注册事件名称,就是DOM 0写法
-
IE事件模型,在该事件模型中,一次事件共有两个过程,事件处理阶段和实践冒泡阶段。事件处理阶段会首先执行目标元素绑定的监听事件。然后是事件冒泡阶段,冒泡是指事件从目标元素冒泡到document,依次检查经过的节点是否绑定了事件监听函数,如果有则执行。这种模型通过attchEvent来添加监听函数,可以添加多个监听函数,会按顺序依次执行。
-
DOM2级事件模型,在该事件模型中,一次事件共有三个过程,第一个过程是事件捕获阶段。捕获指的是事件从document一直向下传播到目标元素,依次检查经过的节点是否绑定了事件监听函数,如果有则执行。后面两个阶段和ie事件模型的两个阶段相同。这种事件模型,事件绑定的函数是addEventListener,其中第三个参数可以指定事件是否在捕获阶段执行。
2022.3.16
140. 实现 Promise.retry,成功后 resolve 结果,失败后重试,尝试超过一定次数才真正的 reject
Promise.retry = function(fn,times = 3) {
return new Promise(async(resolve,reject) => {
while(times--) {
try {
const res = await fn()
console.log('成功了->ret',res)
resolve(res)
break
} catch(error) {
console.log('失败了->error',error)
if(!times) reject(error)
}
}
})
}
function test(){
const n = Math.random()
return new Promise((resolve,reject)=>{
setTimeout(()=>{
if(n > 0.7) {
resolve(n)
} else {
reject(n)
}
})
})
}
Promise.retry(test)
.then(value => {
console.log("->value",value)
},reason =>{
console.log("-> reason",reason)
})
141.Promise.all实现原理
Promise.all = function(promise) {
const res = []
return new Promise((resolve,reject)=>{
promise.forEach(promise => {
promise.then((value,idx) =>{
res[idx] = value
if(res.length === promise.length) {
resolve(res)
}
},reason =>{
reject(reason)
})
})
})
}
142.寄生组合和class继承的区别?
**class的所有方法(包括静态方法和实例方法)都是不可枚举的
//引用一个未声明的变量
function Bar() {
this.bar = 42
}
Bar.answer = function() {
return 42
}
Bar.prototype.print = function() {
console.log(this.bar)
}
const barKeys = Object.keys(Bar) //['answer']
const barProtoKeys = Object.keys(Bar.prototype) //['print']
class Foo {
constructor(){
this.foo = 42
}
static answer() {
return 42
}
print(){
console.log(this.foo)
}
}
const fooKeys = Object.keys(Foo) //[]
const fooProtoKeys = Object.keys(Foo.prototype) //[]
class的所有方法(包括静态方法和实例方法)都没有原型对象prototype,所以也没有[[constructor]],不能使用new来调用。
function Bar() {
this.bar = 42
}
Bar.prototype.print = function() {
console.log(this.bar)
}
const bar = new Bar()
const barPrint = new bar.print() //it's ok
class Foo {
constructor() {
this.foo = 42
}
print(){
console.log(this.foo)
}
}
const foo = new Foo()
const fooPrint = new foo.print() //TypeError: foo.print is not a constructor
**必须使用new调用class
function Bar() {
this.bar = 42;
}
const bar = Bar(); // it's ok
class Foo {
constructor() {
this.foo = 42;
}
}
const foo = Foo(); // TypeError: Class constructor Foo cannot be invoked without 'new'
class声明内部会启用严格模式
// 引用一个未声明的变量
function Bar() {
baz = 42; // it's ok
}
const bar = new Bar();
class Foo {
constructor() {
fol = 42; // ReferenceError: fol is not defined
}
}
const foo = new Foo();
__proto__
//es6:
class Super {}
class Sub extends Super {}
const sub = new Sub();
Sub.__proto__ === Super;
//es5:
Sub.__proto__ === Function.prototype;
ES5 和 ES6 子类 this 生成顺序不同。 ES5 的继承先生成了子类实例,再调用父类的构造函数修饰子类实例,ES6 的继承先生成父类实例,再调用子类的构造函数修饰父类实例。
2022.3.15
137.说说HTTP有哪些请求方法? 知道GET和POST的区别么?为什么GET的url有长度限制?POST和PUT又有什么区别? OPTIONS了解多少?
常见的HTTP请求方法:
- GET:向服务器获取数据
- POST:将实体提交到指定的资源,通常会造成服务器资源的修改
- PUT:上传文件,更新数据
- DELETE:删除服务器上的对象
- OPTION:询问支持的请求方法,用来跨域请求
- HEAD:获取报文首部,与get相比,不返回报文主体
- CONNECT:要求在与代理服务器通信时建立隧道,使用隧道进行TCP通信
- TARCE:回显服务器收到的请求,主要用于测试或诊断。 GET和POST的区别
- 应用场景: GET请求是一个幂等的请求,一般GET请求用于对服务器资源不会产生影响的场景,比如说请求一个网页的资源。而post不是一个幂等的请求,一般用于对服务器资源会产生影响的场景,比如注册用户这类的操作。
- 是否缓存:因为两者应用场景不同,浏览器一般会对GET请求缓存,但很少对post请求缓存。
- 发送的报文格式: GET请求的报文中实体部分为空,post请求的报文中实体部分一般为向服务器发送的数据。
- 安全性: GET请求可以将请求的参数放入url中向服务器发送,这样的做法相对于post请求来说是不太安全的,因为请求的URL会被保留在历史记录中。
- 请求长度: 浏览器由于对url长度的限制,所以会影响get请求发送数据时的长度。这个限制是浏览器规定的,并不是RFC规定的。
- 参数类型: post的参数传递支持更多的数据类型
为什么get方法中,URL有长度限制 get方法中并没有url长度的限制,是浏览器做的有限制 post和PUT的区别
- PUT请求是向服务器端发送数据,从而修改数据的内容,但是不会增加数据的种类等,也就是说无论进行多少次put操作,其结果并没有什么不同(可以理解为更新数据)
- POST请求是向服务端发送数据,该请求会改变数据的种类等资源,它会创建新的内容。(可以理解为是创建数据)
OPTIONS请求:
options方法适用于请求获得由
Request-URI标识的资源在请求/响应的通信过程中可以使用的功能选项。通过这个方法,客户端可以在采取具体资源请求之前,决定对该资源采取何种必要措施,或者了解服务器性能。该请求方法的响应不能缓存。options请求方法的主要用途有两个:- 获取服务器支持的所有HTTP方法
- 用来检查访问权限,例如:在进行CORS跨域资源共享时,对于复杂请求,就是使用OPTIONS方法发送嗅探请求,以判断是否有对指定资源的访问权限。
138. 知道vue的动态路由吗?怎么定义动态路由?如何获取传过来的动态参数?
- param方式
-
配置路由格式:
/router/:id -
传递的方式:在path后面跟上对应的值
-
传递后形成的路径:
/router/123
- 路由定义:
-
//在APP.vue中
<router-link :to="'/user/'+userId" replace>用户</router-link>
//在index.js
{
path: '/user/:userid',
component: User,
},
2. 路由跳转:
// 方法1:
<router-link :to="{ name: 'users', params: { uname: wade }}">按钮</router-link
// 方法2:
this.$router.push({name:'users',params:{uname:wade}})
// 方法3:
this.$router.push('/user/' + wade)
3.参数获取
通过`$route.params.userid`获取传递的值
2. query方式:
- 配置路由格式:/router,也就是普通配置
- 传递的方式:对象中使用的query的key作为传递方式
- 传递后形成的路径:/route?id=123
1. 路由定义:
//方式1:直接在router-link 标签上以对象的形式
<router-link :to="{path:'/profile',query:{name:'why',age:28,height:188}}">档案</router-link>
// 方式2:写成按钮以点击事件形式
<button @click='profileClick'>我的</button>
profileClick(){
this.$router.push({
path: "/profile",
query: {
name: "kobi",
age: "28",
height: 198
}
});
}
2.跳转方法:
// 方法1:
<router-link :to="{ name: 'users', query: { uname: james }}">按钮</router-link>
// 方法2:
this.$router.push({ name: 'users', query:{ uname:james }})
// 方法3:
<router-link :to="{ path: '/user', query: { uname:james }}">按钮</router-link>
// 方法4:
this.$router.push({ path: '/user', query:{ uname:james }})
// 方法5:
this.$router.push('/user?uname=' + jsmes)
3. 获取参数:
通过$route.query 获取传递的值
139. 如何实现浏览器多个标签页之间的通信?
实现多个标签之间的通信,本质上都是通过中介者模式来实现的。因为标签页之间没有办法直接通信,因此我们可以找一个中介者,让标签页和中介者进行通信,然后让这个中介者进行消息的转发,通信方法如下:
- 使用webscoket协议,以为webscoket协议可以实现服务器推送,所以服务器就可以来当这个中介者,标签页通过向服务器发送数据,然后由服务器向其他标签页推送转发。
- 使用shareWorker方式,shareWorker会在页面存在的生命周期内创建一个唯一的线程,并且开启多个页面也只会使用一个线程。这个时候共享线程就可以充当中介者的角色。标签页间通过共享一个线程,然后通过这个共享的线程来实现数据的交换。
- 使用localStorage的方式,我们可以在一个标签页对localStorage的变化事件进行监听,然后当另一个标签页修改数据的时候,我们就可以通过这个监听事件来获取到数据。这个时候localStorage对象就是充当的中介者的角色。
- 使用postMessage方法,如果我们能够获得对应标签页的引用,就可以使用postMessage方法,进行通信。
2022.3.14
133.怎么判断NAN?
Number.isNaN()、Object.is()
134.什么是BFC?
相关概念: - box:Box是CSS布局的对象和基本单位,一个页面是由很多个Box组成的,这个box就是我们所说的盒模型。 - Formatting context:块级上下文格式化,他是页面中的一块渲染区域,并且有一套渲染原则,他决定了其子元素将如何定位,以及和其他元素的关系和相互作用。 块格式化上下文(Block Formatting Context,BFC)是web页面的可视化CSS渲染的一部分,是布局过程中生成块级盒子的区域,也是浮动元素与其他元素的交互限定区域。
通俗来讲:BFC是一个独立的布局环境,可以理解为一个容器,在这个容器中按照一定规则进行物品摆放,并且不会影响其他环境中的物品。如果一个元素符合触发BFC的条件,则BFC中的元素布局不受外部影响。 创建BFC的条件:
- 根元素:body;
- 元素设置浮动: float除none外的值
- 元素设置绝对定位: position(absolute、fixed)
- display值为inline-block、table-cell、table-caption、flex等
- overflow值为:hidden、auto、scroll BFC的特点:
- 垂直方向上,自上而下排列,和文档流的排列方式一致。
- 在BFC中上下相邻的两个容器的margin会重叠
- 计算BFC的高度时,需要计算浮动元素的高度
- BFC区域不会与浮动的容器发生重叠
- BFC是独立的容器,容器内部元素不会影响外部元素
- 每个元素的左margin值和容器的左border相接触 BFC的作用:
- 解决margin重叠问题:由于BFC是一个独立的区域,内部的元素和外部的元素互不影响,将两个元素变为两个BFC,就解决了margin重叠的问题。
- 解决高度塌陷的问题: 在对子元素设置浮动后,父元素会发生高度塌陷,也就是父元素的高度变为0。解决这个问题,只需要把父元素变成一个bfc.常用的办法是给父元素设置overflow:hidden
- 创建自适应两栏布局:可以用来创建自适应两栏布局,左边的宽度固定,右边的宽度自适应。
.left{
width: 100px;
height: 200px;
background: red;
float: left;
}
.right{
height: 300px;
background: blue;
overflow: hidden;
}
<div class="left"></div>
<div class="right"></div>
左侧设置float:left,右侧设置:overflow:hidden。这样右边就出发了bfc,bfc的区域不会与浮动元素发生重叠,所以两侧就不会发生重叠,实现了自适应两栏布局
135. 了解HTTPS吗?请介绍一下
HTTPS主要目的是提供对网站服务器的身份认证,保护交换数据的隐私与完整性。http协议采用的是明文传输信息,存在信息窃听、信息篡改和信息劫持的风险,而协议TLS/SSL具有身份验证、信息加密和完整性校验的功能,可以避免此类问题发生
136. CommonJS和ESModule中什么叫编译时输出接口? 什么叫运行时加载?
ESM之所以被称为编译时输出接口,是因为它的模块解析是发生在编译阶段
也就是说,import和export这些关键字是在编译阶段就做了模块解析,这些关键字的使用如果不符合语法规范,在编译阶段就会抛出语法错误。
例如:根据ES6规范,import只能在模块顶层声明,所以下面的写法会直接报语法错误,不会有log打印,因为它压根就没有进入执行阶段。
console.log('hello world');
if (true) {
import { resolve } from 'path';
}
// out:
// import { resolve } from 'path';
// ^
// SyntaxError: Unexpected token '{'
与此对应的CommonJS不同,它的模块解析发生在执行阶段,因为require和module本质上就是个函数或者对象,只有在执行阶段运行时,这些函数或者对象才会被实例化,因此被称为运行时加载。
这里特别强调,与CommonJS不同,ESM中import的不是对象,export的也不是对象。 例如,下面的写法会提示语法错误:
// 语法错误!这不是解构!!!
import { a: myA } from './a.mjs'
// 语法错误!
export {
a: "a"
}
import和export的用法很像导入一个对象或者导出一个对象,但这和对象完全没有关系。它们的用法是ECMAScript语言层面的设计的,并且'恰巧'的对象的使用类似。
所以在编译阶段,import模块中引入的值就指向了export中导出的值。拿堆和栈来比喻,就像两个指针指向了同一个栈。
来源:link.juejin.cn/?target=htt…
2022.3.11
130.点击一个按钮,浏览器会做些什么?
- 点击按钮后创建一个event实例
- 把事件放到事件队列中,等待处理
- event循环线程处理这个事件
- 沿着DOM路径找到触发事件的元素
- 如果这个元素上有处理这个事件的默认行为,并且要在DOM事件阶段周期之前执行,就执行它的默认行为
- 捕获阶段
- 目标阶段
- 冒泡阶段
- 如果这个元素上有处理这个事件的默认行为,并且要在DOM事件处理阶段周期之后执行,就执行它的默认行为。
131.js内部属性[[Class]]是什么?
所有typeof返回值为object的对象(比如数组)都包含一个内部属性[[Class]],我们可以把它看作一个内部的分类,而非传统的面向对象意义上的类,这个属性无法直接访问,一般通过Object.prototype.toString()来查看
Object.prototype.toString.call([1, 2, 3])
// "[object Array]"
Object.prototype.toString.call(function(){})
// "[object Function]"
132.什么是polyfill?
polyfill指的是用于实现浏览器并不支持的原生API的代码,用来抹平不同浏览器对执行js的差异,解决兼容性
例如:querySelecorAll是很多现代浏览器都支持的原生web API,但是有些古老的浏览器并不支持,那么假设有人写了一段代码来实现这个功能是这些浏览器也支持这个功能,那么就可以成为一个Polyfill
2022.3.10
127.请简述webpack中的loaders与plugin的区别
功能上的区别: loader: 直译为加载器,作用是给webpack提供解析非JavaScript语言的能力。 plugin: 直译为插件,通过监听webpack的广播调用webpack的API给webpack提供更多的功能。
使用方式的区别: loader:在module.rules里面配置,类型是数组,每一项都是plugin实例,使用构造函数生成。
128.题目的输出结果?
const list = [1, 2, 3];
const square = (num) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(num * num);
}, 1000);
});
};
function test() {
list.forEach(async (item) => {
const res = await square(item);
console.log(res);
});
}
test();
一秒后输出1 4 9
解析:forEach写法相当于:
for(let i = 0; i < list.length; i++) {
(async (item) => {
const res = await aquare(item);
console.log(res);
})(list[i]);
}
//函数立即执行了,并不会在 for 循环中等待
129.上题如果想要每秒输出一个结果,怎么改?
将test函数改造:
async function test() {
for(let item of list) {
const res = await square(item);
console.log(res);
}
}
2022.3.9
123.http2.0为什么还没有全面覆盖?
124. 前端人员如何 预防 避免线上 p0 事故
开放题
- 线上基础场景走一遍,确认无误。
- 代码review
- 自测
125. 内存泄漏的事有遇到过吗?
以下几种情况会造成内存泄漏:
- 意外的全局变量:由于使用未声明的变量,而意外地创造了一个全局变量,而使这个变量一直存在内存中无法被回收。
- 被遗忘的计时器或回调函数:设置了setInterval定时器而忘记取消它,如果循环函数有对外部变量的引用的话,那么这个变量会被一只存在内存中,无法被回收。
- 脱离DOM的引用: 获取一个DOM元素的饮用,而后面这个元素被删除,由于一直保留了对这个元素的引用,所以它也无法被回收。
- 闭包:不合理的使用闭包,从而导致某些变量一直被留在内存中。
126.浏览器的事件循环 与 node循环机制 的区别?
Node中的EventLoop和浏览器中的是完全不相同的东西。 Node的eventloop分为6个阶段,它会按照顺序反复执行。每当进入某一个阶段的时候,都会从对应的回调队列中取出函数去执行。当队列为空或者执行的回调函数数量到达系统设定的阈值,就会进入下一阶段。
- Timers(计时器阶段): 初次进入事件循环,会从计时器阶段开始。此阶段会判断是否存在过期的计时器回调(包括setTimeout和setInterval),如果存在则会执行所有过期的计时器回调,执行完毕后,如果回调中出发了相应的微任务,会接着执行所有微任务,执行完微任务后再进入pending callbacks阶段
- Pending callbacks: 执行推迟到下一个循环迭代的I/O回调(系统调用相关的回调)
- Idle/Prepare: 仅供内部使用。
- Poll(轮询阶段):
- 当回调队列不为空时,会执行回调,若回调中触发了相应的微任务,这里的微任务执行时机和其他地方有所不同,不会等到所有回调执行完毕后才执行,而是针对每一个回调执行完毕后,就执行相应微任务。执行完所有的回调后,变为下面的情况。
- 当回调队列为空时(没有回调或所有回调执行完毕):但如果存在有计时器(setTimeout、setInterval和setImmediate)没有执行,会结束轮询阶段,进入check阶段。否则会阻塞并等待任何正在执行的I/O操作完成,并马上执行相应的回调,直至所有回调执行完毕。
- Check(查询阶段): 会检查是否存在setImmediate相关的回调,如果存在则执行所有回调,执行完毕后,如果回调中触发了相应的微任务,会接着执行所有微任务,执行完微任务后再进入Close callbacks阶段。
- Close callbacks: 执行一些关闭回调,比如scoket.on('close',...)等 下面来看一个例子,首先在有些情况下,定时器的执行顺序其实是随机的
setTimeout(() => {
console.log('setTimeout')
}, 0)
setImmediate(() => {
console.log('setImmediate')
})
对于以上代码来说,settimeout可能执行在前,也可能执行在后
- 首先setTimeout(fn, 0) === setTimeout(fn, 1),这是由源码决定的
- 进入事件循环也是需要成本的,如果在准备时候花费了大于1ms的时间,那么在timer阶段就会直接执行setTimeout回调
- 如果准备时间花费小于1ms,那么就是setImmediate回调先执行了
当然在某些情况下,他们的执行顺序一定是固定的,比如以下代码:
const fs = require('fs')
fs.readFile(__filename, () => {
setTimeout(() => {
console.log('timeout');
}, 0)
setImmediate(() => {
console.log('immediate')
})
})
在上述代码中,setImmediate永远先执行,因为两个代码写在IO回调中,IO回调是在poll阶段执行,当回调执行完毕后对列为空,发现存在setImmediate回调,所以就直接跳转到check阶段去执行回调了。
上面都是macrotask的执行情况,对于microtask来说,它会在以上每个阶段完成前清空microtask队列,下图中的Tick就代表了microtask
setTimeout(() => {
console.log('timer21')
}, 0)
Promise.resolve().then(function() {
console.log('promise1')
})
对于以上代码来说,其实和浏览器中的输出是一样的,microtask永远执行在maxrotask前面。
2022.3.8
119. 实现斐波那契数列
function fib(num) {
var arr = [];
if(num == 1){
return arr = [1];
}else if(num == 2){
return arr = [1,1];
}else{
arr = [1,1];
var a=1;
var b=1;
var c=2;
var arrlen =arr.length;
for(var i=2;i<num;i++){
arr.push(c);
a=b;
b=c;
c=a+b;
}
return arr;
}
}
120.Vue.nextTick原理
当dom发生变化,更新后执行的回调。将回调延迟到下次dom更新循环之后执行。在修改数据之后立即使用它等待dom更新。
主要思路就是采用微任务优先的方式调用异步方法去执行nextTick包装的方法。
121.js如何解决单个任务执行时间过长
web Worker的作用,就是为JavaScript创造多线程环境,允许主线程创建Worker线程,将一些任务分配给后者运行。 web Worker有以下几个使用注意点:
- 同源限制:分配给Worker线程运行的脚本文件,必须与主线程的脚本文件同源。
- DOM限制:Worker线程所在的全局对象,与主线程不一样,无法读取主线程所在网页的Dom对象,也无法使用document、window、parent这些对象。但是,Worker线程可以navigator对象和Worker线程不能执行alert()方法和confirm()方法,但可以使用XMLHttpRequest对象发出AJAX请求。ocation对象。
- 通信联系:Worker线程和主线程不在同一个上下文环境,他们不能直接通信,必须通过消息完成。使用worker.postMessage()通信
- 脚本限制:Worker线程不能执行alert()和confirm()方法,但可以使用XMLHttpRequest对象发出AJAX请求。
- 文件限制:Worker线程无法读取本地文件,即不能打开本机的文件系统(file:// ),它所加载的脚本,必须来自网络。
122.取出字符串中第一个出现一次的字符?
2022.3.7
116.js怎么控制一次加载一张图片,加载完再加载下一张?
方法1
<div id="mypic">onloading......</div>
<script type="text/javascript">
var obj = newImage();
obj.src ="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0b148e0800994d0c819fb34951cb2b39~tplv-k3u1fbpfcp-watermark.image";
obj.onload = function(){
alert('图片的宽度为:'+obj.width+';图片的高度为:'+obj.height);
document.getElementById("mypic").innerHTML="<img src='"+this.src+"'/>";
}
</script>
方法二
<script type="text/javascript">
var obj=newImage();
obj.src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0b148e0800994d0c819fb34951cb2b39~tplv-k3u1fbpfcp-watermark.image";
obj.onreadystatechange = function(){
if(this.readyState=="complete"){
alert('图片的宽度为:'+obj.width+';图片的高度为:'+obj.height);
document.getElementById("mypic").innerHTML="<img src='"+this.src+"'/>";}
}
</script>
附加:
let a = 10
let b = async () => {
a = await sum()
console.log('1 :>> ', a);
}
function sum() {
return a + 10
}
b()
a++
console.log('2 :>> ', a);
// 11 20
117.overflow清除浮动的原理
块格式化上下文是css可视化渲染的一部分,它是一块区域,规定了内部块盒的渲染方式,以及浮动相互之间的影响关系,当元素设置了overflow样式且值不为visible时,该元素就构建了一个BFC,BFC在计算高度时,内部浮动的元素的高度也要计算在内,也就是说即使BFC区域只有一个浮动元素,BFC的高度也不会发生塌缩,所以达到了清楚浮动的目的。
118.什么是jsonp?工作原理是什么?他为什么不是真正的ajax?
jsonp其实就是一个跨域解决方案。js跨域请求数据是不可以的,但是js跨域请求js脚本是可以的。所以可以把要请求的数据封装成一个js语句,做一个方法的调用。跨域请求js脚本可以得到此脚本。得到js脚本会立即执行。可以把数据作为参数传递到方法中。就可以获得数据。从而解决跨域问题。
为什么不是真正的ajax
- ajax和jsonp这两种技术在调用方式上看起来很像,目的也一样,都是请求一个url,然后把服务器返回的数据进行处理,因此jquery和ext等框架都把jsonp作为ajax的一种形式进行了封装。
- ajax和jsonp其实本质上是不同的东西。ajax的核心是通过xmlhttprequest获取本页内容,而jsonp的核心则是动态添加
<script>标签来调用服务器提供的js脚本。 - 所以说,其实ajax与jsonp的区别不在于是否跨域,ajax通过服务端代理一样可以实现跨域,jsonp本身也不排斥同域的数据的获取。
- jsonp是一种方式或者说非强制协议,如同ajax一样,他也不一定非要jsonp格式来传递数据,如果你愿意,字符换也行,只不过这样不利于jsonp提供公开服务。
2022.3.4
113. 浏览器渲染进程的线程有哪些?
浏览器的渲染进程的线程总共有五种:
1. GUI渲染线程
负责渲染浏览器页面,解析HTML\CSS,构建DOM树,构建CSSOM树、构建渲染树和绘制页面;当界面需要重绘或由于某种操作引发回流时,该线程就会执行。
2.js引擎线程
js线程也称为js内核,负责处理JavaScript脚本程序,解析javascript脚本,运行代码;js引擎线程一直等待着任务队列中任务的到来,然后加以处理,一个tab页中无论什么时候都只有一个js引擎线程在运行js程序
注意:GUI渲染线程与js引擎线程的互斥关系,所以如果js执行的时间过长,会造成页面的渲染不连贯、导致页面渲染加载阻塞。
3.时间触发线程
时间触发线程属于浏览器而不是js引擎,用来控制事件循环;当js引擎执行代码块如settimeout时(也可是来自浏览器内核的其他线程,如鼠标点击,AJAX异步请求等),会将对应任务添加到事件触发线程中;当对应的事件符合触发条件时被触发时,该线程会把事件添加到待处理队列的队尾,等待js引擎做处理
注意:由于js单线程关系,所以这些待处理队列中的事件都得排队等待js引擎处理(当js引擎空闲时才会去执行)
4.定时器触发进程
定时器触发进程即setinterval与settimeout所在线程;浏览器定时计数器并不是由js引擎计数的,因为js引擎是单线程的,如果处于阻塞县城状态就会影响计时的准确性;因此使用单独线程来计时并触发定时器,计时完毕后,添加到事件队列中,等待js引擎空闲后执行,所以定时器中的任务在设定的时间点不一定能够准确执行,定时器只是在指定时间点将任务添加到是事件队列中
注意:W3C在HTML标准中规定,定时器的定时时间不能小于4ms,如果小于则默认4ms。
5.异步http请求线程
- XMLHttpRequest连接后通过浏览器新开一个线程请求;
- 检测到状态变更时,如果设置有回调函数,异步线程就会产生状态变更事件,将回调函数放入事件队列中,等待js引擎空闲后执行。
114.setTimeout、Promise、Async/Await 的区别
1.setTimeout
console.log('script start') //1. 打印 script start
setTimeout(function(){
console.log('settimeout') // 4. 打印 settimeout
}) // 2. 调用 setTimeout 函数,并定义其完成后执行的回调函数
console.log('script end') //3. 打印 script start
// 输出顺序:script start->script end->settimeout
2.Promise Promise本身是同步的立即执行函数,当在executor中执行resolve或者reject的时候,此时是异步操作,会先执行then/catch等,当主栈完成后,才会去调用resolve/rehect中存放的方法执行,打印p的时候,是打印的返回结果,一个promise实例。
console.log('script start')
let promise1 = new Promise(function (resolve) {
console.log('promise1')
resolve()
console.log('promise1 end')
}).then(function () {
console.log('promise2')
})
setTimeout(function(){
console.log('settimeout')
})
console.log('script end')
// 输出顺序: script start->promise1->promise1 end->script end->promise2->settimeout
当js主线程执行到promise对象时:
- promise1.then()的回调就是一个task
- promise1是resolved或rejected:那这个task就会放入当前事件循环回合的microtask queue中
- setTimeout的回调也是一个task,它会被放入macrotask queue即使是0ms的情况
3.async/await
async function async1(){
console.log('async1 start');
await async2();
console.log('async1 end')
}
async function async2(){
console.log('async2')
}
console.log('script start');
async1();
console.log('script end')
// 输出顺序:script start->async1 start->async2->script end->async1 end
async函数返回了一个promise对象,当函数执行的时候,一旦遇到await就会先返回,等到触发的异步操作完成后,再执行函数体内后面的语句。可以理解为,是让出了线程,跳出了async函数体。 例如:
async function func1() {
return 1
}
console.log(func1())
func1的运行结果其实就是一个promise对象。因此也可以使用then来处理后续逻辑。
func1().then(res => {
console.log(res); // 30
})
await的含义为等待,也就是async函数需要等待await后的函数执行完成并且有了返回结果(promise对象)之后,才能继续执行下面的代码,await通过返回一个promise对象来实现同步的效果。
115. 对对象与数组的解构的理解
解构是es6提供的一种新的提取数据的模式,这种模式能够从对象或者数组里有针对性的拿到想要的数值。
- 数组的解构 :在解构数组时,以元素的位置为匹配条件来提取想要的数据的:
const [a,b,c] = [1,2,3]
最终,abc分别被赋予了数组第012个索引位的值。
数组里的0、1、2索引位的元素值,精准被映射到了左侧的0、1、2变量里去,这就是数组解构的工作模式。还可以通过给左侧变量数组设置空占位的方式,实现对数组中某几个元素的精准提取。
const [a,,c] = [1,2,3]
通过把中间位留空,可以顺利地把数组第一位和最后一位的值赋给啊、c两个变量。
- 对象的解构 :对象解构比数组解构要复杂一些,也更显强大。在解构对象时,是以属性的名称为匹配条件,来提取想要的数据的。现在定义一个对象:
const stu = { name:'bob', age:24 }
//假如想要解构它的两个自有属性,可以这样:
const { name ,age } = stu
这样就得到了name和age两个和stu平级的变量:
注意:对象解构严格以属性名作为定为依据。所以就算调换了name和age的位置,结果也是一样的。
const { age, name } = stu
2022.3.3
109.vue.mixin的使用场景和原理
使用场景: 是一些在组建中需要提炼出来的公共部分,比如公共属性,data公共方法methods或者计算属性computed等,都可以提取到mixin中,当有需要的直接mixin进去 原理: 当前vue实例的options和传入的mixin合并,再返回,真正的实现是靠mergeOptions函数实现的。
export function mergeOptions (
parent: Object,
child: Object,
vm?: Component
): Object {
if (process.env.NODE_ENV !== 'production') {
checkComponents(child)
}
if (typeof child === 'function') {
child = child.options
}
normalizeProps(child, vm)
normalizeInject(child, vm)
normalizeDirectives(child)
//上面的代码可以略过不看,主要是检查各种格式的
if (!child._base) {
if (child.extends) {
parent = mergeOptions(parent, child.extends, vm)
}
if (child.mixins) {
//处理mixins
for (let i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm)
}
}
}
const options = {}
let key
for (key in parent) {
//这里parent是this.options
mergeField(key)
}
for (key in child) {
//这里child是mixins,同时检查parent中是否已经有key即是否合并过
if (!hasOwn(parent, key)) {
mergeField(key)
}
}
function mergeField (key) {
const strat = strats[key] || defaultStrat
options[key] = strat(parent[key], child[key], vm, key)
}
return options
}
110.怎么判断一个变量是数组?
- 通过object.prototype.toString.call()做判断。
Object.prototype.toString.call(obj).slice(8,-1) === 'Array'
- 通过原型链做判断
obj.__proto__ === Array.prototype
- 通过es6的Array.isArray()做判断
Array.isArrray(obj);
- 通过instanceof做判断
obj instanceof Array
- 通过Array.prototype.isPrototypeOf
Array.prototype.isPrototypeOf(obj)
111.隐藏一个元素有多少种方法?
- display:none 元素彻底消失,不会触发点击事件,会产生回流和重绘
- visibility:hidden :无法触发其点击事件,方法元素也是消失了,依然占据着页面空间,只会引起页面重绘。
- opacity:0 可以触发点击事件,只会引起页面重绘。
- height:0 将元素的高度设置为0, 并且设置overflow:hidden。这个元素相当于消失了。无法触发点击事件。
- 设置元素的position与left right top bottom 等,将元素移出至屏幕外。
- 设置元素的position与z-index 将z-index设置成尽量小的负数。
112.vue2对于对象和数组的响应式原理有什么区别? 都有什么缺陷?
2022.3.2
105.vue-router的实现原理?
看记录用1的18题
现有一个div,里边用了data的变量a,值为1,两秒之后将a改为3,从模版编译,数据绑定,依赖收集,派发更新说一下页面由1到3的全套流程。
106.动画的优化,为什么transform比margin的性能好?
- margin是属于布局属性,该属性的变化会导致页面的重排。对布局属性进行动画,浏览器需要对每一帧进行重绘并上传到GPU中进行渲染
- transform是合成属性,浏览器会为元素创建一个独立的复合层,当元素内容没有发生变化,该层不会被重绘,通过重新复合来创建动画。
107.什么是稳定性排序和不稳定性排序?哪些排序是稳定的,哪些是不稳定的?为什么?
稳定不稳定是说序列中相同元素通过排序后,他们的先后顺序不变,如果变了是不稳定排序,不变是稳定排序 稳定排序包括插入排序、冒泡排序、归并排序、基数排序 插入排序: 在一个有序的序列中插入一个数,使插入后的序列保持有序。因为插入的过程中都是从后向前进行查找,遇到小于等于(或大于等于)的数停止寻找,进行插入操作。不改变排序前后相等数值的相对顺序,故使稳定的排序算法。 冒泡排序: 冒泡顾名思义,数值小的向上飘,数值大的向下沉,向上飘的数遇到的小于等于当前数的值停止,向下沉的数遇到大于等于当前数的数停止。类似于对于向上飘的数有个排序之前在其前面数值相等限制了其向上飘的脚步,原先在俺之下,排序后也在俺之下,向下沉也是同理。故也是稳定的排序算法。 归并排序: 将一段序列分为若干个小序列进行排序,排序后的小序列进行合并得到最后的排序结果。主要运用了分治的思想。分成的前后若干个小序列在最后进行合并时本身就包含了前后位置信息,在合并时不改变相同值在排序前后的相对顺序,故归并排序也是稳定排序。 基数排序: 按从低到高的相应位置的值进行排序,也是稳定排序算法。 非稳定排序算法包括:选择排序、快速排序、希尔排序、堆排序
选择排序: [1,2,3,4,5,3]
主要思想是分别找出当前遍历元素中的最小值与相应位置的数进行交换,第一遍寻找元素的从第一个元素起的最小值(或最大值)和第一个元素进行交换,第二趟寻找从第二个元素起最小的(或最大的)元素与第二个元素进行交换,以此类推。排序前[3,3',2]排序后[2,3',3]
快速排序: 排序前[5,3,3,4,3',8,7] 排序后[3',3,3,4,5,8,7]
希尔排序: 一次插入排序是稳定的,多次插入排序不是稳定的。
108.flex:1的三个属性及其特点?
看记录1第八题
2022.3.1
102.canvas和svg哪个更适合用来做动画,为什么?
canvas
- 它是通过javascript来绘制的
- 只要它的位置发生改变,就需要重新绘制
- 它是逐像素的进行渲染
- 依赖屏幕的分辨率
- 弱的文本渲染能力
- 不支持事件处理
- 能够jpg,png图像保存
- 适合图像密集的游戏,能够重复渲染 svg
- 使用xml的2d语言
- 不依赖屏幕的分辨率
- 支持事件处理
- 适合带有大型渲染区域的应用程序
- 不适合游戏
- 复杂度越高渲染速度会越慢 适用范围 canvas是逐像素渲染,一旦图形绘制完成,不会继续被浏览器关注。而svg是通过dom操作来显示的。所以canvas的文本渲染能力弱,而svg最适合带有大型渲染区域的应用程序,比如地图。 而canvas最适合有许多对象要被频繁重绘的图形密集型游戏 svg由于dom操作,在复杂度高的游戏应用中会减慢渲染速度。所以不适合在游戏应用。
103.css中如何解决inline-block元素对齐问题
- 暴力float,当然这是备选方案,毕竟脱离文档流后页面元素会不好控制
- 简单粗暴地给所有元素加上内容,例如空格符
- 设置所有内联元素vertical-align:top/middle/bottom;属性,改变默认设置。
104.javascript数组在V8中做了什么优化?
相关文章:link.juejin.cn/?target=htt… link.juejin.cn/?target=htt…