12道常见的面试题
1. 防抖和节流
防抖:连续触发事件中只有最后一次是执行成功的。
当连续触发事件时,一定时间段内没有在触发事件,事件处理才会执行一次,如果规定时间内又触发了事件,那么就重新开始计时。例如:用户输入的oninput事件, 用户输入后并不执行 fn,如果用户继续输入触发handleChange事件函数,则清除定时器重新计时。当用户停止输入400毫秒后触发fn。
<input type="text" name="" id="num" value="" oninput="handleChange()"/>
function debounce(fn, wait) {
var timeout = null;
console.log(timeout)
return function() {
if(timeout !== null) clearTimeout(timeout);
timeout = setTimeout(fn, wait);
}
}
var debounceinput = debounce(function () {
console.log(document.getElementById('num').value)
},400)
function handleChange(){
debounceinput()
}
节流:使得一定时间内只触发一次函数。原理是通过判断是否到达一定时间来触发函数
节流通俗解释就是我们要节省流量,减少请求次数。比如:登录功能,我们点击登录后请求一次接口,在一定时间内任何登录操作都是无效的,只有过时后在登录才再次触发登录接口。
var throttle = function(func, delay) {
var timer = null;
return function() {
var context = this;
var args = arguments;
if (!timer) {
timer = setTimeout(function() {
func.apply(context, args);
timer = null;
}, delay);
}
}
}
深考呗
其实深拷贝和浅拷贝都是针对的引用类型,JS中的变量类型分为值类型(基本类型)和引用类型;对值类型进行复制操作会对值进行一份拷贝,而对引用类型赋值,则会进行地址的拷贝,最终两个变量指向同一份数据
基本类型:Boolean、number、null、string、undefined 是存在栈空间的
引用类型:object 包含 Array、function、Date 是存在堆空间的
- 赋值、浅拷贝与深拷贝的区别
项目 | 是否指向同一对象 | 第一次数据的基本数据 | 原数据中包含子对象 |
---|---|---|---|
赋值 | 是 | 改变会使原数据一同改变 | 改变会使原数据一同改变 |
浅拷贝 | 否 | 改变不会使原数据一同改变 | 改变会使原数据一同改变 |
深拷贝 | 否 | 改变不会使原数据一同改变 | 改变不会使原数据一同改变 |
- 赋值
// 基本类型
var a = 1;
var b = a;
a = 2;
console.log(a, b); // 2, 1 ,a b指向不同的数据
// 引用类型指向同一份数据
var a = {c: 1};
var b = a;
a.c = 2;
console.log(a.c, b.c); // 2, 2 全是2,a b指向同一份数据
- 浅拷贝的实现
浅拷贝的实现非常简单,其实就是遍历对象属性的问题
var a1 = {b: {c: {}};
var a2 = shallowClone(a1); // 浅拷贝
a2.b.c === a1.b.c // true
function shallowClone(source) {
var target = {};
for(var i in source) {
if (source.hasOwnProperty(i)) {
target[i] = source[i];
}
}
return target;
}
- 深拷贝的实现
数组扁平化
就是将多维数组装化成一位数组
es6提供的新方法flat(depth) 方法中的参数depth,代表展开嵌套数组的深度,默认是1.
参数depth还可以是 Infinity,无需知道数组的维度,直接将目标数组变成1维数组。
let a = [1,[2,3]];
a.flat(); // [1,2,3]
a.flat(1); //[1,2,3]
let a = [1,[2,3,[4,5]]];
a.flat(); // [1,2,3,[4,5]]
a.flat(2); // [1,2,3,4,5]
let a = [1,[2,3,[4,[5]]]];
a.flat(Infinity); // [1,2,3,4,5] a是4维数组
for循环
var arr1 = [1, 2, 3, [1, 2, 3, 4, [2, 3, 4]]];
function flatten(arr) {
var res = [];
for(let i = 0, length = arr.length; i < length; i++) {
if(Array.isArray(arr[i])) {
//res = res.concat(flatten(arr[i])); //concat 并不会改变原数组
res.push(...flatten(arr[i])); //扩展运算符
} else{
res.push(arr[i]);
}
}
return res;
}
flatten(arr1) // [1, 2, 3, 1, 2, 3, 4, 2, 3, 4]
单列模式
保证一个类仅有一个一个实例,并提供一个访问它的全局访问点。
es5实现方式
var Singleton = function(name) {
this.name = name;
//一个标记,用来判断是否已将创建了该类的实例
this.instance = null;
}
// 提供了一个静态方法,用户可以直接在类上调用
Singleton.getInstance = function(name) {
// 没有实例化的时候创建一个该类的实例
if(!this.instance) {
this.instance = new Singleton(name);
}
// 已经实例化了,返回第一次实例化对象的引用
return this.instance;
}
es6实现方式
class Singleton {
constructor(name) {
this.name = name;
this.instance = null;
}
// 构造一个广为人知的接口,供用户对该类进行实例化
static getInstance(name) {
if(!this.instance) {
this.instance = new Singleton(name);
}
return this.instance;
}
}
数组去重
// set 方法 由于set 的key值不能重复
function unique3 (arr) {
return [...new Set(arr)]
}
/**
* array.filter(function(currentValue,index,arr), thisValue)
* filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。
* @currentValue 必须。当前元素的值
* @index 可选。当前元素的索引值
* @arr 可选。当前元素属于的数组对象
* @thisValue 可选。对象作为该执行回调时使用,传递给函数,用作 "this" 的值。如果省略了 thisValue ,"this" 的值为 "undefined"
* */
function unique2(arr){
let res = arr.filter(function(value, index, array){
return array.indexOf(value) == index
})
return res;
}
// indexOf包含
function unique1(arr){
let res = []
for (var i = 0; i < arr.length; i++) {
if(res.indexOf(arr[i]) == -1){
res.push(arr[i])
}
}
return res
}
// 循环法
function unique(arr){
for (var i = 0; i < arr.length - 1; i++) {
for(let j = i+1; j < arr.length; j++){
if(arr[j] == arr[i]){
arr.splice(j,1)
}
}
}
return arr
}
手写promise.all和promise.race
Promise.all
Promise.myAll()返回的肯定是一个promise对象,所以可以直接写一个return new Promise((resolve, reject) => {})(这应该是一个惯性思维)
遍历传入的参数,用Promise.resolve()将参数"包一层",使其变成一个promise对象
关键点是何时"决议",也就是何时resolve出来,在这里做了计数器(count),每个内部promise对象决议后就将计数器加一,并判断加一后的大小是否与传入对象的数量相等,如果相等则调用resolve(),如果任何一个promise对象失败,则调用reject()方法。
一些细节:
官方规定Promise.all()接受的参数是一个可遍历的参数,所以未必一定是一个数组,所以用Array.from()转化一下
使用for…of进行遍历,因为凡是可遍历的变量应该都是部署了iterator方法,所以用for…of遍历最安全
Promise.all = function (iterator) {
let count = 0//用于计数,当等于len时就resolve
let len = iterator.length
let res = []//用于存放结果
return new Promise((resolve,reject) => {
for(var item of iterator){
Promise.resolve(item)//先转化为Promise对象
.then((data) => {
res[count++] = data
if(count === len){
resolve(res)
}
})
.catch(e => {
reject(e)
})
}
})
}
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
},5000)
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('failed')
},1000)
})
let p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('then')
},1000)
})
Promise.all([p1, p3, p2]).then((res)=>{
console.log(res)
})
promise.race
谁先决议那么就返回谁,所以将all的计数器和逻辑判断全部去除掉就可以了。
Promise.all = function (iterator) {
let count = 0//用于计数,当等于len时就resolve
let len = iterator.length
let res = []//用于存放结果
return new Promise((resolve,reject) => {
for(var item of iterator){
Promise.resolve(item)//先转化为Promise对象
.then((data) => {
res[count++] = data
if(count === len){
resolve(res)
}
})
.catch(e => {
reject(e)
})
}
})
}
模拟实现new
实现call/apply/bind
模拟Object.creact()的实现
千分位分隔符
实现三角形
.triangle {
width: 0;
height: 0;
margin: 50px auto;
border-bottom: 100px solid #dc5947;
border-left: 50px solid transparent;
border-right: 50px solid transparent;
cursor: pointer;
transform: scale(1.2);
transition: 0.5s;
}