基础不牢,地动山摇!!!
基础第一篇juejin.cn/post/705232…
2022.04.14
203. Vue有哪些属性或方法是响应式的?
数组的一些方法push、pop、shift、unshift、sort、splice、reverse
202. Vue-loader做了什么?
解析和转换 .vue 文件,提取出其中的逻辑代码 script、样式代码 style、以及 HTML 模版 template,再分别把它们交给对应的 Loader 去处理。
201. npm install做了什么?
2022.04.13
200.小程序的生命周期和更新机制
onLaunch,监听小程序初始化,当小程序初始化完成时,会触发onLaunch(全局只触发一次)
onShow,监听小程序显示,当小程序启动,或从后台进入前台显示,会触发onshow方法
onHide,监听小程序隐藏,当小程序从前台进入后台,会触发onhide方法
onError,错误监听函数,当小程序发生脚本错误,或者api调用失败,会触发onError并带上错误信息
page页面生命周期
onLoad,监听页面加载
onReady,监听页面初次渲染完成
onShow,监听页面显示
onHide,监听页面隐藏
onUnload,监听页面卸载
199.解决移动端滚动穿透和点击穿透
监听touchmove事件,判断开始滑动时是否在顶部,若是则阻止默认事件,方式滚动传递出去,若不是,则不作操作。
需要做的事情有:
1、预存一个全局变量targetY
2、监听可滚动区域的touchstart事件,记录下第一次按下时的
e.targetTouches[0].clientY值,赋值给targetY
3、后期touchmove里边获取每次的e.targetTouches[0].clientY与第一次的进行比较,可以得出用户是上滑还是下滑手势。
4、如果手势是向上滑,且页面现在滚动的位置刚好是整个可滚动高度——弹窗内容可视区域高度的值,说明上滑到底,阻止默认事件。
同理,如果手势是向下滑,并且当前滚动高度为0说明当前展示的已经在可滚动内容的顶部了,此时再次阻止默认事件即可。
<div id="des-container-wrapper"
@touchstart="leftTouchStart"
@touchmove="leftTouchMove"> //弹出层滚动区域容器
<section class="des-container" v-for="item in modelData" :key="item.id">
<h1 class="des-title" v-if="item.question">{{item.question}}</h1>
<div class="des-content" v-html="item.answer"></div>
</section>
</div>
data(){
return {
targetLeftY: 0,
}
},
methods:{
leftTouchStart(e) {
this.targetLeftY = Math.floor(e.targetTouches[0].clientY);
},
leftTouchMove(e) {
let newTargetY = Math.floor(e.targetTouches[0].clientY);
let dom = document.getElementById('des-container-wrapper');
let sT = dom.scrollTop;
let sH = dom.scrollHeight;
let cH = dom.clientHeight;
if (sT <= 0 && newTargetY - this.targetLeftY > 0) {
e.preventDefault();
} else if ((sT >= sH - cH) && newTargetY - this.targetLeftY < 0) {
e.preventDefault();
}
},
},
## 198.移动端 1px问题如何优化
#### 思路一:直接写 0.5px
如果之前 1px 的样式这样写:
border:1px solid #333
可以先在 JS 中拿到 window.devicePixelRatio 的值,然后把这个值通过 JSX 或者模板语法给到 CSS 的 data 里,达到这样的效果(这里用 JSX 语法做示范):
```
然后就可以在 CSS 中用属性选择器来命中 devicePixelRatio 为某一值的情况,比如说这里尝试命中 devicePixelRatio 为 2 的情况:
#container[data-device="2"] {
border:0.5px solid #333
}
直接把 1px 改成 1/devicePixelRatio 后的值,这是目前为止最简单的一种方法。这种方法的缺陷在于兼容性不行,IOS 系统需要 8 及以上的版本,安卓系统则直接不兼容。
思路二:伪元素先放大后缩小
这个方法的可行性会更高,兼容性也更好。唯一的缺点是代码会变多。
思路是先放大、后缩小:**在目标元素的后面追加一个 ::after 伪元素,让这个元素布局为 absolute 之后、整个伸展开铺在目标元素上,然后把它的**宽和高都设置为目标元素的两倍,border 值设为 1px。**接着借助 CSS 动画特效中的放缩能力,把整个伪元素缩小为原来的 50%。此时,伪元素的宽高刚好可以和原有的目标元素对齐,而 border 也缩小为了 1px 的二分之一**,间接地实现了 0.5px 的效果。
代码如下:
#container[data-device="2"] {
position: relative;
}
#container[data-device="2"]::after{
position:absolute;
top: 0;
left: 0;
width: 200%;
height: 200%;
content:"";
transform: scale(0.5);
transform-origin: left top;
box-sizing: border-box;
border: 1px solid #333;
}
}
思路三:viewport 缩放来解决
这个思路就是对 meta 标签里几个关键属性下手:
<meta name="viewport" content="initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5, user-scalable=no">
这里针对像素比为 2 的页面,把整个页面缩放为了原来的 1/2 大小。这样,本来占用 2 个物理像素的 1px 样式,现在占用的就是标准的一个物理像素。根据像素比的不同,这个缩放比例可以被计算为不同的值,用 js 代码实现如下:
const scale = 1 / window.devicePixelRatio;
// 这里 metaEl 指的是 meta 标签对应的 Dom
metaEl.setAttribute('content', `width=device-width,user-scalable=no,initial-scale=${scale},maximum-scale=${scale},minimum-scale=${scale}`);
这样解决了,但这样做的副作用也很大,整个页面被缩放了。这时 1px 已经被处理成物理像素大小,这样的大小在手机上显示边框很合适。但是,一些原本不需要被缩小的内容,比如文字、图片等,也被无差别缩小掉了。
2022.04.12
197. 怎么解决白屏问题
跳转190题
196. UDP和TCP有什么区别
| UDP | TCP | |
|---|---|---|
| 是否连接 | 无连接 | 面向连接 |
| 是否可靠 | 不可靠传输,不使用流量控制和拥塞控制 | 可靠传输(数据顺序和正确性),使用流量控制和拥塞控制 |
| 连接对象个数 | 支持一对一,一对多,多对一和多对多交互通信 | 只能是一对一通信 |
| 传输方式 | 面向报文 | 面向字节流 |
| 首部开销 | 首部开销小,仅8字节 | 首部最小20字节,最大60字节 |
| 适用场景 | 适用于实时应用,例如视频会议、直播 | 适用于要求可靠传输的应用,例如文件传输 |
195. JSON.stringify有什么缺点?
- 使用
JSON.Stringify转换的数据中,如果包含function,undefined,Symbol,这几种类型,不可枚举属性, JSON.Stringify序列化后,这个键值对会消失。 - 转换的数据中包含
NaN,Infinity值(含-Infinity),JSON序列化后的结果会是null。 - 转换的数据中包含
Date对象,JSON.Stringify序列化之后,会变成字符串。 - 转换的数据包含
RegExp引用类型序列化之后会变成空对象。 - 无法序列化不可枚举属性。
- 无法序列化对象的循环引用,(例如:
obj[key] = obj)。 - 无法序列化对象的原型链。
2022.04.11
194、js 的 new 操作符做了什么?
(1)首先创建了一个新的空对象
(2)设置原型,将对象的原型设置为函数的 prototype 对象。
(3)让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象添加属性)
(4)判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。
193、什么是协商缓存?
如果命中强制缓存,我们无需发起新的请求,直接使用缓存内容,如果没有命中强制缓存,如果设置了协商缓存,这个时候协商缓存就会发挥作用了。
上面已经说到了,命中协商缓存的条件有两个:
max-age=xxx过期了- 值为
no-store
使用协商缓存策略时,会先向服务器发送一个请求,如果资源没有发生修改,则返回一个 304 状态,让浏览器使用本地的缓存副本。如果资源发生了修改,则返回修改后的资源。
协商缓存也可以通过两种方式来设置,分别是 http 头信息中的 Etag 和 Last-Modified 属性。
(1)服务器通过在响应头中添加 Last-Modified 属性来指出资源最后一次修改的时间,当浏览器下一次发起请求时,会在请求头中添加一个 If-Modified-Since 的属性,属性值为上一次资源返回时的 Last-Modified 的值。当请求发送到服务器后服务器会通过这个属性来和资源的最后一次的修改时间来进行比较,以此来判断资源是否做了修改。如果资源没有修改,那么返回 304 状态,让客户端使用本地的缓存。如果资源已经被修改了,则返回修改后的资源。使用这种方法有一个缺点,就是 Last-Modified 标注的最后修改时间只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,那么文件已将改变了但是 Last-Modified 却没有改变,这样会造成缓存命中的不准确。
(2)因为 Last-Modified 的这种可能发生的不准确性,http 中提供了另外一种方式,那就是 Etag 属性。服务器在返回资源的时候,在头信息中添加了 Etag 属性,这个属性是资源生成的唯一标识符,当资源发生改变的时候,这个值也会发生改变。在下一次资源请求时,浏览器会在请求头中添加一个 If-None-Match 属性,这个属性的值就是上次返回的资源的 Etag 的值。服务接收到请求后会根据这个值来和资源当前的 Etag 的值来进行比较,以此来判断资源是否发生改变,是否需要返回资源。通过这种方式,比 Last-Modified 的方式更加精确。
当 Last-Modified 和 Etag 属性同时出现的时候,Etag 的优先级更高。使用协商缓存的时候,服务器需要考虑负载平衡的问题,因此多个服务器上资源的 Last-Modified 应该保持一致,因为每个服务器上 Etag 的值都不一样,因此在考虑负载平衡时,最好不要设置 Etag 属性。
192、localhost 和 127.0.0.1 的区别?localhost 调用 127.0.0.1 属于跨域吗?为什么?
localhost:也叫做local称为(本地服务器)
127.0.0.1:在windows系统下称之为:本机地址(本机服务器)
二者的区别:
1、localhost:是不经网卡传输!并且不受防火墙和网卡相关的限制
2、127.0.0.1:是通过网卡传输,依赖网卡,并且受到网卡和防火墙的限制
不会产生跨域
2022.04.08
191.vue/react为什么要在列表组件中写key,起作用是什么?
key的作用就是给每一个VNode一个唯一的key,通过key可以更准确更快的拿到VNode。
vue和react都是采用diff算法来对比新旧虚拟节点,从而更新节点。当新节点跟旧节点头尾交叉对比没有结果时,会根据新节点的key去对比旧节点数组中的key,从而找到相应的旧节点。如果没找到就认为是一个新增节点。而如果没有key,那么就会采用遍历查找的方式去找到对应的旧节点。
在不带key的情况下,节点可以进行复用,省去了操作DOM的开销,只适用于简单的无状态组件的渲染。虽然带上唯一的key会增加开销,但是能保证组件的状态正确,而且用户基本感受不到差距。
190.vue开发遇到的首页加载白屏问题如何解决?
1.ssr
2.使用Gzip压缩,减少文件体积,加快首屏页面打开速度
3.骨架屏
4.外链CSS,JS文件,很多时候我们在main.js中直接import一些ui库或者css文件,可以在index.html,通过script外链引入,这样就不会通过我们的webpack打包
5.原始方法-加loading
189.promise.all()的作用,什么时候会用?
Promise 的 all()方法提供了并行执行异步操作的能力,即在所有异步操作执行完后才执行回调, all()里面接收一个数组,数组中每个元素都返回一个 promise 对象。
promise.all可以用来执行不关心执行顺序,只关心回来结果的顺序的那种需求
2022.04.07
188. vue的v-bind直接传入对象绑定,和v-bind:加冒号绑定有什么区别。
没什么区别,类似于一种简写方式
187. 请求数据一般放在created还是mounted,为什么。
如果是影响布局的放在mounted里,不影响的两者皆可
186. 后端返回一个可执行的js方法字符串 举例 function (){console.log('打印')}。前端如何去执行
使用eval()
2022.04.06
185. 对于React和Vue的理解,以及他们的异同。
184. computed和watch的区别。
- computed和watch都是基于Watcher来实现的\
- computed属性是具备缓存的,依赖的值不发生变化,对其取值时计算属性方法不会重新计算\
- watch则是监控值变化,当值变化时掉哦用对应的回调函数
183. 路由hash和history模式的区别。
2022.04.02
182.说说this
this 是执行上下文中的一个属性,它指向最后一次调用这个方法的对象。在实际开发中,this 的指向可以通过四种调用模式来判断。
- 第一种是函数调用模式,当一个函数不是一个对象的属性时,直接作为函数来调用时,this 指向全局对象。
- 第二种是方法调用模式,如果一个函数作为一个对象的方法来调用时,this 指向这个对象。
- 第三种是构造器调用模式,如果一个函数用 new 调用时,函数执行前会新创建一个对象,this 指向这个新创建的对象。
- 第四种是 apply 、 call 和 bind 调用模式,这三个方法都可以显示的指定调用函数的 this 指向。其中 apply 方法接收两个参数:一个是 this 绑定的对象,一个是参数数组。call 方法接收的参数,第一个是 this 绑定的对象,后面的其余参数是传入函数执行的参数。也就是说,在使用 call() 方法时,传递给函数的参数必须逐个列举出来。bind 方法通过传入一个对象,返回一个 this 绑定了传入对象的新函数。这个函数的 this 指向除了使用 new 时会被改变,其他情况下都不会改变。
这四种方式,使用构造器调用模式的优先级最高,然后是 apply、call 和 bind 调用模式,然后是方法调用模式,然后是函数调用模式。
181.说说模块化
180.数组去重
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.04.01
179. 下列代码输出结果是?
Promise.resole(1).then(2).then(Promise.resolve(3)).then(console.log)
- 如果then方法里传的不是函数,可以认为是then(null),相当于没有传回调函数,任务状态和前一个任务一致,->
Promise.resole(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}!`);
return Reflect.get(target, key, receiver);
},
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>
176. webpack 中是如何处理图片的?
webpack 本身不处理图片,它会把图片内容仍然当做 JS 代码来解析,结果就是报错,打包失败。如果要处理图片,需要通过 loader 来处理。其中,url-loader 会把图片转换为 base64 编码,然后得到一个 dataurl,file-loader 则会将图片生成到打包目录中,然后得到一个资源路径。但无论是哪一种 loader,它们的核心功能,都是把图片内容转换成 JS 代码,因为只有转换成 JS 代码,webpack 才能识别。
2022.03.31
175. commonjs与esModule有什么区别;
174. Vue 3.0 所采用的 Composition Api 与 Vue 2使用的Options Api 的区别;
Composition Api 可以将相同的业务部分的代码写在一起,可复用性更高,代码结构更清晰. composition Api使用的api接口是通过import引入,所以未使用的api方法打包的时候不会被打包进来,减少打包体积,options会全部引入不管用没用
173. webpack构建过程有什么优化手段?webpack优化打包体积方法
- 第三方模块缓存,下次构建取缓存中的数据,减少打包时间
- 第三方库按需加载
- UI组件按页面按需加载
- 若第三方库有min.js版本则引入min.js版本
- moment.js去除语言包,或直接更换为dayjs,他们api都是相同的,dayjs只有2kb
- 代码分割,引入多次的内容可以提取到公共部分
- 小图片使用iconfont代替
- css按需加载
附加
以下代码中,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.03.30
172.实现一个函数柯理化
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.onload = function() {
console.log("一张图片加载完成");
resolve(img);
};
img.onerror = function() {
reject(new Error('Could not load image at' + url));
};
img.src = url;
});
请写一段代码实现这个需求,尽可能快速的将所有图片下载完成
function limitLoad(urls, handler, limit) {
let sequence = [].concat(urls); // 复制urls
// 这一步是为了初始化 promises 这个"容器"
let promises = 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 => { // 获取到已经完成的下标
// 将"容器"内已经完成的那一项替换
promises[fastestIndex] = handler(url).then(
() => {
return fastestIndex; // 要继续将这个下标返回,以便下一次变量
}
);
})
.catch(err => {
console.error(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);
});
170.ES6中的 Reflect 对象有什么用?
Reflect 对象不是构造函数,所以创建时不是用 new 来进行创建。
在 ES6 中增加这个对象的目的:
将 Object 对象的一些明显属于语言内部的方法(比如 Object.defineProperty),放到 Reflect 对象上。现阶段,某些方法同时在 Object 和 Reflect 对象上部署,未来的新方法将只部署在 Reflect 对象上。也就是说,从 Reflect 对象上可以拿到语言内部的方法。
修改某些 Object 方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而 Reflect.defineProperty(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.03.29
169.关于 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 有什么作用,如果项目中没有它会怎么样,举例说明
packagelock.json/yarn.lock 用以锁定版本号,保证开发环境与生产环境的一致性,避免出现不兼容 API 导致生产环境报错
在这个问题之前,需要了解下什么是 semver: 什么是 semver (opens new window)。
当我们在 npm i 某个依赖时,默认的版本号是最新版本号 ^1.2.3,以 ^ 开头可最大限度地使用新特性,但是某些库不遵循该依赖可能出现问题。
^1.2.3指 >=1.2.3 <2.0.0,可查看 semver checker(opens new window)
一个问题: 当项目中没有 lock 文件时,生产环境的风险是如何产生的?
演示风险过程如下:
pkg 1.2.3: 首次在开发环境安装 pkg 库,为此时最新版本1.2.3,dependencies依赖中显示^1.2.3,实际安装版本为1.2.3pkg 1.19.0: 在生产环境中上线项目,安装 pkg 库,此时最新版本为1.19.0,满足dependencies中依赖^1.2.3范围,实际安装版本为1.19.0,但是pkg未遵从 semver 规范,在此过程中引入了 Breaking Change,如何此时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,完美上线。
167.实现一个 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.03.28
166.如何实现表格单双行条纹样式
通过 css3 中伪类 :nth-child 来实现。其中 :nth-child(an+b) 匹配下标 { an + b; n = 0, 1, 2, ...} 且结果为整数的子元素
nth-child(2n)/nth-child(even): 双行样式nth-child(2n+1)/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
164.写一个方法,输出'597646.26'+'798461.2646'的值
2022.03.25
163.代码输出
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 不能实现多路复用(腾讯)
HTTP/1.1 不是二进制传输,而是通过文本进行传输。由于没有流的概念,在使用并行传输(多路复用)传递数据时,接收端在接收到响应后,并不能区分多个响应分别对应的请求,所以无法将多个响应的结果重新进行组装,也就实现不了多路复用。
161.redux 为什么要把 reducer 设计成纯函数
redux三大原则
- 单一数据流 整个应用state都被储存在一个store里面 构成一个Object tree
- State是只读的 唯一改变state的方法就是触发action, action是一个用于描述已发生事件的普通对象
- 使用纯函数来执行修改 为了描述action如何改变state tree, 你需要编写reducers
把reducer设计成纯函数,可以实现时间旅行,记录/回放或者热加载
2022.03.24
160.为什么说JavaScript中函数是一等公民?
在编程语言中,一等公民有几个条件:可以作为函数参数,可以作为函数返回值,可以赋值给变量。
按照上面的条件,其实JavaScript中的数据都可以认为是一等公民~
无论是基础数据类型还是引用类型都是满足上面条件的。一般说JavaScript函数是一等公民实际上是相对其它编程语言而言,因为并不是所有的编程语言中函数都能满足上述条件。
159.JavaScript去重有几种方式?分别是?
150.你在开发过程中最常用的设计模式是什么?简述一下。
2022.03.23
158.预加载和懒加载的区别,预加载在什么时间加载合适
预加载是指在页面加载完成之前,提前将所需资源下载,之后使用的时候从缓存中调用;懒加载是延迟加载,按照一定的条件或者需求等到满足条件的时候再加载对应的资源两者主要区别是一个是提前加载,一个是迟缓甚至不加载。懒加载对服务器前端有一定的缓解压力作用,预加载则会增加服务器前端压力。
157.Mvvm与mvc的区别
Mvc模型视图控制器,视图是可以直接访问模型,所以,视图里面会包含模型信息,mvc关注的是模型不变,所以,在mvc中,模型不依赖视图,但是视图依赖模型Mvvm模型视图和vmvm是作为模型和视图的桥梁,当模型层数据改变,vm会检测到并通知视图层进行相应的修改
156.代码输出
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.03.22
155.什么情况下会遇到跨域,怎么解决?
跨域问题其实就是浏览器的同源策略造成的。
同源策略限制了从同一个源加载的文档或脚本如何与另一个源的资源进行交互。这是浏览器的一个用于隔离潜在恶意文件的重要的安全机制。同源指的是:协议、端口号、域名必须一致。
1)CORS
下面是MDN对于CORS的定义:
跨域资源共享(CORS) 是一种机制,它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain)上的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请求,它会在请求的头信息中增加一个Orign字段,该字段用来说明本次请求来自哪个源(协议+端口+域名),服务器会根据这个值来决定是否同意这次请求。如果Orign指定的域名在许可范围之内,服务器返回的响应就会多出以下信息头:
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 // 表示文档类型
如果Orign指定的域名不在许可范围之内,服务器会返回一个正常的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 是有序的。因此,当迭代的时候, Map 对象以插入的顺序返回键值。 | Object 的键是无序的 |
| Size | Map 的键值对个数可以轻易地通过size 属性获取 | Object 的键值对个数只能手动计算 |
| 迭代 | Map 是 iterable 的,所以可以直接被迭代。 | 迭代Object需要以某种方式获取它的键然后才能迭代。 |
| 性能 | 在频繁增删键值对的场景下表现更好。 | 在频繁添加和删除键值对的场景下未作出优化。 |
WeakMap 对象也是一组键值对的集合,其中的键是弱引用的。其键必须是对象,原始数据类型不能作为key值,而值可以是任意的。
153.Promise为什么能链式调用?
由于每次调用 .then 或者 .catch 都会返回一个新的 promise,从而实现了链式调用, 它并不像一般任务的链式调用一样return this。
2022.03.21
152.JS的各种位置,比clientHeight,scrollHeight,offsetHeight,以及scrollTop,offsetTop,clientTop的区别
clientHeight:表示的是可视区域的高度,不包含border和滚动条offsetHeight:表示可视区域的高度,包含了border和滚动条scrollHeight:表示了所有区域的高度,包含了因为滚动被隐藏的部分。clientTop:表示边框border的厚度,在未指定的情况下一般为0scrollTop:滚动后被隐藏的高度,获取对象相对于由offsetParent属性指定的父坐标(css定位的元素或body元素)距离顶端的高度。
151.v-for与v-if优先级(vue)
首先不要把v-if与v-for用在同一个元素上,原因:v-for比v-if优先,如果每一次都需要遍历整个数组,将会影响速度,尤其是当之需要渲染很小一部分的时候。v-for比v-if具有更高的优先级
150.应该在React组件的何处发起Ajax请求(react)
高阶组件是一个以组件为参数并返回一个新组件的函数。HOC运行你重用代码、逻辑和引导抽象。最常见的可能是Redux的connect函数。除了简单分享工具库和简单的组合,HOC最好的方式是共享React组件之间的行为。如果你发现你在不同的地方写了大量代码来做同一件事时,就应该考虑将代码重构为可重用的HOC
2022.03.18
149.怎么处理项目中的异常捕获行为?
1.try...catch
2.window.onerror
3.window.addEventListener('error', function, boolen)
4.window.addEventListener('unhandledrejection')
148.s什么是React高阶组件,适用于什么场景?
高阶组件就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件,他只是一个组件的设计模式,这种设计模式是由react自身的组合性质必然产生的。
适用场景:
- 代码复用
- 渲染劫持
- state抽象和更改
- props更改
147.promise有没有解决异步的问题?
Promise解决了callback回调地狱的问题,async/await是异步的终极解决方案
2022.03.17
146.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
})
})
145.Vuex和localStorage的区别是什么
1.vuex储存在内存,localstorage以文件的方式储存再本地
2.vuex能做到数据响应式,localstorage不能做到
3.刷新页面是vuex储存的值会丢失,localstorage不会丢失
144.为什么不建议使用通配符初始化css样式
1.影响网站的性能,虽然写着简单,但是通配符会把所有的标签都遍历一遍,当网站较大,样式较多的时候,这样就大大加强了网站的负载,会使网站加载时间延长
2.由于权重问题,会影响到其他没有选择器设置样式的标签继承来自父类的样式,因为通配符的权重是0,而默认情况视为null
143.三种事件模型是什么
- DOM0 级事件模型,这种模型不会传播,所以没有事件流的概念,但是现在有的浏览器支持以冒泡的方式实现,它可以在网页中直接定义监听函数,也可以通过 js 属性来指定监听函数。所有浏览器都兼容这种方式。直接在dom对象上注册事件名称,就是DOM0写法。
- IE 事件模型,在该事件模型中,一次事件共有两个过程,事件处理阶段和事件冒泡阶段。事件处理阶段会首先执行目标元素绑定的监听事件。然后是事件冒泡阶段,冒泡指的是事件从目标元素冒泡到 document,依次检查经过的节点是否绑定了事件监听函数,如果有则执行。这种模型通过attachEvent 来添加监听函数,可以添加多个监听函数,会按顺序依次执行。
- DOM2 级事件模型,在该事件模型中,一次事件共有三个过程,第一个过程是事件捕获阶段。捕获指的是事件从 document 一直向下传播到目标元素,依次检查经过的节点是否绑定了事件监听函数,如果有则执行。后面两个阶段和 IE 事件模型的两个阶段相同。这种事件模型,事件绑定的函数是addEventListener,其中第三个参数可以指定事件是否在捕获阶段执行。
2022.03.16
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,所以也没有[[construct]],不能使用 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 的继承先生成父类实例,再调用子类的构造函数修饰父类实例。
141.Promise.all实现原理
Promise.all = function (promises) {
const res = []
return new Promise((resolve, reject) => {
promises.forEach(promise => {
promise.then((value,idx) => {
res[idx] = value
if (res.length === promises.length) {
resolve(res)
}
}, reason => {
reject(reason)
})
})
})
}
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)
}
}, 1000)
})
}
Promise.retry(test)
.then(value => {
console.log("-> value", value);
}, reason => {
console.log("-> reason", reason);
})
2022.03.15
139.说说 HTTP 有哪些请求方法?那你知道 GET 和 POST 有什么区别吗?为什么 GET 方法中,URL有长度限制?POST和PUT又有什么区别呢?OPTIONS 了解多少?
常见的HTTP请求方法
- GET: 向服务器获取数据;
- POST:将实体提交到指定的资源,通常会造成服务器资源的修改;
- PUT:上传文件,更新数据;
- DELETE:删除服务器上的对象;
- HEAD:获取报文首部,与GET相比,不返回报文主体部分;
- OPTIONS:询问支持的请求方法,用来跨域请求;
- CONNECT:要求在与代理服务器通信时建立隧道,使用隧道进行TCP通信;
- TRACE: 回显服务器收到的请求,主要⽤于测试或诊断。 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的动态路由吗?怎么定义动态路由?如何获取传过来的动态参数?
(1)param方式
- 配置路由格式:
/router/:id - 传递的方式:在path后面跟上对应的值
- 传递后形成的路径:
/router/123
1)路由定义
//在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 获取传递的值
137.如何实现浏览器多个标签页之间的通信?
实现多个标签页之间的通信,本质上都是通过中介者模式来实现的。因为标签页之间没有办法直接通信,因此我们可以找一个中介者,让标签页和中介者进行通信,然后让这个中介者来进行消息的转发。通信方法如下:
- 使用 websocket 协议,因为 websocket 协议可以实现服务器推送,所以服务器就可以用来当做这个中介者。标签页通过向服务器发送数据,然后由服务器向其他标签页推送转发。
- 使用 ShareWorker 的方式,shareWorker 会在页面存在的生命周期内创建一个唯一的线程,并且开启多个页面也只会使用同一个线程。这个时候共享线程就可以充当中介者的角色。标签页间通过共享一个线程,然后通过这个共享的线程来实现数据的交换。
- 使用 localStorage 的方式,我们可以在一个标签页对 localStorage 的变化事件进行监听,然后当另一个标签页修改数据的时候,我们就可以通过这个监听事件来获取到数据。这个时候 localStorage 对象就是充当的中介者的角色。
- 使用 postMessage 方法,如果我们能够获得对应标签页的引用,就可以使用postMessage 方法,进行通信。
2022.03.14
136、怎么判断NaN?
Number.isNaN()、Object.is()
135、什么是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 的区域不会与浮动元素发生重叠,所以两侧就不会发生重叠,实现了自适应两栏布局。
134、了解HTTPS吗?请介绍一下
HTTPS的主要目的是提供对网站服务器的身份认证,保护交换数据的隐私与完整性。HTTP协议采用明文传输信息,存在信息窃听、信息篡改和信息劫持的风险,而协议TLS/SSL具有身份验证、信息加密和完整性校验的功能,可以避免此类问题发生。
133、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 中导出的值。如果读者了解 linux,这就有点像 linux 中的硬链接,指向同一个 inode。或者拿栈和堆来比喻,这就像两个指针指向了同一个栈。
来源:
www.zhihu.com/question/62…
2022.03.11
132.点击一个按钮,浏览器会做些什么事情?
- 1.点击按钮后创建一个Event实例
- 2.把事件放到事件队列中,等待处理
- 3.Event循环线程处理这个事件
- 4.沿着DOM路径找到触发事件的元素
- 5.如果这个元素上有处理这个事件的默认行为,并且要在DOM事件阶段周期之前执行,就执行它的默认行为
- 6.捕获阶段
- 7.目标阶段
- 8.冒泡阶段
- 9.如果这个元素上有处理这个事件的默认行为,并且要在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]"
130.什么是 Polyfill?
Polyfill指的是用于实现浏览器并不支持的原生API的代码,用来抹平不同浏览器对执行js的差异,解决兼容性
例如: querySelectorAll是很多现代浏览器都支持的原生WEB API,但是有些古老的浏览器并不支持,那么假设有人写了一段代码来实现这个功能使这些浏览器也支持这个功能,那么就可以成为一个Polyfill
2022.03.10
129.请简述webpack中的loaders与plugin的区别
功能上的区别:
loader:直译为加载器,作用是给 webpack 提供解析非 javascript 语言的能力。
plugin:直译为插件,通过监听 webpack 的广播调用 webpack 的 API 给 webpack 提供更多的功能。
使用方式的区别:
loader:在 module.rules 里面配置,类型是数组,每一项都是 Object,描述了对什么类型的文件(test),使用什么加载(loader)和使用的参数(options)。
plugin:在 pulgins 里面配置,类型是数组,每一项都是 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 循环中等待。
127. 上题如果想要每秒输出一个结果,怎么改
将 test 函数改造成:
async function test() {
for(let item of list) {
const res = await square(item);
console.log(res);
}
}
2022.03.09
126.浏览器的事件循环 与 node循环机制 的区别?
Node 中的 Event Loop 和浏览器中的是完全不相同的东西。
Node 的 Event Loop 分为 6 个阶段,它们会按照顺序反复运行。每当进入某一个阶段的时候,都会从对应的回调队列中取出函数去执行。当队列为空或者执行的回调函数数量到达系统设定的阈值,就会进入下一阶段。
(1)Timers(计时器阶段):初次进入事件循环,会从计时器阶段开始。此阶段会判断是否存在过期的计时器回调(包含 setTimeout 和 setInterval),如果存在则会执行所有过期的计时器回调,执行完毕后,如果回调中触发了相应的微任务,会接着执行所有微任务,执行完微任务后再进入 Pending callbacks 阶段。
(2)Pending callbacks:执行推迟到下一个循环迭代的I / O回调(系统调用相关的回调)。
(3)Idle/Prepare:仅供内部使用。
(4)Poll(轮询阶段):
- 当回调队列不为空时:会执行回调,若回调中触发了相应的微任务,这里的微任务执行时机和其他地方有所不同,不会等到所有回调执行完毕后才执行,而是针对每一个回调执行完毕后,就执行相应微任务。执行完所有的回调后,变为下面的情况。
- 当回调队列为空时(没有回调或所有回调执行完毕):但如果存在有计时器(setTimeout、setInterval和setImmediate)没有执行,会结束轮询阶段,进入 Check 阶段。否则会阻塞并等待任何正在执行的I/O操作完成,并马上执行相应的回调,直到所有回调执行完毕。
(5)Check(查询阶段):会检查是否存在 setImmediate 相关的回调,如果存在则执行所有回调,执行完毕后,如果回调中触发了相应的微任务,会接着执行所有微任务,执行完微任务后再进入 Close callbacks 阶段。
(6)Close callbacks:执行一些关闭回调,比如socket.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 永远执行在 macrotask 前面。
125.内存泄漏的事有遇到过吗?
以下四种情况会造成内存的泄漏:
- 意外的全局变量: 由于使用未声明的变量,而意外的创建了一个全局变量,而使这个变量一直留在内存中无法被回收。
- 被遗忘的计时器或回调函数: 设置了 setInterval 定时器,而忘记取消它,如果循环函数有对外部变量的引用的话,那么这个变量会被一直留在内存中,而无法被回收。
- 脱离 DOM 的引用: 获取一个 DOM 元素的引用,而后面这个元素被删除,由于一直保留了对这个元素的引用,所以它也无法被回收。
- 闭包: 不合理的使用闭包,从而导致某些变量一直被留在内存当中。
124.前端人员如何 预防 避免线上 p0 事故
开放题
123.http2.0 为什么还没有全面覆盖?
2022.03.08
121.JS如何解决单个任务执行时间过长
Web Worker 的作用,就是为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行。
Web Worker 有以下几个使用注意点:
- 同源限制:分配给 Worker 线程运行的脚本文件,必须与主线程的脚本文件同源。
- DOM 限制:Worker 线程所在的全局对象,与主线程不一样,无法读取主线程所在网页的 DOM 对象,也无法使用document、window、parent这些对象。但是,Worker 线程可以navigator对象和lWorker 线程不能执行alert()方法和confirm()方法,但可以使用 XMLHttpRequest 对象发出 AJAX 请求。ocation对象。
- 通信联系:Worker 线程和主线程不在同一个上下文环境,它们不能直接通信,必须通过消息完成。使用worker.postMessage()通信。
- 脚本限制: Worker 线程不能执行alert()方法和confirm()方法,但可以使用 XMLHttpRequest 对象发出 AJAX 请求。
- 文件限制:Worker 线程无法读取本地文件,即不能打开本机的文件系统(file://),它所加载的脚本,必须来自网络。
120.Vue nextTick原理
当dom发生变化,更新后执行的回调。将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。
主要思路就是采用微任务优先的方式调用异步方法去执行 nextTick 包装的方法
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;
}
}
112.取出字符串中第一个出现一次的字符
2022.03.07
118、什么是jsonp工作原理是什么?他为什么不是真正的ajax
Jsonp其实就是一个跨域解决方案。Js跨域请求数据是不可以的,但是js跨域请求js脚本是可以的。所以可以把要请求的数据封装成一个js语句,做一个方法的调用。跨域请求js脚本可以得到此脚本。得到js脚本之后会立即执行。可以把数据做为参数传递到方法中。就可以获得数据。从而解决跨域问题。
jsonp原理:(动态创建script标签,回调函数)浏览器在js请求中,是允许通过script标签的src跨域请求,可以在请求的结果中添加回调方法名,在请求页面中定义方法,就可获取到跨域请求的数据。
为什么不是真正的ajax?
1、ajax和jsonp这两种技术在调用方式上"看起来"很像,目的也一样,都是请求一个url,然后把服务器返回的数据进行处理,因此jquery和ext等框架都把jsonp作为ajax的一种形式进行了封装;
2、但ajax和jsonp其实本质上是不同的东西。ajax的核心是通过XmlHttpRequest获取本页内容,而jsonp的核心则是动态添加<script>标签来调用服务器提供的js脚本。
3、所以说,其实ajax与jsonp的区别不在于是否跨域,ajax通过服务端代理一样可以实现跨域,jsonp本身也不排斥同域的数据的获取。
4、还有就是,jsonp是一种方式或者说非强制协议,如同ajax一样,它也不一定非要json格式来传递数据,如果你愿意,字符换也行,只不过这样不利于jsonp提供公开服务。
117、overflow清除浮动的原理
块格式化上下文是CSS可视化渲染的一部分,它是一块区域,规定了内部块盒的渲染方式,以及浮动相互之间的影响关系当元素设置了overflow样式且值不为visible时,该元素就构建了一个BFC,BFC在计算高度时,内部浮动元素的高度也要计算在内,也就是说技术BFC区域内只有一个浮动元素,BFC的高度也不会发生塌缩,所以达到了清除浮动的目的
116、JS怎么控制一次加载一张图片,加载完后再加载下一张
方法一
<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
2022.03.04
115、对对象与数组的解构的理解
解构是 ES6 提供的一种新的提取数据的模式,这种模式能够从对象或数组里有针对性地拿到想要的数值。
1)数组的解构
在解构数组时,以元素的位置为匹配条件来提取想要的数据的:
const [a, b, c] = [1, 2, 3]
最终,a、b、c分别被赋予了数组第0、1、2个索引位的值:
数组里的0、1、2索引位的元素值,精准地被映射到了左侧的第0、1、2个变量里去,这就是数组解构的工作模式。还可以通过给左侧变量数组设置空占位的方式,实现对数组中某几个元素的精准提取:
const [a,,c] = [1,2,3]
通过把中间位留空,可以顺利地把数组第一位和最后一位的值赋给 a、c 两个变量:
2)对象的解构
对象解构比数组结构稍微复杂一些,也更显强大。在解构对象时,是以属性的名称为匹配条件,来提取想要的数据的。现在定义一个对象:
const stu = {
name: 'Bob',
age: 24
}
假如想要解构它的两个自有属性,可以这样:
const { name, age } = stu
这样就得到了 name 和 age 两个和 stu 平级的变量:
注意,对象解构严格以属性名作为定位依据,所以就算调换了 name 和 age 的位置,结果也是一样的:
const { age, name } = stu
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/reject中存放的方法执行,打印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
- promise1 是 pending: 这个 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对象来实现同步的效果。
113、浏览器渲染进程的线程有哪些
浏览器的渲染进程的线程总共有五种:
(1)GUI渲染线程
负责渲染浏览器页面,解析HTML、CSS,构建DOM树、构建CSSOM树、构建渲染树和绘制页面;当界面需要重绘或由于某种操作引发回流时,该线程就会执行。
注意:GUI渲染线程和JS引擎线程是互斥的,当JS引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。
(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,则默认为4ms。
(5)异步http请求线程
- XMLHttpRequest连接后通过浏览器新开一个线程请求;
- 检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将回调函数放入事件队列中,等待JS引擎空闲后执行;
2022.03.03
112.vue2对于对象和数组的响应式原理有啥区别?都有什么缺陷?如何解决?
111.隐藏一个元素有多少种方法
1、display:none:元素彻底消失(不会触发其点击事件),会产生回流和重绘
2、visibility:hidden:无法触发其点击事件,方法元素也是消失了,依然占据着页面空间,只会引起页面重绘
3、opacity:0:可以触发点击事件,只会引起页面重绘
4、height:0:将元素的高度设置为0,并且设置overflow:hidden。这个元素相当于消失了,即无法触发点击事件
5、设置元素的position与left,top,bottom,right等,将元素移出至屏幕外
6、设置元素的position与z-index,将z-index设置成尽量小的负数
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)
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
}
2022.03.02
108. flex:1 的三个属性以及其特点
107. 什么是稳定排序和不稳定排序?哪些排序是稳定的,哪些是不稳定的?为什么?
稳定不稳定是说序列中相同元素经过排序后,他们的先后顺序不变。如果变了是不稳定排序,不变是稳定排序。
稳定排序包括插入排序、冒泡排序、归并排序、基数排序
插入排序: 在一个有序的序列中插入一个数,使插入后的序列保持有序。因为插入的过程中都是从后向前进行查找,遇到小于等于(或大于等于)的数停止寻找,进行插入操作。不改变排序前后相等数值的相对顺序,故使稳定的排序算法。
冒泡排序: 冒泡故名思义,数值小的向上飘,数值大的向下沉,向上飘的数遇到的小于等于当前数的值停止,向下沉的数遇到大于等于当前数的数停止,类似于对于向上飘的数有个排序之前在其前面数值相等限制了其向上飘的脚步,原先在俺之下,排序后也在俺之下,向下沉也是同理。故也是稳定的排序算法。
归并排序: 将一段序列分为若干个小序列进行排序,排序后的小序列进行合并得到最后的排序结果。主要运用了分治的思想。分成的前后若干个小序列在最后进行合并时本身就包含了前后位置信息,在合并时不改变相同值在排序前后的相对顺序,故归并排序也是稳定排序。
基数排序: 按从低到高的相应位的值进行排序,也是稳定排序算法。
非稳定排序算法包括:选择排序、快速排序、希尔排序、堆排序
选择排序: [1,2,4,2,5,3 ]
主要思想是分别找出当前遍历元素中的最小值与相应位置的数进行交换,第一遍寻找元素的从第一个元素起的最小值(或最大值)和第一个元素进行交换,第二趟寻找从第二个元素起最小的(或最大的)元素与第二个元素进行交换,以此类推。排序前 [3,3',2] 排序后 [2,3',3]
快速排序: 排序前[5,3,3,4,3',8,7] 排序后[3',3,3,4,5,8,7]
希尔排序: 一次插入排序是稳定的,多次插入排序不是稳定的
106. 动画的优化,为什么transform 比margin的性能好。
1.margin是属于布局属性,该属性的变化会导致页面的重排。 对布局属性进行动画,浏览器需要为每一帧进行重绘并上传到GPU中进行渲染
2.transform是合成属性,浏览器会为元素创建一个独立的复合层,当元素内容没有发生变化,该层不会被重绘,通过重新复合来创建动画
105. 说一下vue-router 的实现原理
附加
现有一个div,里边用了data的变量a,值为1,两秒之后将a改为3。从模板编译、数据绑定、依赖收集、派发更新说下页面由1到变为3的全套流程
2022.03.01
104.JavaScript数组在V8中做了什么优化
相关文章:zhuanlan.zhihu.com/p/29638866
www.cnblogs.com/vivotech/p/…
103.CSS中如何解决同一行中的inline-block元素对齐问题
1、暴力float,当然这是备选方案,毕竟脱离文档流后页面元素会不好控制
2、简单粗暴地给所有元素都加上内容,例如空格符
3、设置所有内联元素 vertical-align: top/middle/bottom; 属性,改变默认设置。
102.canvas和svg哪个更适合用来做动画,为什么
canvas
1.它是通过JavaScript来绘制的
2.只要它的位置发生改变,就需要重新绘制
3.它是逐像素的进行渲染
4.依赖屏幕的分辨率
5.弱的文本渲染能力
6.不支持事件处理
7.能够jpg、png图像保存
8.适合图像密集的游戏,能够重复渲染
svg
1.使用XML的2d语言
2.不依赖屏幕的分辨率
3.支持事件处理
4.适合带有大型渲染区域的应用程序
5.不适合游戏
6.复杂度越高渲染速度会越慢
适用范围
Canvas是逐像素进行渲染的,一旦图形绘制完成,就不会继续被浏览器关注。而SVG是通过DOM操作来显示的。
所以Canvas的文本渲染能力弱,而SVG最适合带有大型渲染区域的应用程序,比如地图。
而Canvas 最适合有许多对象要被频繁重绘的图形密集型游戏。
而SVG由于DOM操作 在复杂度高的游戏应用中 会减慢渲染速度。所以不适合在游戏应用。