[TOC]
答过的笔试题
1. 写出打印的值
1.1 变量定义提升
console.log(a); // 1
function a() {}
var a = 5;
function b() {
console.log(a); // 2
var a = 3;
console.log(a); // 3
}
b();
// 输出结果:
// function a() {}
// undefined
// 3
1、第一个console:全局中,函数声明会提升,变量定义会提升,当发现一个变量已经定义后,便不在提升,所以第一个输出了函数定义。
2、在函数内部,变量定义也会提升到函数内部的顶部,此时的值还是undefined,由于函数内部已经定义变量a,便不会去全局对象中查找,所以输出undefined。
3、变量a已经赋值为3,所以输出3
1.2 箭头函数绑定问题
var c = (v) => {
console.log(this.name)
console.log(v)
}
var d = {name: 1}
c.call(d, 2)
// 输出结果:
// undefined // node里面输出是undefined, Chrome终端里面输出的是空
// 2
1.3 var变量for循环输出问题
for(var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i)
}, 1000)
}
console.log(i)
// 输出结果:
// 5 马上输出
// 5 5 5 5 5 1秒后,输出5个5
上面代码修改成输出1 2 3 4 5
第一个解决办法:在for循环里面定义一个临时变量,然后打印这个临时变量。
for(var i = 0; i < 5; i++) {
let tmp = i;
setTimeout(function() {
console.log(tmp)
}, 1000)
}
console.log(i)
第二种解决办法:setTimeout
方法使用立即执行函数包裹上,传一个变量
for(var i = 0; i < 5; i++) {
(function(b) {
setTimeout(function() {
console.log(b)
}, 1000)
})(i)
}
console.log(i)
如果代码最后一行不用输出i,for循环中定义的i
使用let
来声明,可以实现循环打印1到0.
1.4 async异步函数问题
async function async1() {
console.log('1')
await async2()
console.log('2')
}
async function async2() {
console.log('3')
}
console.log('4')
setTimeout(() => {
console.log('5')
},0)
async1()
new Promise((resolve) => {
console.log('6')
resolve()
}).then(() => {
console.log('7')
})
console.log('8')
// 输出结果:
// 4、1、3、6、8、2、7、5
执行过程:
第一步:执行同步:1、输出4、setTimeout回调放入回调队列,2、执行async1(),输出1,执行async2(),输出3,返回一个Promise,async1()函数的await处处于Promise的then,放入回调队列等待执行。3、执行new Promise
输出6,then放入回调队列。执行console.log('8'),输出8。总体输出:4、1、3、6、8
第二步:开始去回调队列执行可执行的回调:
是否有可执行的微任务(主要是Promise),有的话取出所有微任务执行,发现有2个,执行async1()中的微任务输出2,执行new Promise()
回调,输出7。没有可执行的微任务了,结束微任务。
取出一个宏任务,发现有一个setTimeout,取出执行,输出5.
1.5 setTimeout和async
console.log(1)
setTimeout(() => {console.log('2')},1000) // 第一个setTimeout回调
async function fn() {
console.log('3');
setTimeout(() => {console.log('4')},20) // 第二个setTimeout回调
}
async function run() {
console.log('5');
await fn()
console.log('6');
}
run()
var time = (new Date()).getTime()
// 等待150毫秒
while(true) {
if (time + 150 < (new Date()).getTime()) {
break;
}
}
setTimeout(() => { // 三个setTimeout回调
console.log('7');
new Promise((resolve) => {
console.log('8')
resolve()
}).then(() => { console.log('9') })
},0)
console.log('10')
// 输出结果:
// 1、5、3、10、6、4、7、8、9、2
执行过程:
1、执行一个宏任务(同步任务): 执行console输出1,执行run()异步函数,执行console输出5,执行函数fn(),执行console输出3,一个setTimeout放入回调队列,fn()函数执行完毕,await等待到一个Promise回调,放入到回调队列,run()执行完毕,等待状态。while循环,消耗了150毫秒,接着执行,遇到一个setTimeout放到回调队列,执行console输出10.
第一个宏任务执行结束,输出:1、5、3、10。此时回调队列有3个setTimeout回调。
2、查看是否有微任务可执行,经查找,没有,忽略。
3、执行一个宏任务,有一个setTimeout可执行,执行console输出4。
4、查看是否有微任务,没有,执行一个宏任务,发现有一个可执行的,执行:执行console输出7,执行new Promise
,执行console输出8,一个微任务放到回调队列。
5、查看是否有微任务可执行,经查找有一个可以执行,执行微任务:执行console输出9。
6、执行一个宏任务,有一个可以执行了,执行console输出2.
7、执行结束。
1.6 let变量不会绑定到window上
let obj = {
foo: function() {
console.log(this.bar)
},
bar: 1
}
let foo = obj.foo;
let bar = 2;
obj.foo() // 1
foo() // undefined
由于foo方法中输出this.bar
,全局中,使用let声明的bar不会绑定到window上面,所以单独调用foo方法中,输出undefined
。
2. 写出几个vue或react常用的声明周期狗子函数
挂载阶段
constructor:初始化构造函数
static getDerivedStateFromProps:调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用
render():渲染的html元素,react类组件唯一必须实现的方法
componentDidMount():会在组件挂载后(插入 DOM 树中)立即调用
更新阶段
getDerivedStateFromProps():上面有说明
shouldComponentUpdate():props或state变化,render()渲染之前调用,返回默认true(需要渲染页面)
render():上面有说明
getSnapshotBeforeUpdate():在
render()
之后,渲染输出(提交到DOM节点)之前调用componentDidUpdate(): 更新后调用,首次渲染不会执行此方法
卸载阶段
componentWillUnmount():组件卸载及销毁之前直接调用
3. 写一个递归方法,把数组[1,2,[3,[4,5],6],7]
,变成一维数组[1,2,3,4,5,6,7]
function func(arr) {
var newArr = []
for(var i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i])) {
newArr = newArr.concat(func(arr[i]))
} else {
newArr.push(arr[i])
}
}
return newArr
}
var arr = [1,2,[3,[4,5],6],7];
var newArr = func(arr)
console.log('newArr:', newArr)
4. 字符串相邻字符去重aaabbdebb=>abdeb
给一个字符串,把字符串中相邻重复的字符去掉,比如aaabbdebb
变成abdeb
var str = 'aaabbdebb'
function deleteRepeat(str) {
var newStr = str[0]
var tmp = str[0]
for (var i = 1; i < str.length; i++) {
if (tmp !== str[i]) {
newStr += str[i]
tmp = str[i]
}
}
return newStr;
}
var t = deleteRepeat(str);
console.log(t)
5. get1_by2_list334_android7
删除偶数和_
给一个字符串:get1_by2_list334_android7
(每个单词后偶棉携带一个数字),删除其中偶数和_
,不用不用循环,只用正则,如何实现输出get1bylistandroid7
var str = 'get1_by2_list334_android7';
var result = str.replace(/(\d*[02468])*_*/g, '')
console.log(result);
6.不用循环语句和迭代器实现0到1000数组赋值
不使用任何循环控制语句和迭代器的情况下,实现一个0到1000的数组赋值。
var i = 0;
var arr = [];
function fn() {
arr[i] = i
i++;
if (i > 1000) {
clearInterval(id)
console.log(arr)
}
}
var id = setInterval (fn, 0);
7. JSON格式的树状结构生成一个dom树
将类似以下JSON表示的树状结构(可以无限层级)通过parseDOM函数(使用document.createElement,document.createTextNode,appendChild等方法)生成一颗DOM树(返回一个element元素)
const jsonTree = {
"tagName": "ul",
"props": {
"className": "list",
"data-name": "jsontree"
},
"children": [{
"tagName": "li",
"children": [{
"tagName": "img",
"props": {
"src": "//img.alicdn.com/tps/TB1HwXxLpXXXXchapXXXXXXXXXX-32-32.ico",
"width": "16px"
}
}]
},
{
"tagName": "li",
"children": [{
"tagName": "a",
"props": {
"href": "https://www.aliyun.com",
"target": "_blank"
},
"children": "阿里云"
}]
}
]
};
function parseDOM(jsontree){
const {tagName,props,children} = jsontree;
const element = document.createElement(tagName);
// 添加属性
if (typeof props === 'object') {
for (var key in props) {
element.setAttribute(key, props[key]);
}
}
// 添加子元素
if (children) {
if (typeof children === 'string') {
element.innerHTML = children;
} else if (Array.isArray(children)) {
children.forEach(function(e) {
element.appendChild(parseDOM(e))
})
}
}
return element;
}
var one = document.getElementById('one');
one.appendChild(parseDOM(jsonTree));
http://js.jirengu.com/juyeki/edit?html,js,output
8. ['1', '2', '3'].map(parseInt)结果是啥?为什么?
此面试题考察的内容:1、对于数组map函数的理解以及使用。2、对于parseInt的转换规则是否理解。
数组的map
方法接受一个函数作为参数。该函数调用时,map
方法向它传入三个参数:当前成员、当前位置和数组本身。
parseInt(string, radix) :函数将其第一个参数转换为一个字符串,对该字符串进行解析,然后返回指定基数的十进制整数或 NaN
。 radix
是2-36之间的整数,表示被解析字符串的基数。第二个参数可不传,默认值为10,即默认是十进制转十进制。第二个参数若传递的是0、NaN、undefined,则分两种情况: 1、若string以“0x”或“0X”开头,则默认为16进制。2、若不是第一种情况则会10进制
[“1”,“2”,“3”].map(parseInt)解析:
parseInt("1",0),
parseInt("2",1),
parseInt("3",2),
1、parseInt(“1”,0)由于传入的radix是0,而string非“0x”或“0X”则进制为十进制,parseInt(“1”,0)则为parseInt(“1”,10),输出1
2、parseInt(“2”,1)由于radix是1,不在2-36区间,输出NaN。
3、parseInt(“3”,2)的radix为2,在2-36区间,又因为在二进制中“3”是不存在的,为无效数字,输出NaN
所以最终结果:
[1,NaN,NaN]