前端面试题总结:
1、实现对数组进行乱序
var a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
a.sort(function(a, b) {
return Math.random()>0.5?1:-1;
});
如果a.sort中的函数返回的为1则表示按照原来的顺序不变,如果返回的为-1表示倒叙,其他的就是随机的了
2、变量提升解决方法
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(new Date, i);
}, 1000);
}
i的输出结果:5,5,5,5,5
想要输出0,1,2,3,4 的解决方法
方案一:闭包法
for (var i = 0; i < 5; i++) {
(function(j) { // j = i
setTimeout(function() {
console.log(new Date, j);
}, 1000);
})(i);
}
console.log(new Date, i);
方案二: 定义函数法
var output = function (i) {
setTimeout(function() {
console.log(new Date, i);
}, 1000);
};
for (var i = 0; i < 5; i++) {
output(i); // 这里传过去的 i 值被复制了
}
console.log(new Date, i);
方案三:使用es6中的let
for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(new Date, i);
}, 1000);
}
3、生成一个包含临界值的随机整数
function random(min,max) {
return Math.floor(Math.random() * (max - min+1)) + min;
}
random1(1,10) 包含1 和 10
4、什么是闭包,闭包得作用
闭包:当一个函数返回它内部定义的一个函数时,就产生了一个闭包。闭包不但包括被返回的函数,还包括这个函数的定义环境。
闭包得作用:一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。
5、js数组主要有哪些方法?主要参数你了解吗?
假如有一个数组:[0,1,2]
1 、shift():删除数组的第一个元素,返回删除的值。这里是0
2 、unshift(3,4):把参数加载数组的前面,返回数组的长度(会改变原数组)。现在list:中是3,4,0,1,2
3、pop():删除数组的最后一个元素,返回删除的值。这里是2.
4、push(3):将参数加载到数组的最后,返回数组的长度,现在List中时:0,1,2,3
5、concat(3,4):把两个数组拼接起来(concat() 方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组)。0,1,2,3,4
6、splice(start,deleteCount,val1,val2,...):从start位置开始删除deleteCount项,并从该位置起插入val1,val2,...
要注意了 deleteCount==0是添加
只写一个参数表示从第一个参数开始的位置全部删除
7、reverse:将数组反序
var a = [1,2,3,4,5];
var b = a.reverse(); //a:[5,4,3,2,1] b:[5,4,3,2,1]
8、sort(orderfunction):按指定的参数对数组进行排序 var a = [1,2,3,4,5]; var b = a.sort(); //a:[1,2,3,4,5] b:[1,2,3,4,5]
9、slice(start,end):返回从原数组中指定开始下标到结束下标之间的项组成的新数组
6、写一个正则表达式,验证正整数。
7、三种减低页面加载时间的方法
1、压缩静态(css/js)文件
2、合并js、css文件,减少http请求
3、外部js、css文件放在最底下
4、减少dom操作,尽可能用变量替代不必要的dom操作
8、下面输出什么?
["1", "2", "3"].map(parseInt)
9、此函数返回的结果是啥?
var test = (function(a) {
this.a = a;
return function(b) {
return this.a + b;
}
} (function(a, b) {
return a;
}(1, 2)));
console.log(test(4)); //输出什么???? 5
解释:先执行 test(4)把a赋值给this.a 然后返回一个函数这个函数需要一个参数,因为function(a,b)是个自执行函数所以会将1返回所以最终结果是5,
10、简化以下代码:触发submit时,根据不同的状态进行不同的操作
submit(status){
if(status == 1){
send('processing');
jumpTo('IndexPage');
}else if(status == 2){
send('fail');
jumpTo('FailPage');
}else if(status == 3){
send('fail');
jumpTo('FailPage');
}else if(status == 4){
send('success');
jumpTo('SuccessPage');
}else if(status == 5){
send('cancel');
jumpTo('CancelPage');
}else {
send('other');
jumpTo('Index');
}
}
简化结果:
const actions = {
'1': ['processing','IndexPage'],
'2': ['fail','FailPage'],
'3': ['fail','FailPage'],
'4': ['success','SuccessPage'],
'5': ['cancel','CancelPage'],
'default': ['other','Index'],
};
submit(status){
let action = actions[status] || actions['default'],
logName = action[0],
pageName = action[1];
send(logName);
jumpTo(pageName);
}
11、限制输入框只能输入长度为9的数字或字母组合
<input type="text" id='code' maxlength="9" v-model="code" @input="code=code.replace(/[^\w]/ig,'')" />
12、undefined和xxx is not defined区别
undefined是javascript的一种基本数据类型,变量未赋值或者函数没有返回值时返回。
xxx is not defined是一种错误类型,其完整形式是:Uncaught ReferenceError: xxx is not defined(未捕获的引用错误),对象表明一个不存在的变量被引用,即:当你尝试引用一个未被定义的变量时,将会抛出一个 ReferenceError 。
13、给定一个数组,实现去重和统计出现次数
const arr = [1,3,8,3,7,9,8,7,7,1,4];
let count = arr.reduce((prev,now)=>{
now in prev ? prev[now]++ : prev[now] = 1;
console.log(prev,now)
return prev
},{});
console.log(count)
//逗号运算符了解一下
let a = arr.reduce((prev,now)=>(prev[now]++ || (prev[now] = 1),prev),{})
console.log(a)
解析,重点是这个{}也可以用
14、Chrome浏览器下小于12号字体的解决方案
1.可以使用transform: scale来解决
想要支持IE8及以下,再给其添加一个真实的字体大小
2.也可以考虑UI切图
15.下面的代码输出什么?
class Example extends React.Component {
constructor() {
super();
this.state = {
val: 0
};
}
componentDidMount() {
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 1 次 log
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 2 次 log
setTimeout(() => {
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 3 次 log
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 4 次 log
}, 0);
}
render() {
return null;
}
};
解析:
1、第一次和第二次都是在 react 自身生命周期内,触发时 isBatchingUpdates 为 true,所以并不会直接执行更新 state,而是加入了 dirtyComponents,所以打印时获取的都是更新前的状态 0。
2、两次 setState 时,获取到 this.state.val 都是 0,所以执行时都是将 0 设置成 1,在 react 内部会被合并掉,只执行一次。设置完成后 state.val 值为 1。
3、setTimeout 中的代码,触发时 isBatchingUpdates 为 false,所以能够直接进行更新,所以连着输出 2,3。
输出: 0 0 2 3
在React中,如果是由React引发的事件处理(比如通过onClick引发的事件处理),调用setState不会同步更新this.state,除此之外的setState调用会同步执行this.state。所谓“除此之外”,指的是绕过React通过addEventListener直接添加的事件处理函数,还有通过setTimeout/setInterval产生的异步调用。
**原因:**在React的setState函数实现中,会根据一个变量isBatchingUpdates判断是直接更新this.state还是放到队列中回头再说,而isBatchingUpdates默认是false,也就表示setState会同步更新this.state,但是,有一个函数batchedUpdates,这个函数会把isBatchingUpdates修改为true,而当React在调用事件处理函数之前就会调用这个batchedUpdates,造成的后果,就是由React控制的事件处理过程setState不会同步更新this.state。
16、如何优雅的生成一个长度为n,值从0递增的数组
1. Array.apply(null, {length: 10}).map(Number.call, Number)
2. Object.keys(Array.from({length:5}))
17、多维数组降维
1. arr作为apply方法的第二个参数,本身是一个数组,数组中的每一个元素(还是数组,即二维数组的第二维)会被作为参数依次传入到concat中,效果等同于[].concat([1,2], [3,4], [5,6])。
function reduceDimension(arr) {
return Array.prototype.concat.apply([], arr);
}
2. 递归展开 可解决多维数组
function reduceDimension(arr){
let ret = [];
let toArr = function(arr){
arr.forEach(function(item){
item instanceof Array ? toArr(item) : ret.push(item);
});
}
toArr(arr);
return ret;
}
18、如何利用数据驱动的原理实现一个简单的 v-show 指令
1. 获取 div 上的指令(属性)以及指令的初始值;2. 定义能切换显示隐藏 div 的 dom 操作(视图刷新)方法;3. 劫持指令对应数据的 setter,setter 触发时调用 2 中的视图刷新。
<button onClick="model.isShow = true">显示</button>
<button onClick="model.isShow = false">隐藏</button>
<div v-show="isShow">Hello World!</div>
<script>
// 第 1 步: 定义数据和视图
var model = {
isShow: false
}
var view = document.querySelector('div')
// 第 2 步: 定义视图刷新方法
var updateView = function(value) {
view.style.display = value ? '' : 'none'
}
// 第 3 步: 设置初始视图表现
var directiveKey = view.getAttribute('v-show')
updateView(model[directiveKey])
// 第 4 步: 监听数据变化,然后刷新视图,达到数据驱动的目的
Object.defineProperty(model, 'isShow', {
set: function(val) {
updateView(val)
}
})
</script>
19、下面输出是什么?
var a = {},
b = {key: 'b'},
c = {key: 'c'},
d = {key: 'd'}
a[b] = 123;
a[c] = 456;
a[d] = 678;
console.log(a[c])
解析
b,c,d均为引用对象,因此,在使用b,c,d作为键名时,会调用对象的Object.prototype.toString方法,在进行赋值的时候,会不断覆盖a的"[object Object]"属性,直到最后a["[object Object]"] = 678。
20、请使用尾递归优化以下函数:
function f(n) {
if ( n <= 1 ) {return 1};
return f(n - 1) + f(n - 2);
}
说明尾调优化有哪些好处,适用的场景
解析:
function f(n , ac1 = 1 , ac2 = 1) {
if( n <= 1 ) {return ac2};
return f(n - 1, ac2, ac1 + ac2);
}
尾调优化的好处:
尾调优化用来解决函数调用的时候,在内存中保存调用栈消耗内存的问题。特别是递归函数。f事实上是一个斐波那契函数。
没有进行尾调优化的时候需要同时保存成千上百个调用帧,很容易发生“栈溢出”错误。但对于尾递归来说,由于只存在一个调用帧,所以永远不会发生“栈溢出”错误。
21、js如何实现完美的继承。
1、借用构造函数。核心:使用call,将要继承的属性构造函数绑定到当前的实例上,借用构造函数给当前实例写入属性。这种方法的属性是直接写入到当前实例上的,而非使用原型链查找得来。
2、原型链继承核心是:将构造函数的原型指向另一个构造函数的实例
这样会造成一个现象就是 obj.constructor 会是上层的构造函数,而非当前构造函数,因为当前对象的原型和构造函数式解耦状态,通过constructor访问的时候,由于当前对象原型没有改属性,通过原型链查找,到上层的构造函数。因此,需要将当前实例的原型的构造函数指向当前构造函数才行。
3、组合继承:最大的问题就是无论什么情况下, 都会调用两次父类构造函数: 一次是在创建子类型原型的时候, 另一次是在子类型构造函数内部。
4、组合寄生式继承:继承了组合继承的优点,通过Object.create创建构造函数原型,不用调用父类的构造函数
Babel转码es6class转换为es5的代码就是,就是使用的组合寄生式继承。
22、vue中使用v-for时为什么不能用index作为key?
Vue 和 React 都实现了一套虚拟DOM,使我们可以不直接操作DOM元素,只操作数据便可以重新渲染页面。而隐藏在背后的原理便是其高效的Diff算法。如果使用唯一index作为key,删除元素的话,index也会重新计算,剩下的元素因为与 key 的关系发生变化,就会重新渲染,从而会影响部分性能。
23、vue watch 和 computed 区别是什么?
当页面中有某些数据依赖其他数据进行变动的时候,可以使用计算属性computed。watch和computed很相似,watch用于观察和监听页面上的vue实例,当然在大部分情况下我们都会使用computed,但如果要在数据变化的同时进行异步操作或者是比较大的开销,那么watch为最佳选择。
24、(a ==1 && a== 2 && a==3) 可能为 true 吗?
解法一:
利用松散相等运算符 == 的工作原理,你可以简单地创建一个带有自定义toString( 或者 valueOf)函数的对象,在每一次使用它时候改变它所的返回值,使其满足所有三个条件。const a = {
i: 1,
toString: function () {
return a.i++;
}
}
if(a == 1 && a == 2 && a == 3) {
console.log('Hello World!');
}
解法二:
定义a的defineProperty
var val = 0;
Object.defineProperty(window, 'a', {
get: function() {
return ++val;
}
});
console.log(a == 1 && a == 2 && a == 3);
25、输出结果:
//函数作用域与this
var name = 'san';
function test() {
var name = 'JoJo';
return function() {
console.log(name);
return this.name;
}
}
console.log(test()());
依次输出的结果
JoJo san
26、如何确定this指向
如果要判断一个运行中函数的 this 绑定,就需要找到这个函数的直接调用位置。找到之后就可以顺序应用下面这四条规则来判断 this 的绑定对象。
1. 由 new 调用?绑定到新创建的对象。
2. 由 call 或者 apply (或者 bind )调用?绑定到指定的对象。
3. 由上下文对象调用?绑定到那个上下文对象。
4. 默认:在严格模式下绑定到 undefined ,否则绑定到全局对象。
一定要注意,有些调用可能在无意中使用默认绑定规则。如果想“更安全”地忽略 this 绑定,你可以使用一个 DMZ 对象,比如 ø = Object.create(null) ,以保护全局对象。
ES6 中的箭头函数并不会使用四条标准的绑定规则,而是根据当前的词法作用域来决定this ,具体来说,箭头函数会继承外层函数调用的 this 绑定(无论 this 绑定到什么)。这其实和 ES6 之前代码中的 self = this 机制一样
27、写出下面代码打印的结果,并解释原因
var num = 25;
function foo(){
console.log(this.num);
}
foo();//25
(function bar(){
this.num = 13;
foo(); // 13
})();
console.log(num);//13
(function bar1(foo){
this.num = 23;
foo();
})(foo)//23
解析:
如果要判断一个运行中函数的 this 绑定,就需要找到这个函数的直接调用位置。在严格模式下绑定到 undefined ,否则绑定到全局对象
28、webSocket如何兼容低浏览器?
Adobe Flash Socket 、 ActiveX HTMLFile (IE) 、 基于 multipart 编码发送 XHR 、 基于长轮询的 XHR
29、WEB应用从服务器主动推送Data到客户端有那些方式?
html5 websoket、WebSocket通过Flash、XHR长时间连接、XHR Multipart Streaming、不可见的Iframe、<script>标签的长时间连接(可跨域)
30、js浮点数运算不精确 如何解决?
产生误差的原因:JavaScript 所有数字都是以IEEE-754标准格式64位浮点数形式储存,1与1.0是相同的。因为有些小数以二进制表示位数是无穷的。JavaScript会把超出53位之后的二进制舍弃,所以涉及小数的比较和运算要特别小心。
解决办法:在知道小数位个数的前提下,可以考虑通过将浮点数放大倍数到整型(最后再除以相应倍数),再进行运算操作,这样就能得到正确的结果了
31、写出打印结果
var a=10;let b=20;c=30;
function d(){
console.log(a,b,c);
var a=b/c|0;
console.log(a,b,c)};
d();
解析:
undefined 20 30 // 变量声明提升
0 20 30 // | 二进制位运算符
二进制或运算符(or):符号为|,表示若两个二进制位都为0,则结果为0,否则为1。
将|两边的数用32位二进制数表示,对比对应位上的数字,如果都为0,则该位上记为0,否则该位上记为1,把得到的新的32位二进制数转为十进制数即为最终结果
32、class中extends的用法,以及你怎么使用继承
关键字在类声明或类表达式中用于创建一个类作为另一个类的一个子类。
class Dog extends Animal
如果子类中存在构造函数,则需要在使用“this”之前首先调用 super()。
自由发挥
33、6*0.7 和5*0.7的区别
4.199999999999999 3.5 可以用toFixed进行四舍五入
34、如何获取javascript三个数中的最大值和最小值?
1.Math.max(a,b,c);//最大值 2.Math.min(a,b,c)//最小值
35、写一个function,清除字符串前后的空格
String.prototype.trim= function(){
return this.replace(/^\s+/,"").replace(/\s+$/,"");
}
36、在vue组件里添加一个倒计时需要注意什么
参考:1、使用setTimeout函数替代setInterval,为了让计时更准确可以添加到new
Date()上
2、在组件卸载时注意一定清除计时器
37、浏览器跨域出现的原因
同源策略相关