问题先导
-
grid
布局的理解和使用【CSS】 -
for in
和for of
的区别【js基础】 -
ajax
、axios
和fetch
的区别【js基础】 - 数组遍历有哪些方法?有什么区别?【js基础】
- template和jsx有什么区别【Vue】
- Vue初始化页面闪烁的问题如何解决【Vue】
-
Vue.extend
有什么作用【Vue】 - 将数字每千分位使用逗号隔开【手写代码】
- 实现非负大整数的相加【手写代码】
- 实现add(1)(2)(3)与add(1, 2)(3)的结果相等【手写代码】
- 代码输出(Promise相关)【输出结果】
知识梳理
grid布局的理解和使用
grid
布局又称网格布局,是一种二维布局,使用网格布局的元素就像像表格一样,网格布局让我们能够按行或列来对齐元素。 然而在布局上,网格比表格更可能做到或更简单。 例如,网格容器的子元素可以自己定位,以便它们像CSS定位的元素一样,真正的有重叠和层次。
使用网格布局,我们需要将元素的display
设置为grid
或inline-grid
。
网格布局的一些基本的概念:
轨道(行列)
网格就像一个表格,有行和列划分出多个网格单元格,一行或者一列就可以称为网格的一个轨道。使用grid-template-columns
和grid-template-rows
来分别设置行列的布局情况。值有多种形式:
- 像素或百分比:如
grid-template-columns: 100px 20% 20% auto;
表示划分为四列的网格布局。 - 按比例布局:使用特殊单位
fr
,可以用于表示单元格的比例,如100px 1fr 2fr
表示该网格划分为三列,第一列占100px宽度,剩下列两列按照1:2
的比例平分。 - 重复函数
repeat
:repeat(3, 1fr 2fr)
表示重复1fr 2fr
三次。 - 长度边界:有时候我们希望宽度有最小或最大值,就可以使用
minmax
来设置,比如minmax(100px, auto)
表示该行或列最小长度为100px,而最大值会根据内容自动扩展。 - 弹性布局:有时候我们不能确定列数,就可以使用
auto-fill
来设置动态盒模型:repeat(auto-fill, minmax(100px, auto))
,这样,盒子就能根据整体长度自适应变化,来调整列数。
网格线
当我们设计好了网格的行列值,就可以再具体划分网格区域,这就需要用到网格线。
应该注意的是,当我们定义网格时,我们定义的是网格轨道,而不是网格线。Grid 会为我们创建编号的网格线来让我们来定位每一个网格元素. 例如下面这个三列两行的网格中,就拥有四条纵向的网格线。
默认每个子元素占据一个网格单元,如果需要某个子元素占据多个单元,就可以用grid-column-start
、grid-column-end
、grid-row-start
和grid-row-end
四个属性来控制该单元格被哪些网格线所包围。
值得注意的是,我们还能让两个单元格重叠,共用一部分网络单元格,这时候会出现遮挡叠加效果,我们可以使用z-index
来控制单元格的层级。
网格间距
使用grid-column-gap
和grid-row-gap
来设置行列间距。
其他
到这里,网格布局的基本使用已经差不多了,还有一些细节可通过其他属性来控制。常用的有:
justify-content
:设置单元格水平对齐方式,类似flex
布局的justify-content
,属性名一样,但可选值不一样。align-content
:设置单元格的垂直对齐方式,类似flex
布局的align-items
,同样可选值也有差异,但值得注意的是,flex
也有align-content
属性,定义的是多根轴线的对齐方式。place-content
:上面两个属性的总和就是place-content
。grid-auto-flow
:设置网格流动方向,默认是先行后列,也就是从左往右再从上往下,我们也可以改为column
让流动方向变成先列后行,这和flex
中的flex-direction
类似,此外flex
还多了一个flex-wrap
来控制换行的方式,两个属性联合就是flex-flow
属性了。
还有一些别的属性说明可以参考:CSS Grid 网格布局教程。
参考:
for in 和 for of 的区别
for…of 是ES6新增的遍历方式,允许遍历一个含有iterator
可迭代接口的数据结构(数组、对象等)并且返回各项的值,和ES3中的for…in的区别如下:
- for…of 遍历获取的是对象的键值,for…in 获取的是对象的键名。
- for… in 会遍历对象的整个原型链,性能非常差不推荐使用,而 for … of 只遍历当前对象不会遍历原型链。
ajax、axios、fetch的区别
老生常谈的ajax
,05年之前本身并不是一种新技术,而是作为一种新术语出现用来描述一种使用现有技术集合的‘新’方法,包括: HTML 或 XHTML, CSS, JavaScript, DOM, XML, XSLT, 以及最重要的 XMLHttpRequest。AJAX模型以后, 网页应用能够快速地将增量更新呈现在用户界面上,而不需要重载(刷新)整个页面。这使得程序能够更快地回应用户的操作。
但ajax
一直没有规范的封装体,类似jquery ajax
封装地已经十分便捷好用了,但还是有一些缺陷:
- 本身是针对MVC编程,不符合前端MVVM的浪潮
- 基于原生XHR开发,XHR本身的架构不清晰
- 不符合关注分离(Separation of Concerns)的原则
- 配置和调用方式非常混乱,而且基于事件的异步模型不友好。
而ea6提出的Fetch
的问世正是为了提供一个获取资源的接口(包括跨域请求),提供了更强大和灵活的功能集。但目前大多数浏览器仍处于实验阶段,应用程度不高。
fetch
带来了以下好处:
- 语法简洁,更加语义化
- 基于标准 Promise 实现,支持 async/await
- 更加底层,提供的API丰富(request, response)
- 脱离了XHR,是ES规范里新的实现方式
而Axios
是一个js库,是基于Promise
封装的HTTP客户端,同时支持浏览器端和Node端使用,符合最新的ES规范,封装了一些常规的请求和响应API,是jquery ajax
的替代品,底层仍然是XMLHttpRequest
对象。官网地址:www.axios-js.com/。
总结来说就是,ajax
这种技术合集,由于XMLHttpRequest
本身不太友好的设计,让网络请求的发起和数据处理比较繁琐,之后出现的Juqery Ajax
封装了较为实用的版本,但由于jquery
逐渐退出历史舞台和Promise
的出现,出现了类似Axios
这样的HTTP请求库,让异步请求的发起更加方便,而Fetch
是ES6官方提出的一种ajax封装API,同样基于Promise
,但摒弃了XMLHttpRequest
对象,实用更新的规范,语法也更加简洁,目前的缺点就是大多数浏览器还处于试验阶段,大都还不支持Fetch API
。
参考:
数组有哪些遍历方法,有什么区别
方法 | 改变原数组 | 特点 | |||
---|---|---|---|---|---|
forEach() | 否 | 不改变原数组,没有返回值 | |||
map() | 否 | 不改变原数组,通过遍历返回值生成新的数组 | |||
filter() | 否 | 过滤数组,返回包含符合条件的元素的数组 | |||
for…of | 否 | for…of遍历具有Iterator迭代器的对象的属性,返回的是数组的元素、对象的属性值,不能遍历普通的obj对象,将异步循环变成同步循环 | |||
every() 和 some() | 否 | 数组元素检测方法,some()只要有一个是true,便返回true;而every()只要有一个是false,便返回false. | |||
find() 和 findIndex() | 否 | 数组元素查找方法,find()返回的是第一个符合条件的值;findIndex()返回的是第一个返回条件的值的索引值 | |||
reduce() 和 reduceRight() | 否 | 接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。reduce()对数组正序操作;reduceRight()对数组逆序操作 |
template和jsx的有什么分别
对于 runtime 来说,只需要保证组件存在 render 函数即可,而有了预编译之后,只需要保证构建过程中生成 render 函数就可以。在 webpack 中,使用vue-loader
编译.vue文件,内部依赖的vue-template-compiler
模块,在 webpack 构建过程中,将template预编译成 render 函数。与 react 类似,在添加了jsx的语法糖解析器babel-plugin-transform-vue-jsx
之后,就可以直接手写render函数。
所以,template和jsx的都是render的一种表现形式,不同的是:JSX相对于template而言,具有更高的灵活性,在复杂的组件中,更具有优势,而 template 虽然显得有些呆滞。但是 template 在代码结构上更符合视图与逻辑分离的习惯,更简单、更直观、更好维护。
vue初始化页面闪动现象如何解决
使用vue开发时,在vue初始化之前,由于div是不归vue管的,所以我们写的代码在还没有解析的情况下会容易出现花屏现象,看到类似于{{message}}的字样,虽然一般情况下这个时间很短暂,但是还是有必要让解决这个问题的。
我们可以在css中加入隐藏元素的代码,让其在初始化之前处于隐藏状态:
[v-cloak] { display: none;}
复制代码
如果没有彻底解决问题,则在根元素加上style="display: none;" :style="{display: 'block'}"
extend有什么作用
这个 API 很少用到,作用是扩展组件生成一个构造器,通常会与 $mount
一起使用。使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。data
选项是特例,需要注意 – 在 Vue.extend()
中它必须是函数。
// 创建构造器
var Profile = Vue.extend({
template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
data: function () {
return {
firstName: 'Walter',
lastName: 'White',
alias: 'Heisenberg'
}
}
})
// 创建 Profile 实例,并挂载到一个元素上。
new Profile().$mount('#mount-point')
复制代码
将数字每千分位用逗号隔开
很简单的一题,有多种实现思路,这里提供其中一种:
/**
* 将数字以某个分隔符分割成若干片段
* @param {number} number 数字
* @param {number} sliceLen 片段长度
* @param {string} split 分割符
* @returns string
*/
function numberFormat(number, sliceLen, split) {
if(sliceLen <= 0 || isNaN(sliceLen)) {
sliceLen = 1;
}
const numberStr = number.toString();
const len = numberStr.length;
if(len <= sliceLen) {
return numberStr;
}
if(typeof split !== 'string') {
split = ',';
}
let str = '', count = 0;
for(let i = 0; i < len; i++) {
str += numberStr[i];
if(count === sliceLen - 1 && i != len - 1) {
str += split;
count = 0;
} else {
count++;
}
}
return str;
}
复制代码
实现非负大整数相加
我们知道由于存储位置的限制,整数和小数都是有安全范围的,当整数超过最大安全值Number.MAX_SAFE_INTEGER
(2^52 – 1),或小数小于最小值Number.EPSILON
(2 ^ -52)时,数字之间的计算就不准确了。
因此ES6提出了大整数BigInt
用于存储这些超过范围的整数,进而实现大整数的计算。当然本题为了实现大整数相加,是不借用BigInt
对象的。方法就是模拟加法的计算规则,从个位开始相加,不断进位再相加,最终得到答案。
/**
* 非负大整数相加
* @param {string} n1Str
* @param {string} n2Str
*/
function addBignumber(n1Str, n2Str) {
const len1 = n1Str.length;
const len2 = n2Str.length;
let i = 1; // 1表示个位
let temp = 0; // 进位值
let res = '';
while(!((i > len1 || i > len2) && temp == 0)) {
const a = +(n1Str[len1 - i] || 0);
const b = +(n2Str[len2 - i] || 0);
const sum = (a + b + temp).toString();
res = sum.slice(-1) + res;
temp = parseInt(sum.slice(0, sum.length - 1) || '0');
i++;
}
if(i <= len1) {
res = n1Str.slice(0, len1 - i + 1) + res;
} else if(i <= len2) {
res = n2Str.slice(0, len2 - i + 1) + res;
}
return res;
}
复制代码
实现add(1)(2)(3)相加结果等于add(1, 2)(3)
本题的目的就是实现一个柯里化函数。这在之前的练习我们已经写过。
/**
* 柯里化函数
* @param {Function} fn
* @param {...any} args
*/
function curry(fn, ...args) {
if(fn.length <= args.length) {
return fn.apply(this, args);
}
return curry.bind(this, fn, ...args);
}
/**
* 三数求和
*/
function add(a, b, c) {
debugger
return a + b + c;
}
const curryAdd = curry(add);
console.log(curryAdd(1)(2)(3) === curryAdd(1, 2)(3))
复制代码
代码输出(Promise相关)
代码片段:
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')
})
})
复制代码
本题主要考察process.nextTick
的使用,这是node.js
中的api
。在node中,同浏览器端一样代码处于轮询执行的过程,比如浏览器端就是:宏任务和微任务不断交替执行的过程,期间可能不断会有异步让宏任务和微任务进入任务队列。
node端也是一样,它的事件循环机制分为以下几个阶段:
- 定时器:本阶段执行已经被
setTimeout()
和setInterval()
的调度回调函数。 - 待定回调:执行延迟到下一个循环迭代的 I/O 回调。
- idle, prepare:仅系统内部使用。
- 轮询:检索新的 I/O 事件;执行与 I/O 相关的回调(几乎所有情况下,除了关闭的回调函数,那些由计时器和
setImmediate()
调度的之外),其余情况 node 将在适当的时候在此阻塞。 - 检测:
setImmediate()
回调函数在这里执行。 - 关闭的回调函数:一些关闭的回调函数,如:
socket.on('close', ...)
。
感到费解的请参考nexttick – node.js中的说明,实际上和浏览器端的宏任务微任务无差异。
而setImmediate()
和 setTimeout()
很类似,但是基于被调用的时机,他们也有不同表现。
setImmediate()
设计为一旦在当前 轮询 阶段完成, 就执行脚本。setTimeout()
在最小阈值(ms 单位)过后运行脚本。
就用户而言,我们有两个类似的调用,但它们的名称令人费解。
process.nextTick()
在同一个阶段立即执行。setImmediate()
在事件循环的接下来的迭代或 ‘tick’ 上触发。
实质上,这两个名称应该交换,因为 process.nextTick()
比 setImmediate()
触发得更快,但这是过去遗留问题,因此不太可能改变。如果贸然进行名称交换,将破坏 npm 上的大部分软件包。每天都有更多新的模块在增加,这意味着我们要多等待每一天,则更多潜在破坏会发生。尽管这些名称使人感到困惑,但它们本身名字不会改变。
基于上面API的分析,我们来看具体代码片段的执行流程分析:
console.log('1'); // 1.打印1
// 2.定时器(可以理解为异步宏任务进入队列)
setTimeout(function() {
// 10.打印2
console.log('2');
// 11.加入立即阶段执行回调
process.nextTick(function() {
// 15.打印3,执行下一个阶段:微任务
console.log('3');
})
// 12.执行Promise
new Promise(function(resolve) {
// 13.打印4
console.log('4');
// 14.异步微任务进入队列,执行阶段 nextTick
resolve();
}).then(function() {
// 16.打印5,执行下一个宏任务
console.log('5')
})
})
// 3.加入立即阶段执行回调
process.nextTick(function() {
// 8.打印6。一个轮询执行结束,执行 setImmediate 并开启下一个事件轮询:执行微任务队列
console.log('6');
})
// 4.执行异步Promise
new Promise(function(resolve) {
// 5.打印7
console.log('7');
// 6.异步微任务进入队列
resolve();
}).then(function() {
// 9.打印8,执行宏任务队列
console.log('8')
})
// 7.定时器(异步宏任务进入队列),一个阶段执行结束,执行 nextTick
setTimeout(function() {
// 17.打印9
console.log('9');
// 18.加入立即阶段执行回调
process.nextTick(function() {
// 22.打印10,nextTick执行完毕,开启下一个阶段:执行微任务队列
console.log('10');
})
// 19.执行Promise
new Promise(function(resolve) {
// 20.打印11
console.log('11');
// 21.异步微任务进入队列,宏任务执行结束,执行阶段回调函数 nextTick
resolve();
}).then(function() {
// 23.打印12
console.log('12')
})
})
复制代码
收集打印信息和执行序号,就可以得到输出结果(对于这种很长的代码,执行过的代码可以注释掉以清晰视野):
1
7
6
8
2
4
3
5
9
11
10
12
复制代码
参考:
代码片段:
console.log(1)
setTimeout(() => {
console.log(2)
})
new Promise(resolve => {
console.log(3)
resolve(4)
}).then(d => console.log(d))
setTimeout(() => {
console.log(5)
new Promise(resolve => {
resolve(6)
}).then(d => console.log(d))
})
setTimeout(() => {
console.log(7)
})
console.log(8)
复制代码
执行流程分析:
// 1.打印1
console.log(1)
// 2.加入异步宏任务队列01
setTimeout(() => {
// 10.打印2,执行微任务队列(无),继续执行宏任务队列02
console.log(2)
})
// 3.执行Promise
new Promise(resolve => {
// 4.打印3
console.log(3)
// 5.加入异步微任务队列
resolve(4)
}).then(d => console.log(d)) // 9.打印4,微任务队列执行结束,执行宏任务队列01
// 6.加入异步宏任务队列02
setTimeout(() => {
// 11.打印5
console.log(5)
// 12.执行Promise
new Promise(resolve => {
// 13.加入异步微任务队列,宏任务执行结束,执行下一个微任务队列
resolve(6)
}).then(d => console.log(d)) // 14.打印6,执行下一个宏任务队列03
})
// 7.加入异步宏任务队列03
setTimeout(() => {
// 15.打印7
console.log(7)
})
// 8.打印8,宏任务执行结束,执行微任务队列
console.log(8)
复制代码
打印结果:
1
3
8
4
2
5
6
7
复制代码
代码片段:
setTimeout(() => {
console.log(1);
}, 0);
new Promise((resolve, reject) => {
console.log(2);
resolve(3);
new Promise(resolve2 => {
console.log(4);
resolve2(5);
}).then((r) => {
console.log(r);
});
}).then(r => {
console.log(r);
});
new Promise(resolve2 => {
console.log(6);
resolve2(7);
}).then((r) => {
console.log(r);
});
复制代码
本题有个小细节,就是Promise内部的Promise回调会先于父Promie进入异步微任务队列,所以最终的输出结果为:
2
4
6
5
3
7
1
复制代码