面试题

156 阅读12分钟

1.display的block、inline和inline-block的区别?

(1)block: 会独占一行,多个元素会另起一行,可以设置width、height、margin和padding属性;

(2)inline: 元素不会独占一行,设置width、height属性无效。但可以设置水平方向的margin和padding属性,不能设置垂直方向的padding和margin;

(3)inline-block: 将对象设置为inline对象,但对象的内容作为block对象呈现,之后的内联对象会被排列在同一行内。

对于行内元素和块级元素,其特点如下:

行内元素

  • 设置宽高无效;
  • 可以设置水平方向的margin和padding属性,不能设置垂直方向的padding和margin;
  • 不会自动换行;

块级元素

  • 可以设置宽高;
  • 设置margin和padding都有效;
  • 可以自动换行;
  • 多个块状,默认排列从上到下。

2.定位布局 position中的relative、absolute、fixed、sticky它们之间的区别?

  • (1)relative:相对定位,相对于自己本身在正常文档流中的位置进行定位。
  • (2)absolute:生成绝对定位,相对于最近一级定位不为static的父元素进行定位
  • (3)fixed: 生成绝对定位,相对于浏览器窗口或者frame进行定位。
  • (4)static:默认值,没有定位,元素出现在正常的文档流中。(很少用)
  • (5)sticky:生成粘性定位的元素,容器的位置根据正常文档流计算得出。(很少用)

3.盒子居中的几种方法?

  • 子绝父相
  • Flex布局
  • transform 4.js基本数据类型有哪些?
JavaScript共有八种数据类型,分别是 
UndefinedNullBooleanNumberStringObjectSymbolBigInt

4.数据类型检测的方式有哪些?

(1)typeof

console.log(typeof 2);               // number
console.log(typeof true);            // boolean
console.log(typeof 'str');           // string
console.log(typeof []);              // object    
console.log(typeof function(){});    // function
console.log(typeof {});              // object
console.log(typeof undefined);       // undefined
console.log(typeof null);            // object

其中数组、对象、null都会被判断为object,其他判断都正确。

(2)instanceof

instanceof可以正确判断对象的类型,其内部运行机制是判断在其原型链中能否找到该类型的原型

javascript
 代码解读
复制代码
javascript
复制代码console.log(2 instanceof Number);                    // false
console.log(true instanceof Boolean);                // false 
console.log('str' instanceof String);                // false 
 
console.log([] instanceof Array);                    // true
console.log(function(){} instanceof Function);       // true
console.log({} instanceof Object);                   // true

可以看到,instanceof只能正确判断引用数据类型,而不能判断基本数据类型。instanceof 运算符可以用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性。

(3) constructor

javascript
 代码解读
复制代码
javascript
复制代码console.log((2).constructor === Number); // true
console.log((true).constructor === Boolean); // true
console.log(('str').constructor === String); // true
console.log(([]).constructor === Array); // true
console.log((function() {}).constructor === Function); // true
console.log(({}).constructor === Object); // true

constructor有两个作用,一是判断数据的类型,二是对象实例通过 constrcutor 对象访问它的构造函数。需要注意,如果创建一个对象来改变它的原型,constructor就不能用来判断数据类型了:

ini
 代码解读
复制代码
javascript
复制代码function Fn(){};
 
Fn.prototype = new Array();
 
var f = new Fn();
 
console.log(f.constructor===Fn);    // false
console.log(f.constructor===Array); // true

(4)Object.prototype.toString.call()

Object.prototype.toString.call() 使用 Object 对象的原型方法 toString 来判断数据类型:

vbscript
 代码解读
复制代码
javascript
复制代码var a = Object.prototype.toString;
 
console.log(a.call(2));
console.log(a.call(true));
console.log(a.call('str'));
console.log(a.call([]));
console.log(a.call(function(){}));
console.log(a.call({}));
console.log(a.call(undefined));
console.log(a.call(null));

同样是检测对象obj调用toString方法,obj.toString()的结果和Object.prototype.toString.call(obj)的结果不一样,这是为什么?

这是因为toString是Object的原型方法,而Array、function等类型作为Object的实例,都重写了toString方法。不同的对象类型调用toString方法时,根据原型链的知识,调用的是对应的重写之后的toString方法(function类型返回内容为函数体的字符串,Array类型返回元素组成的字符串…),而不会去调用Object上原型toString方法(返回对象的具体类型),所以采用obj.toString()不能得到其对象类型,只能将obj转换为字符串类型;因此,在想要得到对象的具体类型时,应该调用Object原型上的toString方法。

4.forEach和map的区别?

这方法都是用来遍历数组的,两者区别如下:

-   forEach()方法会针对每一个元素执行提供的函数,对数据的操作会改变原数组,该方法没有返回值;
-   map()方法不会改变原数组的值,返回一个新数组,新数组中的值为原数组调用函数处理之后的值;

4.js原型链最终指向是什么?

*js原型链最终指向的是null*

5.js中 “==“和“===“的区别详解?

    1. ===:三个等号我们称为等同符,当等号两边的值为相同类型的时候,直接比较等号两边 的值,值相同则返回 true,若等号两边的值类型不同时直接返回 false。也就是说三个等号既要判断值也要判断类型是否相等。
    1. ==:两个等号我们称为等值符,当等号两边的值为相同类型时比较值是否相同,类型不同时会发生类型的自动转换,转换为相同的类型后再作比较。也就是说两个等号只要值相等就可以。

6.什么是闭包,为什么要用它?

  • 概念: 有权访问另一个函数内部变量的函数。
  • 本质: 是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量,利用闭包可以突破作用链域,将函数内部的变量和方法传递到外部。
  • 面试:什么是闭包?

通俗的来说:闭包是在一个函数内部在定一个函数,然后内部函数访问外部函数的一个变量就会形成闭包,闭包的话会形成一个私有空间,然后避免全局变量的一个污染,然后会持久化存储数据到内存中,但是闭包也有弊端,它会导致内存泄漏

  • 拓展:内存泄漏怎么解决?

首先避免它的使用,其次的话就是变量执行完以后,可以让它赋值为null,最后利用JS的一个垃圾回收机制进行回收

  • 闭包用处:
  1. 读取内部函数的变量;
  2. 这些变量的值始终会保持在内存中,不会在外层函数调用后被自动清除
  • 闭包优点:
  1. 变量会一直在内存中;        
  2. 避免全局变量的污染;
  3. 私有变量的存在;
  • 闭包缺点

    变量长期储存在内存中,会增大内存的使用量,使用不当会造成内存泄露

  • 判断闭包的3个特点:

  1.函数嵌套函数;

  2.内部函数一定操作了外部函数的局部变量;

  3.外部函数一定将内部函数返回到外部并保存在一个全局变量中;

  • 判断闭包的执行结果:

  1.外部函数被调用了几次就有几个受保护的局部变量的副本;

  2.来自一个闭包的函数被调用几次,受保护的局部变量就变化几次;

  • 闭包特性:
  1. 函数嵌套函数;
  2. 内部函数可以直接使用外部函数的局部变量;
  3. 变量或参数不会被垃圾回收机制回收;

7.数组有哪些原生方法?


数组和字符串的转换方法:toString()、toLocalString()、join() 其中 join() 方法可以指定转换为字符串时的分隔符。

数组尾部操作的方法 pop() 和 push(),push 方法可以传入多个参数。

数组首部操作的方法 shift() 和 unshift() 重排序的方法 reverse() 和 sort(),sort() 方法可以传入一个函数来进行比较,传入前后两个值,如果返回值为正数,则交换两个参数的位置。

数组连接的方法 concat() ,返回的是拼接好的数组,不影响原数组。

数组截取办法 slice(),用于截取数组中的一部分返回,不影响原数组。

数组插入方法 splice(),影响原数组查找特定项的索引的方法,indexOf() 和 lastIndexOf() 迭代方法 every()、some()、filter()、map() 和 forEach() 方法

数组归并方法 reduce() 和 reduceRight() 方法

8.组的遍历方法有哪些?

forEach() 数组方法,值是基本类型, 改变不了;如果是引用类型分两种情况:1、没有修改形参元素的地址值, 只是修改形参元素内部的某些属性,会改变原数组;2、直接修改整个元素对象时,无法改变原数组,没有返回值

map()否数组方法,不改变原数组,有返回值,可链式调用

filter()否数组方法,过滤数组,返回包含符合条件的元素的数组,可链式调用

for...offor...of遍历具有Iterator迭代器的对象的属性,返回的是数组的元素、对象的属性值,不能遍历普通的obj对象,将异步循环变成同步循环

every() 和 some()否数组方法,some()只要有一个是true,便返回true;而every()只要有一个是false,便返回false

find() 和 findIndex()否数组方法,find()返回的是第一个符合条件的值;findIndex()返回的是第一个返回条件的值的索引值

reduce() 和 reduceRight()否数组方法,reduce()对数组正序操作;reduceRight()对数组逆序操作

9.js的深浅拷贝?

浅拷贝:创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
深拷贝: 将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。

浅拷贝的实现方式:

Object.assign() 方法:用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
Array.prototype.slice():slice() 方法返回一个新的数组对象,这一对象是一个由 begin和end(不包括end)决定的原数组的浅拷贝。原始数组不会被改变。
拓展运算符`...`:
**深拷贝的实现方式:**

-   **乞丐版:** JSON.parse(JSON.stringify(object)),缺点诸多(会忽略undefined、symbol、函数;不能解决循环引用;不能处理正则、new Date())
-   **基础版(面试够用):** 浅拷贝+递归 (只考虑了普通的 object和 array两种数据类型)

10. {}和[]的valueOf和toString的结果是什么?

{} 的 valueOf 结果为 {} ,toString 的结果为 "[object Object]" 
[] 的 valueOf 结果为 [] ,toString 的结果为 ""

11. 为什么在调用这个函数时,代码中的b会变成一个全局变量?

function myFunc() { 
let a = b = 0; 
} 
myFunc();
原因是赋值运算符是从右到左的求值的。这意味着当多个赋值运算符出现在一个表达式中时,它们是从右向左求值的。所以上面代码变成了这样:
function myFunc() {
  let a = (b = 0);
}

myFunc();

首先,表达式b = 0求值,在本例中b没有声明。因此,JS引擎在这个函数外创建了一个全局变量b,之后表达式b = 0的返回值为0,并赋给新的局部变量a。

我们可以通过在赋值之前先声明变量来解决这个问题。

function myFunc() {
  let a,b;
  a = b = 0;
}
myFunc();

12.js造成内存泄漏操作有哪些?

  • 1.意外的全局变量

  • 2.被遗忘的计时器或回调函数

  • 3.脱离 DOM 的引用

  • 4.闭包

  • 第一种情况是我们由于使用未声明的变量,而意外的创建了一个全局变量,而使这个变量一直留在内存中无法被回收。

  • 第二种情况是我们设置了setInterval定时器,而忘记取消它,如果循环函数有对外部变量的引用的话,那么这个变量会被一直留在内存中,而无法被回收。

  • 第三种情况是我们获取一个DOM元素的引用,而后面这个元素被删除,由于我们一直保留了对这个元素的引用,所以它也无法被回收。

  • 第四种情况是不合理的使用闭包,从而导致某些变量一直被留在内存当中。

13. TypeScript 相对于纯 JavaScript 有什么好处?

答案:TypeScript 提供了多种好处,包括静态类型、更好的代码分析和工具支持、改进的代码可读性、早期错误检测、更轻松的代码重构以及增强的代码文档。它还使开发人员能够编写更易于维护和扩展的应用程序。

14.typeScript 中的泛型是什么?举个例子。

答案:TypeScript 中的泛型允许您创建可与各种类型一起使用的可重用组件或函数。它们支持强类型,同时保持使用不同数据类型的灵活性。这是一个例子:

function identity<T>(arg: T): T {  
    return arg;
}
const result1 = identity<number>(42); // Explicitly specifying the type
const result2 = identity('hello'); // Inferring the type

15.解释 TypeScript 中联合类型的概念并提供示例。

答:联合类型允许一个变量有多种类型。它通过使用 | 来表示类型之间的符号。这允许变量存储任何指定类型的值。这是一个例子:

function printId(id: number | string): void {  
console.log(`ID: ${id}`);
}
printId(123); // Output: "ID: 123"printId('abc'); // Output: "ID: abc"

16.Ts基本类型

答:联合类型允许一个变量有多种类型。它通过使用 | 来表示类型之间的符号。这允许变量存储任何指定类型的值。这是一个例子:

[1、布尔值]

[2、数字]

[3、字符串]

[4、数组]

[5、元组Tuple]

[6、枚举]

[7Any]

[8Void]

[9NullUndefined]

[10Never]

[11Object]

[12、类型别名与联合类型]

[13、类型推论]

17.type和interface的区别?

interface可以重复声明,type不行,继承方式不一样,type使用交叉类型方式,interface使用extends实现。在对象扩展的情况下,使用接口继承要比交叉类型的性能更好。建议使用interface来描述对象对外暴露的借口,使用type将一组类型重命名(或对类型进行复杂编程)。

18.vue的生命周期以及生命周期做了些什么?
beforeCreate是new Vue()之后触发的第一个钩子,在当前阶段data、methods、computed以及watch上的数据和方法都不能被访问。

created在实例创建完成后发生,当前阶段已经完成了数据观测,也就是可以使用数据,更改数据,在这里更改数据不会触发updated函数。可以做一些初始数据的获取,在当前阶段无法与Dom进行交互,如果非要想,可以通过vm.$nextTick来访问Dom。

beforeMount发生在挂载之前,在这之前template模板已导入渲染函数编译。而当前阶段虚拟Dom已经创建完成,即将开始渲染。在此时也可以对数据进行更改,不会触发updated。

mounted在挂载完成后发生,在当前阶段,真实的Dom挂载完毕,数据完成双向绑定,可以访问到Dom节点,使用$refs属性对Dom进行操作。

beforeUpdate发生在更新之前,也就是响应式数据发生更新,虚拟dom重新渲染之前被触发,你可以在当前阶段进行更改数据,不会造成重渲染。

updated发生在更新完成之后,当前阶段组件Dom已完成更新。要注意的是避免在此期间更改数据,因为这可能会导致无限循环的更新。

beforeDestroy发生在实例销毁之前,在当前阶段实例完全可以被使用,我们可以在这时进行善后收尾工作,比如清除计时器。

destroyed发生在实例销毁之后,这个时候只剩下了dom空壳。组件已被拆解,数据绑定被卸除,监听被移出,子实例也统统被销毁。

19.vue3与vue2的区别?

源码组织方式变化:使用 TS 重写

支持 Composition API:基于函数的API,更加灵活组织组件逻辑(vue2用的是options api)

响应式系统提升:Vue3中响应式数据原理改成proxy,可监听动态新增删除属性,以及数组变化

编译优化:vue2通过标记静态根节点优化diff,Vue3 标记和提升所有静态根节点,diff的时候只需要对比动态节点内容

打包体积优化:移除了一些不常用的api(inline-template、filter)

生命周期的变化:使用setup代替了之前的beforeCreate和created

Vue3 的 template 模板支持多个根标签

Vuex状态管理:创建实例的方式改变,Vue2new Store , Vue3为createStore

Route 获取页面实例与路由信息:vue2通过this获取router实例,vue3通过使用 getCurrentInstance/ userRoute和userRouter方法获取当前组件实例

Props 的使用变化:vue2 通过 this 获取 props 里面的内容,vue3 直接通过 props

父子组件传值:vue3 在向父组件传回数据时,如使用的自定义名称,如 backData,则需要在 emits 中定义一下

20.什么是mvvm?
MVVM是Model-View-ViewModel缩写,也就是把MVC中的Controller演变成ViewModel。Model层代表数据模型,View代表UI组件,ViewModel是View和Model层的桥梁,数据会绑定到viewModel层并自动将数据渲染到页面中,视图变化的时候会通知viewModel层更新数据。

21.说一下 v-if 与 v-show 的区别?
v-if和v-show看起来似乎差不多,当条件不成立时,其所对应的标签元素都不可见,但是这两个选项是有区别的:

1、v-if在条件切换时,会对标签进行适当的创建和销毁,而v-show则仅在初始化时加载一次,因此v-if的开销相对来说会比v-show大。

2、v-if是惰性的,只有当条件为真时才会真正渲染标签;如果初始条件不为真,则v-if不会去渲染标签。v-show则无论初始条件是否成立,都会渲染标签,它仅仅做的只是简单的CSS切换。

22.为什么vue组件中data是一个函数? 对象为引用类型,当复用组件时,由于数据对象都指向同一个data对象,当在一个组件中修改data时,其他重用的组件中的data会同时被修改;而使用返回对象的函数,由于每次返回的都是一个新对象(Object的实例),引用地址不同,则不会出现这个问题。

23.为什么会产生跨域,跨域的解决方法有哪些?
跨域问题主要是由于浏览器的同源策略所致,‌即浏览器阻止前端网页从一个域名向另一个域名的服务器发送请求。‌这种策略是为了保证用户信息的安全,‌防止恶意的网站窃取数据。‌例如,‌如果一个网站设置了Cookie来保存用户的登录状态,‌而没有同源限制,‌另一个网站可能读取该Cookie,‌导致隐私信息泄漏。‌

解决跨域问题的常见方法包括:‌

  1. JSONP:‌这是一种早期的解决方案,‌利用<script>标签可以跨域的原理实现数据传输。‌它的原理是通过动态创建<script>标签,‌并给其写入由后端生成的JS函数调用地址,‌从而实现跨域通信。‌但这种方法需要服务的支持,‌且只能发起GET请求。‌
  2. CORS(‌跨来源资源共享)‌:‌CORS是一种W3C规范的技术方案,‌通过在后端设置HTTP头部信息来允许跨域请求。‌它支持所有类型的HTTP请求,‌功能完善,‌并且是现代浏览器广泛支持的标准解决方案。‌
  3. 代理服务器:‌如使用Nginx进行反向代理,‌将跨域请求代理为不跨域的请求。‌这种方法需要在Nginx进行额外配置,‌但支持各种请求方式。‌
  4. 后端设置允许跨域:‌在后端服务器中设置允许跨域的相关配置,‌如设置HTTP头部信息,‌以允许来自其他域的请求。‌
  5. Flash跨域:‌对于不支持CORS的旧版浏览器(‌如IE7及以下版本)‌,‌可以考虑使用Flash进行跨域请求。‌Flash在发送请求前会检查服务器域名根目录下的crossdomain.xml文件,‌根据该文件的内容决定是否允许跨域请求。‌

这些方法各有优缺点,‌选择哪种方法取决于具体的应用场景和浏览器兼容性需求。‌例如,‌CORS因其广泛的支持度和灵活性成为现代Web开发中解决跨域问题的首选方案

24.常用的UI框架?

25.小程序发行提示包过大有哪几种解决方式?

什么是防抖和节流?有什么区别?如何实现?

防抖: 触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间
思路: 每次触发事件时都取消之前的延时调用方法

节流: 高频事件触发,但在 n 秒内只会执行一次,所以节流会稀释函数的执行频率
思路: 每次触发事件时都判断当前是否有等待执行的延时函数

4.交换a和b的值,不能重新定义变量?

方法一:使用加法(仅限于数字)
let a = 5;   
let b = 10;     
a = a + b;   
b = a - b;   
a = a - b;     
console.log(a); // 输出 10   
console.log(b); // 输出 5

方法二:使用解构赋值
解构赋值是ES6中引入的一种语法,它可以用来从数组或对象中提取数据,
然后将其赋值给不同的变量。对于交换两个变量的值,解构赋值非常有用。
let a = 5;   
let b = 10;     
[a, b] = [b, a];     
console.log(a); // 输出 10   
console.log(b); // 输出 5
解构赋值在这里非常直观和简洁,因为它直接创建了新的变量绑定,
然后将原来的`b`的值赋给`a`,原来的`a`的值赋给`b`。
这是交换两个变量值而不使用临时变量的推荐方法。

26.vue对象或者数组数据发生改变视图不更新有哪几种解决方式?

this.$set()
this.$forceUpdate