前端高频面试题(摘自pink老师抖音)

157 阅读12分钟

1、什么是MVVM,MVC模型

MVC

即Model-View-Contorller(模型-视图-控制器)是项目的一种分层开发思想,它把复杂的业务逻辑抽离为单一的小模块,每个模块看似相互独立,其实又各自有相互依赖的关系。它的好出是:能保证模块的单一性,方便程序的开发、维护、耦合度低。

MVVM

即Model-View-ViewModel(模型-视图-控制器)是一种双向数据绑定的模式,用ViewModel来建立起Model数据层和View视图层的连接,数据改变会影响视图,视图改变也会影响数据。

2、vue双向数据绑定的原理?

vue.js是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

具体步骤:

第一步

需要observe的数据对象进行递归遍历,包括子属性对象的属性,都家上setter和getter这样的话,给这个对象的某个值赋值,就会触发setter,那么就监听到了数据的变化。

第二步

compile解析模块指令,将模块中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图。

vue实例从创建到销毁的过程就是生命周期

也就是从开始创建、初始化数据、编译模板、挂载dom-->渲染、更新-->渲染、准备销毁、销毁中等一系列过程。

vue的生命周期常见的主要分为4大阶段8大钩子函数

一、创建前后

beforeCreate:data和method还没初始化。

created:data和method初始化完成。

二、渲染前后

beforeMount:已经编译好了模板字符串、但没有真正渲染到页面中。

mounted:已经渲染完成,可以看到页面。

三、数据更新前后

beforeUpdate:已经可以拿到最新的数据,但没有渲染到视图中。

updated:已经把更新后的数据渲染到视图中。

四、销毁前后

beforeDestroy:实例进去准备销毁阶段,此时data、methods、指令等还是可用状态

destroyed:实例已经完成销毁,此时的data、methods、指令等已不可用

3、v-if和v-show的区别

v-if

是真正的条件渲染,因为它会确保在切换过程中 条件块内的事件监听器和子组件适当地被销毁和重建,操作地实际上是dom元素地创建和销毁。

v-show

不管初始条件的true/false元素会一直被渲染,知道页面实例被销毁,只是简单地操作css(display:none/block)来切换这个dom是否显示。

总结:

v-if有更高的切换开销,v-show有更高的初始渲染开销,因此如果非常频繁地切换,使用v-show会更好,交互起来更流畅,如果运行时使用条件很少改变,则使用v-if

4、async await是什么?它有哪些作用?

async await是ES7的新语法,它的作用就是async用于申明一个function是异步的,而await用于等待一个异步方法执行完成,它可以很好地替代promise中的then

async函数返回的是一个promise对象,可以使用then方法添加回调函数,当函数执行的时候,一旦遇到await就先返回,等到异步操作完成之后再执行函数内后面的语句。

5、常用的数组方法

concat()

合并两个或多个数组,此方法不会更改数组,会返回一个新的数组:let arrA = arrB.concat(arrC)

find()

方法返回数组中提供的测试函数(函数内判断)第一个元素的,否则返回undefined

array.find(function(currentValue, index, arr),thisValue)
var ages = [3, 10, 18, 20];
 
function checkAdult(age) {
    return age >= 18;
}
 
function myFunction() {
    document.getElementById("demo").innerHTML = ages.find(checkAdult);
}
//  18

findIndex()

方法返回数组中满足提供测试函数(函数内判断)第一个元素的索引,否则返回-1。

array.findIndex(function(currentValue, index, arr), thisValue)

includes()

方法用来判断数组是否包含一个指定的值,根据情况,返回true/false

string.includes(searchvalue, start) // start可选,开始的索引(默认0)

indexOf()

方法返回在数组中可以找到一个指定值的第一个索引,不存在则返回-1。

string.indexOf(searchvalue,start) // start可选,开始的索引(默认0)

join()

方法将一个数组(或一个类数组对象)的所有元素连接成一个字符串并返回这个字符串,如果数组长度为1,返回的字符串不使用分隔符。

array.join(separator) // separator可选。指定要使用的分隔符。如果省略该参数,则使用逗号作为分隔符。

-----------------

pop()

方法从数组中删除最后一个元素,并返回该元素的值,改变数组

array.pop()
const sites = ['Google', 'Runoob', 'Taobao', 'Zhihu', 'Baidu'];
 
console.log(sites.pop());
// 输出结果为: "Baidu"
 
console.log(sites);
// 输出结果为: ['Google', 'Runoob', 'Taobao', 'Zhihu']
 
sites.pop();
 
console.log(sites);
//  输出结果为: ["Google", "Runoob", "Taobao"]

push()

方法将一个或多个元素添加到数组末尾,并返回该数组的新长度,改变数组

array.push(item1, item2, ..., itemX)
var fruits = ["Banana", "Orange", "Apple", "Mango"];

fruits.push("Kiwi","Lemon","Pineapple")

// Banana,Orange,Apple,Mango,Kiwi,Lemon,Pineapple

shift()

方法从数组中删除 第一个元素,并返回该元素的值,改变数组

array.shift()
var fruits = ["Banana", "Orange", "Apple", "Mango"];

fruits.shift()

// Orange,Apple,Mango

unshift()

方法将一个或多个元素添加到数组头部,并返回该数组的新长度,改变数组

array.unshift(item1,item2, ..., itemX)
var fruits = ["Banana", "Orange", "Apple", "Mango"];

fruits.unshift("Lemon","Pineapple");

// Lemon,Pineapple,Banana,Orange,Apple,Mango

splice()

方法通过删除替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容,改变数组

array.splice(index,howmany,item1,.....,itemX)
// index 必需。规定从何处添加/删除元素。该参数是开始插入和(或)删除的数组元素的下标,必须是数字。
// howmany 可选。规定应该删除多少个元素。必须是数字,但可以是 "0"则不删除。如果未规定此参数,则删除从 index 开始到原数组结尾的所有元素。
// item1, ..., itemX 可选。要添加到数组的新元素
var fruits = ["Banana", "Orange", "Apple", "Mango"];

fruits.splice(2,0,"Lemon","Kiwi");

let a = [1,2,3,4]; 
let b = a.splice(2,1,"B");
console.log(b) // [3]
console.log(a) // [1, 2, "B", 4]
let c = a.splice(1,2);
console.log(a) // [1,4]


// Banana,Orange,Lemon,Kiwi,Apple,Mango

reverse()

方法将数组中元素的位置颠倒,并返回该数组,改变数组

array.reverse()

sort()

方法用原地算法对数组的元素进行排序,并返回数组,默认排序顺序是将元素转换为字符串,然后比较它们的UTF-16代码单元格序列时构建的。

array.sort(sortfunction)
// sortfunction: 排序的方法
/***
 * 根据对象字段排序方法 type = 'reverse' || 'order'
 * data.sort(util.compare('Y', 'reverse'))
 * 'Y': 字段名
 * 'reverse': 排序方式
 */
function compare(property, type) {
  return function (a, b) {
    var value1 = a[property];
    var value2 = b[property];
    return type == 'reverse' ? (value2 - value1) : (value1 - value2);
  }
}

6、数组 有哪几种循环方式?分别有什么用?

every

方法测试一个数组的所有元素是否能通过某个指定函数的测试,它返回一个布尔值

array.every(function)

some()

方法测试是否至少有一个元素通过被提供的测试函数,返回一个布尔值

array.some(function)

filter()

方法创建一个新数组,起包含通过所提供测试函数的所有元素。

let newArr = array.filter(function) // 过滤

forEach()

方法对数组的每个月拿苏执行一次提供的函数。

array.forEach(item => {
	// 执行操作
})

7、常用的字符串方法有哪些?

chartAt()

方法从一个字符串中返回指定字符。

string.charAt(index) // index必需,表示字符串中某个位置的数字,即字符在字符串中的位置。

concat()

方法将一个或多个字符串与源字符串连接合并,形成一个新的字符串并返回

let strA = 'ABC',
    strB = 'CDE'
let srtC = strA.concat(strB) // 'ABCCDE'

includes

方法用于判断一个字符串是否包含在另一个字符串中,返回true/false

string.includes(searchvalue, start) // start可选,开始的索引(默认0)

indexOf()

方法返回调用它的string对象中给i第一次出现指定值的索引,从start处进行搜索,如果未找到该值,则返回-1

string.indexOf(searchvalue,start) // start可选,开始的索引(默认0)

match()

方法检索返回第一个字符串匹配正则表达式的结果

string.match(regexp) // regexp,必需。规定要匹配的模式的 RegExp 对象。如果该参数不是 RegExp 对象,则需要首先把它传递给 RegExp 构造函数,将其转换为 RegExp 对象。

// 在字符串中查找 "ain":

var str="The rain in SPAIN stays mainly in the plain"; 
var n=str.match(/ain/g);

// n 输出数组结果值: ain,ain,ain

replace()

方法返回一个由替换之(replacement)替换一些或所有匹配的模式(pattern)后的新字符串,模式可以是一个字符串或者一个正则表达式,替换值可以是一个字符串或者一个每次匹配都要调用的回调函数

方法不会改变原字符串,而是返回一个新字符串

string.replace(searchvalue,newvalue)
// searchvalue	必需。规定子字符串或要替换的模式的 RegExp 对象。请注意,如果该值是一个字符串,则将它作为要检索的直接量文本模式,而不是首先被转换为 RegExp 对象
// newvalue	必需。一个字符串值。规定了替换文本或生成替换文本的函数。

padStart()&padEnd()

方法用另一个字符串填充当前字符串(重复,如果需要的话),一边产生的字符串达到给定的长度。填充从当前字符串的开始应用的,常用于时间补0。

返回新的字符串,表示用参数字符串从头部(左/右侧)补全原字符串。

console.log("h".padStart(5,"o"));  // "ooooh"
console.log("h".padEnd(5,"o"));    // "hoooo"
console.log("h".padStart(5));      // "    h"

slice()

方法截取某个字符串的一部分,并返回一个新的字符串,不会改变原字符串

string.slice(start,end)

// start 必须。 要抽取的片断的起始下标,第一个字符位置为 0。如果为负数,则从尾部开始截取。
// end 可选。 紧接着要截取的片段结尾的下标。若未指定此参数,则要提取的子串包括 start 到原字符串结尾的字符串。如果该参数是负数,那么它规定的是从字符串的尾部开始算起的位置。slice(-2) 表示提取原数组中的倒数第二个元素到最后一个元素(包含最后一个元素)。

split()

方法使用指定的分隔符字符串将一个String对象分割成字符串数组,返回一个字符串数组,不改变原字符串

string.split(separator,limit)
// separator 可选。字符串或正则表达式,从该参数指定的地方分割 string Object。
// limit 可选。该参数可指定返回的数组的最大长度。如果设置了该参数,返回的子串不会多于这个参数指定的数组。如果没有设置该参数,整个字符串都会被分割,不考虑它的长度。

trim()

方法会从一个字符串的两端删除空白字符。在这个上下文中的空白字符是所有的空白字符(space,tab,no-break space等)以及所有行终止字符(LF,CR)。

8、什么是原型链

每一个实例对象上都有一个proto属性,指向构造函数的原型对象,构造函数的原型对象也是一个对象,也有proto属性,这样一层一层往上找的过程就成了原型链

9、什么是闭包?手写一个闭包函数?闭包有哪些优缺点?

闭包(closure)指有权访问另一个函数作用域中变量的函数。简单理解就是,一个作用域可以访问另一个函数内部的局部变量

function fn() {
    var num = 10
    function fun() {
        console.log(num)
    }
    return fun
}
var f = fn()
f()

作用:延长变量作用域、在函数的外部可以访问函数内部的局部变量,容易造成内层泄露,因为闭包中的局部变量永远不会被回收

10、常见的继承有哪些?

一、原型链继承

特点:1、实例可继承的属性有:实例的构造函数的属性,父类构造函数的属性,父类原型的属性。 (新实例不会继承父类实例的属性)。

缺点:1、新实例无法向父类构造函数传参

2、继承单一

3、所有新实例都会共享父类实例的属性。(原型上的属性是共享的,一个实例修改了原型属性,另一个实例的原型属性也会呗修改)

二、借用构造函数继承

重点:用.call().apply()将父类构造函数引入子类函数(在子类函数中做了父类函数的自执行(复制))【

特点:1、只继承了父类构造函数的属性,没有继承父类原型的属性

2、解决了原型链继承缺点1、2、3

3、可以继承多个构造函数属性(call多个)

缺点:1、只能继承父类构造函数的属性

2、无法实线构造函数的复用

3、每个新实例都有父类构造函数的副本,冗余臃肿

三、组合继承(组合原型链继承和接用构造函数继承)-- 常用

重点:结合了两种模式的优点,传参和复用。

特点:1、可以继承父类原型上的属性,可以传参,可以复用。

2、每个新实例引入的构造函数属性是私有的。

缺点:调用了两次父类构造函数(耗内存),子类的构造函数会代替原型上的那个父类构造函数。

四、原型式继承

重点:用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了可以随意增添属性的实例或对象,object.create()就是这个原理

特点:类似于复制一个对象,用函数来包装。

缺点:1、所有实例都会继承原型上的属性。

2、无法实现复用。(新实例属性都是后面添加的)

五、class类实线继承

通过extends和super来实现继承

六、寄生式继承

重点:就是给原型式继承外面套了个壳子。

优点:没有创建自定义类型,因为只是套了个壳子返回对象,这个函数顺理成章就成了创建的对象。

缺点:没有原型,无法复用

11、后台管理系统中权限管理怎么实线的?

登录:当用户填写完账号密码后向服务端验证是否正确,验证通过之后