前端面试题精选
1.memorize函数实现应用
首先什么是memorize函数,memorize直译:记忆,缓存等意思,到了计算机层面就翻译为缓存函数,缓存函数就是把计算的结果,存在函数中,当再次调用的时候就可以直接调用。这种方法就是用空间来换取时间
const memorize = function(fn) {
const cache = {}
return function(...args) { // 参数值,是一个数组,数组的值是需要计算的值
const _args = JSON.stringify(args)
return cache[_args] || (cache[_args] = fn.apply(fn, args))
// 缓存函数的核心判断依据,传入的键名对应的键值为null 则调用add方法 反之从cache中拿取
}
}
const add = function(a) {
return a + 1
}
const adder = memorize(add) // memorize只调用一次 后面adder调用的都是return的函数
adder(1) // 2 cache: { '[1]': 2 }
adder(1) // 2 cache: { '[1]': 2 }
adder(2) // 3 cache: { '[1]': 2, '[2]': 3 }
2. bind,apply,call的区别和实现
javascript 权威指南上的解释是: call()、apply()可以看做是某个对象的方法,通过调用方法的形式来间接调用函数。bind()就是将某个函数绑定到某个对象上。
var obj = {
x:1
}
function foo() {
console.log(this.x)
}
foo.call(obj) //1
call()和apply()的第一个参数相同,就是指定对象,他们的根本区别就在于传入的参数。
call传入参数形式为call(obj, 1, 2, 3)
apply传入参数形式为apply(obj, [1,2,3])
bind()方法和前两者不同在于:bind()方法会返回执行上下文被改变的函数,而不会立即执行,而前两者是直接执行该函数。他的参数和call()相同.
3.数组去重
去重方法有很多种,常用必需会写的几种去重方法有
1. 遍历去重
2. json键名不唯一去重
3. new Set()去重
3.1 遍历去重
let arr = [1, 2, 3, 4, 5, 6, 7, 7, 7, 3, 111, 1, 3]
for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
if (arr[i] == arr[j]) {
arr.splice(j, 1)
i = i - 1
}
}
}
3.2 json键名去重
let arr = [1, 2, 3, 4, 5, 6, 7, 7, 7, 3, 111, 1, 3]
let obj = {}
let result = []
for(let i = 0 ; i < arr.length; i++) {
obj[arr[i]] = null
}
for(let key in obj) {
result.push(key)
}
3.3 new Set()去重
let arr = [1, 2, 3, 4, 5, 6, 7, 7, 7, 3, 111, 1, 3]
let newArr = new Set(arr) // 返回一个set对象
let result = []
newArr.forEach(val => {
result.push(val)
})
4.数组扁平化
数组扁平化简单来说就是把一个多维数组变为一维数组,核心思想:
1.递归
2.数值toString() + split()
3.Array.prototype.flat(depth) // Infinity代表无限扁平
递归
function flatten(arr) {
var res = [];
arr.map(item => {
if(Array.isArray(item)) {
res = res.concat(flatten(item));
} else {
res.push(item);
}
});
return res;
}
数值的扁平化toString() + split()
let arr = [1,2,3,[2,3,4],45,7,[24,[2,3]]]
function flatten(arr) {
return arr.toString().split(',').map(val => {
return Number(val)
})
}
flat()
let arr = [1,2,3,[2,3,4],45,7,[24,[2,3]]]
arr.flat(infinity)
5.debounce和throttle的实现和使用场景
去抖动和节流,二者都是随着时间推移控制执行函数的次数来达到较少资源消耗,特别在事件触发上,尤为重要。
debounce的作用是,当调用动作触发一段时间后,才会执行该动作,若在这段时间间隔内又调用此动作则将重新计算时间间隔。
简单来说就是:调用会一直刷新动作,动作是被延迟执行,直到停止调用等待了一段时间后再执行动作。看下面简单debounce实现节流例子:
function easyDebounce(method, scope) {
clearTimeout(method.tId); // onresize会一直被触发,只要在100毫秒内被连续触发计时器都会刷新直到时间大于100毫秒
method.tId= setTimeout(function(){
method.call(scope);
}, 100);
}
function resizeDiv(){
var div = document.getElementById("myDiv");
div.style.height = div.offsetWidth + "px";
}
// 节流在resize事件中最常用
window.onresize = function(){
easyDebounce(resizeDiv);
};
throttle预先设定一个执行周期,当调用动作的时刻大于等于执行周期则执行该动作,然后进入下一个新的时间周期。throttle可以基于debounce实现,只需要加上一个阀值(最小值)如果时间小于定义的阀值 则不触发动作,大于则触发并刷新周期,而且第一次必定触发。节流不实用定时器,debounce使用定时器
var throttleV2 = function(action, delay){
var statTime = 0;
return function() { // 每次resize执行的函数 闭包保证statTime可以一直使用
var currTime = +new Date();
if (currTime - statTime > delay) {
action.apply(this, arguments);
statTime = currTime ; // 每次调用完之后刷新时间
}
}
}
// example
function resizeHandler() {
console.log("resize");
}
window.onresize = throttleV2(resizeHandler, 300);
6.vue数据双向绑定
vue实现数据的双向绑定根本API是Object.defineProperty(),通过这个方法重写get,和set属性来达到数据双向绑定
详细解读一下Object.defineProperty()这个东西到底干了啥
var Book = {}
var name = '';
Object.defineProperty(Book, 'name', {
set: function (value) {
name = value;
console.log('书名:' + value);
},
get: function () {
return '《' + name + '》'
}
})
Book.name = '前端学习';
console.log(Book.name);
双向绑定是用发布订阅模式实现,所以涉及到监听器Observer、订阅者Watcher、解析器Compile,以及消息订阅器dep收集watcher。
图片来自互联网:
7.vue scoped实现原理
vue scoped的实现是通过在对应的dom节点添加data-v-哈希值,并在style每个属性后面加上这串data-v-hash来实现样式的唯一性,起到一个样式作用域的效果。
但是实际应用中添加scoped也会带来一些麻烦,比如设置完scoped属性之后无法修改子组件的样式。再次有两点供大家参考:
- 不用scoped属性,每个组件使用良好的命名规范,每个组件的根节点取唯一的类名。
- 使用scoped属性,在需要操作子组件样式的时候使用‘>>>’来修改子组件的节点样式。
持续更新中~~~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。