面试题总结
1、var、let、const区别?
答:
- 1、var声明的变量为方法作用域,let、const是块级作用域即 {} 之内可见;
- 2、在变量声明之前就访问的话,let、const会报错(ReferenceError: xxx is not defined),而var使用默认值undefined;
- 3、let、const作用域相同,但是const定义的是常量,一旦被赋值不可更改,let值和类型都可以改变,没有限制。
- 4、const一旦声明变量,就必须立即初始化,不能留到以后赋值。
2、vue生命周期都有什么?vue-router路由守卫都有什么?路由跳转方式?传参方式都有什么以及区别是什么?
答:
- vue生命周期:beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeDestroy、destroyed;
- vue-router路由守卫:
- 导航守卫按照维度分三个:(参数都是to, from, next)
- 全局的:进入任何一个路由都会执行
beforeEach:进入路由前执行
beforeResolve:在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用
afterEach:导航确认执行时执行,可理解为导航完成时执行 - 路由的:进入某个路由才会执行
beforeEnter: 进入该路由前 - 组件的:进入某个组件才会执行组件复用时
beforeRouteEnter: 进入组件时
beforeRouteUpdate: 组件被复用时调用
beforeRouteLeave: 离开组件前
- 路由跳转:
- 一、this.$router.push({path:url}),或者replace(用法同push,但是这个方法不会向history里面添加新的记录)
- 二、<router-link to="/xxx/xx">点这里跳转</router-link>
- 传参方式:
- 一、<router-link :to="{name:'home', params: {id:1}}"> ,<router-link :to="{name:'home', query: {id:1}}">
- 二、
this.$router.push({name:'home',query: {id:'1'}})
this.$router.push({name:'home',params: {id:'1'}}) // 只能用 name
- query和params区别:
query类似 get, 跳转之后页面 url后面会拼接参数,类似?id=1, 非重要性的可以这样传, 密码之类还是用params,刷新页面id还在
params类似 post, 跳转之后页面 url后面不会拼接参数 , 但是刷新页面id 会消失
3、vuex属性都有什么?用法?
答:Vuex有五个核心属性: state, getters, mutations, actions, modules。
- state:vuex的基本数据,用来存储变量
- geeter:从基本数据(state)派生的数据,相当于state的计算属性
- mutation:提交更新数据的方法,必须是同步的(如果需要异步使用action)。接受 state 作为第一个参数,提交载荷作为第二个参数。
- action:和mutation的功能大致相同,不同之处在于 ==》1. Action 提交的是 mutation,而不是直接变更状态。 2. Action 可以包含任意异步操作。
- modules:模块化vuex,可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理。
使用下面这两种方法存储数据:
- modules:模块化vuex,可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理。
- dispatch:异步操作,写法:
this.$store.dispatch('action方法名',值)
- commit:同步操作,写法:
this.$store.commit('mutations方法名',值)
4、vue中父组件调用子组件中的方法以及子组件调用父组件方法分别怎么做?
答:父调子:父组件通过$ref获取到子组件的实例对象并调用子组件的 sonFun 方法,this.$refs.sonRef.sonFun();
子调父:一、直接在子组件中通过this.$parent.fatherFun()来调用父组件的方法
二、this.$emit('fatherFun');
三、父组件把方法传入子组件中,在子组件里直接调用这个方法;eg: <child :fatherFun="fatherFun"></child>,子组件:this.fatherFun();
5、vue中 provide 和 inject 用法
答:provide 是在父组件中定义,然后所有子组件都是可以通过 inject 注入该变量或方法进行操作。
6、computed和watch区别,以及computed和methods中方法的区别
答:computed:
- 支持缓存,只有依赖数据发生改变,才会重新进行计算
- 不支持异步,当computed内有异步操作时无效,无法监听数据的变化
watch:
- 不支持异步,当computed内有异步操作时无效,无法监听数据的变化
- 不支持缓存,数据变,直接会触发相应的操作;
- watch支持异步;
computed和methods中方法的区别:
- watch支持异步;
- 1、methods使用时,一般情况需要加括号,而computed则不需要。
- 2、methods每次调用时会重新执行函数,而computed在其内部变量不变或其返回值不变的情况下多次调用只会执行一次,后续执行时直接从缓存中获取该computed的结果。
7、VUE中key的意义和作用?
答:key的作用主要是为了高效的更新虚拟DOM,提高Diff算法性能。
- 1、key的作用主要是为了高效的更新虚拟DOM,其原理是vue在patch过程中通过key可以精准判断两个节点是否是同一个,从而避免频繁更新不同元素,使得整个patch过程更加高效,减少DOM操作量,提高性能。
- 2、若不设置key还可能在列表更新时引发一些隐蔽的bug
- 3、vue中在使用相同标签名元素的过渡切换时,也会使用到key属性,其目的也是为了让vue可以区分它们,否则vue只会替换其内部属性而不会触发过渡效果。
8、写一个JS函数,实现JS变量深度克隆。(不准使用JSON.parse、JSON.stringify)
第一种方法:
function extendDeep(parent, child) {
child = child || {};
for (let i in parent) {
if (parent.hasOwnProperty(i)) {
// 检测当前属性是否为对象
if (typeof parent[i] === "object") {
// 如果当前属性为对象,还要检测它是否为数组
// 这是因为数组的字面量表示和对象的字面量表示不同
// 前者时[],后者时{}
child[i] = Object.prototype.toString.call(parent[i] === "[object Array]") ? [] : {};
// 递归调用 extendDeep
extendDeep(parent[i], child[i]);
} else {
child[i] = parent[i];
}
}
}
return child;
}
第二种方法:
用ES6的Array.isArray方法,使用此方法判断变量是否为数组,则非常简单。
Array.isArray([]); // => true
Array.isArray({0: 'a', length: 1}); // => false
// 实际上,通过Object.prototype.toString去判断一个值的类型,也是各大主流库的标准。因此Array.isArray的polyfill通常长这样:
if (!Array.isArray){
Array.isArray = function(arg){
return Object.prototype.toString.call(arg) === '[object Array]';
};
}
function deepCopy(obj) {
if (typeof obj != 'object' || obj === null) {
return obj;
}
var newObj = {};
if (Array.isArray(obj)) {
newObj = [];
}
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = deepCopy(obj[key]);
}
}
return newObj;
}
9、解释下margin塌陷以 及 margin合并
margin塌陷,也叫 margin重叠
- 标准文档流中,两个盒子,分别有上下外边距,竖直方向的margin不叠加,只取较大的值作为margin(水平方向的margin是可以叠加的)。
<style>
body{
background-color:#000;
}
.wrapper{
width:200px;
height:200px;
background-color:red;
margin-top:100px;
}
.box{
width:50px;
height:50px;
background-color:#eee;
opacity:0.8;
}
</style>
</head>
<body >
<div class="wrapper">
<div class="box"></div>
</div>
</body>
A和B都是距离上边100px;
现在给B设置margin-top:100px;发现两个方块位置没动;
而当给B设置margin-top:150px;小方块B带着大方块A往下移动了50px;
原理:父子嵌套元素在垂直方向的margin,父子元素是结合在一起的,他们两个的margin会取其中最大的值.
正常情况下,父级元素应该相对浏览器进行定位,子级相对父级定位.
但由于margin的塌陷,父级相对浏览器定位.而子级没有相对父级定位,子级相对父级,就像坍塌了一样.
margin塌陷解决方法
1.给父级设置边框或内边距(不建议使用)
.wrapper{
width:200px;
height:200px;
background-color:red;
margin-top:100px;
border-top:1px solid black;
}
2.触发bfc(块级格式上下文),改变父级的渲染规则
方法:
改变父级的渲染规则有以下四种方法,给父级盒子添加
(1)position:absolute/fixed
(2)display:inline-block;
(3)float:left/right
(4)overflow:hidden
这四种方法都能触发bfc,但是使用的时候都会带来不同的麻烦,具体使用中还需根据具体情况选择没有影响的来解决margin塌陷
margin合并现象:
原理:两个兄弟结构的元素在垂直方向上的margin是合并的
原理图:
margin合并问题也可以用bfc解决,
1.给box2加上一层父级元素并加上overflow:hidden;
<style>
.wrapper{
overflow:hidden;
}
</style>
<div class="box1"></div>
<div class="wrapper">
<div class="box2"></div>
</div>
2.给两个都加一层父级再加bfc
<div class="wrapper">
<div class="box1"></div>
</div>
<div class="wrapper">
<div class="box2"></div>
</div>
但是这两种方法都改变了HTML结构,在开发中一般不采用的;
所以在实际应用时,在margin合并这个问题上,我们一般不用bfc,而是通过只设置上面的元素的margin-bottom来解决距离的问题。
10、解释下CSS-BFC
一、何为BFC
BFC(Block Formatting Context)格式化上下文,是Web页面中盒模型布局的CSS渲染模式,指一个独立的渲染区域或者说是一个隔离的独立容器。
二、形成BFC的条件
1、浮动元素,float 除 none 以外的值;
2、定位元素,position(absolute,fixed);
3、display 为以下其中之一的值 inline-block,table-cell,table-caption;
4、overflow 除了 visible 以外的值(hidden,auto,scroll);
参考:
https://www.cnblogs.com/chen-cong/p/7862832.html
https://www.jianshu.com/p/828023418450
11、看下这行数字的规律,写个方法返回第30位是什么?1,1,2,3,5,8...
function fn(len) {
if (len < 3) {
return 1;
}
var first = 1, sec = 1, third = 0;
for (let i = 2; i < len; i++) {
third = first + sec;
first = sec; sec = third;
}
return third;
}
fn(1); // 1
fn(2); // 1
fn(5); // 5
fn(30); // 832040
这个叫斐波那契数列,另一种实现方式
function fibonacci(n) {
// 一、递归解法
return n <= 2 ? 1 : fibonacci(n - 1) + fibonacci(n - 2);
// 二、循环解法
// var num1 = 1;
// var num2 = 1;
// for (var i = 2; i < n; i++) {
// num2 += num1;
// num1 = num2 - num1;
// }
// return num2;
// 三、循环解法另一种方式
// let obj = {
// a0: 0,
// a1: 1,
// a2: 1,
// a3: 2,
// }, i = 0;
// while (i < n) {
// i++;
// obj['a' + (i + 2)] = obj['a' + i] + obj['a' + (i + 1)];
// }
// return obj['a' + i];
}
12、 javascript的typeof返回哪些数据类型.
答案:string,boolean,number,undefined,function,object
13、例举3种强制类型转换和2种隐式类型转换?
答案:强制(parseInt,parseFloat,number)
隐式(== ===)
14、split()、join() 的区别
答案:前者是将字符串切割成数组的形式,后者是将数组转换成字符串
15、数组方法pop()、push() 、unshift()、 shift()
答案:push()尾部添加 pop()尾部删除
unshift()头部添加 shift()头部删除
16、IE和标准下有哪些兼容性的写法
答案:
var ev = ev || window.event;
document.documentElement.clientWidth || document.body.clientWidth;
Var target = ev.srcElement || ev.target;
17、innerHTML和outerHTML的区别
答案:
innerHTML(元素内包含的内容)
outerHTML(自己以及元素内的内容)
18、offsetWidth offsetHeight和clientWidth clientHeight的区别
答案:
(1)offsetWidth (content宽度+padding宽度+border宽度)
(2)offsetHeight(content高度+padding高度+border高度)
(3)clientWidth(content宽度+padding宽度)
(4)clientHeight(content高度+padding高度)
19、闭包的好处
答案:
(1)希望一个变量长期驻扎在内存当中(不被垃圾回收机制回收)
(2)避免全局变量的污染
(3)私有成员的存在
(4)安全性提高
20、冒泡排序算法
var array = [5, 4, 3, 2, 1];
var temp = 0;
for (var i = 0; i <array.length; i++){
for (var j = 0; j <array.length - i; j++){
if (array[j] > array[j + 1]){
temp = array[j + 1];
array[j + 1] = array[j];
array[j] = temp;
}
}
21、请写出一个打乱JS数组【1,2,3,4,5,6】内元素排列顺序的方法。
function randomsort(a, b) {
return Math.random() > .5 ? -1 : 1;
}
var arr = [1, 2, 3, 4, 5, 6];
arr.sort(randomsort);
22、不用循环(包括ES5的forEach、map等方法),创建一个长度位100的数组,并且每个元素值等于的下标索引【0,1,2...99】。
// 方法一
Object.keys(Array.from({length:100}));
// 方法二
Object.keys(Array.apply(null,{length:100}));
// 方法三
Object.keys([...Array(100)]);
// 方法四
Object.keys(Array(100).keys());
// 方法五
[...Array(100).keys()];
23、Object.assign用法、特点、模拟实现
答:浅拷贝Object.assign,将所有可枚举属性的值从一个或多个源对象复制到目标对象,同时返回目标对象。
Object.assign(target, ...sources)
其中 target 是目标对象,sources 是源对象,可以有多个,返回修改后的目标对象 target。
如果目标对象中的属性具有相同的键,则属性将被源对象中的属性覆盖。后来的源对象的属性将类似地覆盖早先的属性。
Object.assign 模拟实现
实现一个 Object.assign 大致思路如下:
1、判断原生 Object 是否支持该函数,如果不存在的话创建一个函数 assign,并使用 Object.defineProperty 将该函数绑定到 Object 上。
2、判断参数是否正确(目标对象不能为空,我们可以直接设置{}传递进去,但必须设置值)
3、使用 Object() 转成对象,并保存为 to,最后返回这个对象 to
4、使用 for..in 循环遍历出所有可枚举的自有属性。并复制给新的目标对象(hasOwnProperty返回非原型链上的属性)
实现代码如下,这里为了验证方便,使用 assign2 代替 assign。注意此模拟实现不支持 symbol 属性,因为ES5 中根本没有 symbol 。
if (typeof Object.assign2 != 'function') {
Object.defineProperty(Object, "assign2", {
value: function (target) {
'use strict';
if (target == null) {
throw new TypeError('Cannot convert undefined or null to object');
}
var to = Object(target);
for (var index = 1; index < arguments.length; index++) {
var nextSource = arguments[index];
if (nextSource != null) {
for (var nextKey in nextSource) {
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
to[nextKey] = nextSource[nextKey];
}
}
}
}
return to;
},
writable: true,
configurable: true
});
}
24、封装函数 f,使 f 的 this 指向指定的对象
答:
function bindThis(f, oTarget) {
return f.bind(oTarget);
}
25、获取 url 中的参数
- 指定参数名称,返回该参数的值 或者 空字符串
- 不指定参数名称,返回全部的参数对象 或者 {}
- 如果存在多个同名参数,则返回数组
答:
function getUrlParam(sUrl, sKey) {
let obj = {}, sArr = sUrl.split("?")[1].split("#")[0].split("&");
sArr.forEach(item => {
let [key, value] = item.split("=");
obj[key] = obj.hasOwnProperty(key) ? [].concat(obj[key], value) : value;
});
return sKey === undefined ? obj : obj[sKey] || "";
}
26、查找两个节点的最近的一个共同父节点,可以包括节点自身(oNode1 和 oNode2 在同一文档中,且不会为相同的节点)
答:
function commonParentNode(oNode1, oNode2) {
while (!oNode1.contains(oNode2)) {
oNode1 = oNode1.parentNode;
}
return oNode1;
}
27、为 Array 对象添加一个去除重复项的方法
示例1:
输入描述:
[false, true, undefined, null, NaN, 0, 1, {}, {}, 'a', 'a', NaN]
输出描述:
[false, true, undefined, null, NaN, 0, 1, {}, {}, 'a']
答:
Array.prototype.uniq = function () {
return [...new Set(this)];
}
28、根据包名,在指定空间中创建对象
示例1:
输入描述:
namespace({a: {test: 1, b: 2}}, 'a.b.c.d')
输出描述:
{a: {test: 1, b: {c: {d: {}}}}}
答:
function namespace(oNamespace, sPackage) {
if (!sPackage || Object.prototype.toString.call(oNamespace) !== "[object Object]") {
return oNamespace || {};
}
let arr = sPackage.split('.');
const key = arr.shift();
if (arr.join(".") && Object.prototype.toString.call(oNamespace[key]) !== "[object Object]") {
oNamespace[key] = {};
}
oNamespace[key] = namespace(oNamespace[key], arr.join("."));
return oNamespace || {};
}
29、时间格式化输出,按所给的时间格式输出指定的时间
格式说明
对于 2014.09.05 13:14:20
yyyy: 年份,2014
yy: 年份,14
MM: 月份,补满两位,09
M: 月份, 9
dd: 日期,补满两位,05
d: 日期, 5
HH: 24制小时,补满两位,13
H: 24制小时,13
hh: 12制小时,补满两位,01
h: 12制小时,1
mm: 分钟,补满两位,14
m: 分钟,14
ss: 秒,补满两位,20
s: 秒,20
w: 星期,为 ['日', '一', '二', '三', '四', '五', '六'] 中的某一个,本 demo 结果为 五
示例1:
输入描述:
formatDate(new Date(1409894060000), 'yyyy-MM-dd HH:mm:ss 星期w')
输出描述:
2014-09-05 13:14:20 星期五
答:正则的就不写了,网上一堆,写个不用正则的,
function formatDate(date, format) {
// 写个不用正则的方法
// obj的 key 顺序必须是 yyyy 在 yy 前面,MM 在 M 前......,为下面replace准备
let obj = {
yyyy: date.getFullYear(), // 2021
yy: (date.getFullYear()).toString().substr(2, 4), // 21
MM: ("0" + (date.getMonth() + 1)).slice(-2), // 01
M: date.getMonth() + 1, // 1
dd: ("0" + date.getDate()).slice(-2),
d: date.getDate(),
HH: ("0" + date.getHours()).slice(-2), // 24制小时
H: date.getHours(),
hh: ("0" + date.getHours() % 12).slice(-2), // 12制小时
h: date.getHours() % 12,
mm: ("0" + date.getMinutes()).slice(-2),
m: date.getMinutes(),
ss: ("0" + date.getSeconds()).slice(-2),
s: date.getSeconds(),
w: "日一二三四五六".charAt(date.getDay()),
};
for (let key in obj) {
format = format.replace(key, obj[key]);
}
return format;
}
30、获取字符串的长度
如果第二个参数 bUnicode255For1 === true,则所有字符长度为 1
否则如果字符 Unicode 编码 > 255 则长度为 2
示例1:
输入描述:
'hello world, 哈哈', false
输出描述:
17
答:
function strLength(s, bUnicode255For1) {
if (!bUnicode255For1) {
let len = s.length;
for (let i = 0; i < len; i++) {
if (s.charCodeAt(i) > 255) {
len++;
}
}
return len;
}
return s.length;
}
31、邮箱字符串判断
判断输入是否是正确的邮箱格式
示例1:
输入描述:
邮箱字符串
输出描述:
true表示格式正确
答:
function isAvailableEmail(sEmail) {
return /^([\w+\.])+@(\w+)([.]\w+)+$/.test(sEmail);
}
32、颜色字符串转换
将 rgb 颜色字符串转换为十六进制的形式,如 rgb(255, 255, 255) 转为 #ffffff
- rgb 中每个 , 后面的空格数量不固定
- 十六进制表达式使用六位小写字母
- 如果输入不符合 rgb 格式,返回原始输入
示例1:
输入描述:
'rgb(255, 255, 255)'
输出描述:
#ffffff
答:可以用正则来实现更简单点,可是我不太熟悉正则,不过JS也可以实现
function rgb2hex(sRGB) {
const sRGBSave = sRGB;
if (sRGBSave.substr(0, 4) !== "rgb(") {
return sRGBSave;
}
sRGB = sRGB.split("(")[1].split(",");
if (sRGB.length !== 3) {
return sRGBSave;
}
let res = "#";
for (let i = 0; i < 3; i++) {
const item = parseFloat(sRGB[i]);
if (isNaN(item) || item < 0 || item > 255) {
return sRGBSave;
}
res += ("0" + (item).toString(16)).slice(-2);
}
return res;
}
33、将字符串转换为驼峰格式
css 中经常有类似 background-image 这种通过 - 连接的字符,通过 javascript 设置样式的时候需要将这种样式转换成 backgroundImage 驼峰格式,请完成此转换功能
- 以 - 为分隔符,将第二个起的非空单词首字母转为大写
- -webkit-border-image 转换后的结果为 webkitBorderImage
示例1:
输入描述:
'font-size'
输出描述:
fontSize
答:
function cssStyle2DomStyle(sName) {
sName = sName.split("-");
let str = "";
for (let key in sName) {
if (sName[key]) {
let first = sName[key].slice(0, 1);
if (str) {
first = first.toUpperCase();
}
let tail = sName[key].slice(1 - sName[key].length);
str += first + tail;
}
}
return str;
}
34、字符串字符统计
统计字符串中每个字符的出现频率,返回一个 Object,key 为统计字符,value 为出现频率
- 不限制 key 的顺序
- 输入的字符串参数不会为空
- 忽略空白字符
示例1:
输入描述:
'hello world'
输出描述:
{h: 1, e: 1, l: 3, o: 2, w: 1, r: 1, d: 1}
答:
第一种
function count(str) {
var obj = {};
// \S,匹配非空字符串,function(s)里的s代表匹配到的每一项
str.replace(/\S/g,function(s){
!obj[s] ? obj[s]=1 : obj[s]++;
})
return obj;
}
第二种
function count(str) {
let obj = {};
for (let i = 0; i < str.length; i++) {
if (str.charAt(i) !== " ") {
obj[str.charAt(i)] = obj.hasOwnProperty(str.charAt(i)) ? ++obj[str.charAt(i)] : 1;
}
}
return obj;
}
35、算法题:跳台阶
题目描述
一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。
示例1:
输入描述1:
1
输出描述1:
1
输入描述2:
4
输出描述2:
5
答:
function jumpFloor(number) {
let obj = { 1: 1, 2: 2 };
for (let i = 3; i <= number; i++) {
obj[i] = obj[i - 1] + obj[i - 2];
}
return obj[number];
}
36、JavaScript数组扁平化
答:reduce遍历数组每一项,若值为数组就递归遍历,否则cancat。(也可以用map遍历方法或者,toString&split (join&split)调用数组的toString方法,将数组变为字符串后用split分割为数组。)
function flatten(arr) {
return arr.reduce((result, item)=> {
return result.concat(Array.isArray(item) ? flatten(item) : item);
}, []);
}
作者:_皓月__
原文链接:https://www.jianshu.com/p/3549b7f0d4cf