this指向问题

题1

题2

题3

题4

css面试题

  1. BFC(块级格式化上下文,用于清除浮动,防止margin重叠) 独立的容器
  2. BFC的生成
1
2
3
4
float 不为none的元素
position为fixed和absolute的元素
display为inline-block,table-cell,talbe-caption,flex,inline-flex的元素
overflow不为visible的元素
  1. 常见的块元素有<h1>~<h6>、<p>、<div>、<ul>、<ol>、<li>等,其中<div>标签是最典型的块元素。
  2. 常见的行内元素有<a>、<strong>、<b>、<em>、<i>、<del>、<s>、<ins>、<u>、<span>等,其中<span>标签最典型的行内元素。有的地方也成内联元素
  3. 在行内元素中有几个特殊的标签——<img />、<input />、<td>,可以对它们设置宽高和对齐属性,有些资料可能会称它们为行内块元素。
  4. H5语义化元素:header,nav,main,article,section,aside,footer,small,strong

基础面试题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 给定后转换成指定的输出形式
const csv = `
name, age, parent
Bob, 36, David,
David, 60,
Anni, 10, Bob
`
function csvTran(t) {
const a = t.split('\n')
const b = a.splice(1, a.length - 2).map(v => v.replace(/\s/g, '').split(','))
const d = b.slice(1, b.length)
const list = []
for (let i of d) {
const g = {}
for(let f in i){
const h = b[0][f]
if(h){
g[h] = i[f]
}
}
list.push(g)
}
console.log(list)
}
csvTran(csv)
  • 给定一个正偶数,算出他有几种方式拆分成两个素数相加的形式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
let a = 10
// 寻找所有的素数
function AddList(n) {
const res = []
for (let i = 2; i < n; i++) {
let flag = true
for (let m = 2; m < i; m++) {
console.log(m)
if (i % m === 0) {
flag = false
console.log(m)
break
}
}
if (flag) {
res.push(i)
}
}
console.log(res)
return res
}
// 相加计算
function countAdd(list, num) {
const r = []
for (let i = 0; i < list.length; i++) {
console.log(i)
for (let j = i + 1; j < list.length; j++) {
if (list[i] + list[j] === num) {
r.push([[list[i], list[j]]])
}
}
}
console.log(r)
}
const l = AddList(a)
countAdd(l, a)

面试题问答

微信小程序是如何实现登录的

  • 首先需要一个按钮来触发事件
  • 调用微信小程序的登录接口wx.login,拿到code
  • 调用微信小程序的获取用户的接口wx.getUserProfile拿到个人信息
  • 拿到个人信息后调用后台的接口,将个人信息在传入到后台,登录后将相关的信息缓冲在本地存储中,方便后续的开发使用

vue-router说说你了解的

  • 两种模式hash模式和history模式
  • 两种的区别:
    • 都是依靠浏览器自身的特性;
    • hash模式是在浏览器地址中加一个#号,虽然改变了url地址,但是它不会包括在http请求,只是用来指导浏览器的动作
    • history模式则是没有#号 但是在页面刷新的时候如果后端没有进行配置,并且访问不到资源就会造成404局面,因此需要去配置好nginx进行拦截处理

vue2和vue3的区别

  • vue2和vue3的数据双向绑定的原理发生了变化

    • vue2使用的是es5中的API Object.defineProperty()对数据进行劫持结合发布者订阅者模式的方式来实现;
    • vue3使用的是ES6中的API proxy 进行数据代理
  • vue3支持碎片化节点,组件中可以有多个根节点

  • vue2和vue3使用的API不同

    • vue2 使用的是Options API选项形式;
    • vue3 使用的是合成型API composition API 组合式API;
  • vue2和vue3它们的生命周期不同

    • vue2:
      • beforeCreate
      • created
      • beforeMunted
      • Mounted
      • beforeUpdate
      • Update
      • beforeDestory
      • destoryed
      • activated
      • deactivated
    • vue3:
      • setup()
      • onBeforeMount
      • onMouted
      • computed
      • onBeforeUpdate
      • onUpdated
      • onBeforeUnmount
      • onUnmouted
      • onActivated
      • onDeactivated
  • 它们建立数据方式不同

    • vue2: 直接在data中创建
    • vue3: 在setup中创建,该组件在组件初始化构建时候触发
  • 父子的传参方式不同

  • vue3新增了瞬移组件 teleport

Vuex面试题

nextTick vue

  • 用途:
    在视图更新之后,基于新的视图进行操作,使用nextTick中的回调函数,在下一次更新DOM更新循环结束之后执行回调

  • 为什么使用$nextTick?
    DOM更新是异步操作,在数据更新完成之后,DOM不会立即更新,而是等同一事件循环中的所有数据变化完成之后,在统一进行视图更新。所以使用$nextTick可以使同步任务在DOM更新完成之后去执行。
    例如:
    获取设置输入框由隐藏变为显示,再去获取这个DOM元素就需要使用$nextTick
    在 created 和 mounted 阶段,如果需要操作渲染后的试图,也要使用 nextTick 方法。
    注意 mounted 不会承诺所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以用 vm.$nextTick 替换掉 mounted

  • 原理:事件循环

vue eventBus

  • EventBus 是消息传递的一种方式,基于一个消息中心,订阅和发布消息的模式,称为发布订阅者模式。

  • on 订阅消息 emit 发布消息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    lass Bus {
    constructor () {
    this.callbacks = {}
    }
    $on(name,fn) {
    this.callbacks[name] = []
    this.callbacks[name].push(fn)
    }
    $emit(name,args) {
    if(this.callbacks[name]){
    //存在遍历所有callback
    this.callbacks[name].forEach(cb => cb(args))
    }
    }
    }
    const EventBus = new Bus()
    EventBus.$on('fn1', function(msg) {
    alert(`订阅的消息是:${msg}`);
    });
    EventBus.$emit('fn1', '你好,世界!');

    闭包的概念,作用,应用及缺点

  • 概念:函数嵌套函数,内部函数就是闭包;能够读取其他函数内部变量的函数

  • 作用:可以读取函数内部的变量;可以使变量的值始终在内存中,不会被垃圾回收机制回收;可以避免使用全局变量,防止全局变量污染

  • 应用:for循环中的保留i的操作;防抖和节流;函数柯里化

  • 缺点:容易引起内存泄漏

原型和原型链

  • 原型:每个函数上都有prototype属性,该属性指向原型对象;使用原型对象的好处就是所有的实例共享它所包含的属性和方法
  • 原型链:每个对象上都有一个原型对象,通过__proto__指针指向其原型对象,并从中继承方法和属性,同时原型对象也可能拥有原型,最终指向null

继承

总结

  1. 实现继承共有6种方法,有:原型链继承,构造函数继承,组合式继承(原型链+构造函数),原型继承,寄生式继承,寄生组合式继承
  2. 这几种方法 每种方法都有各自的特点
    • 原型链继承他会将子函数原型指向要继承的函数实例,这样会造成共用一个原型对象,当一个实例发生变化,另一个也随之跟着变化
    • 构造函数继承 只能继承父类的实例的属性和方法,不能继承父类原型的属性和方法
    • 组合式继承 原型链和构造函数继承结合 会导致 在执行两次构造函数,影响性能;并且这里的constructor可进行遍历(应该禁用)
    • 原型式继承 使用es5中的方法Object.create方法 创建一个空的对象,将新对象的原型指向对象
    • 寄生式继承 在原型式继承的基础上,在父类的方法上添加了一些方法
    • 寄生组合式继承 使用工厂函数进行封装处理 使用es5中的Object.defineProperty方法对函数原型(实例原型)的constructor的值进行配置enumerable为false

原型链继承

1
2
3
4
5
6
7
8
9
10
11
12
13
function Parent1() {
this.name = 'parent1';
this.play = [1, 2, 3]
}
function Child1() {
this.type = 'child2';
}
Child1.prototype = new Parent1();
const s1 = new Child1()
s1.play.push(4)
const s2 = new Child1()
console.log(s1.play) // [1,2,3,4]
console.log(s2.play) // [1,2,3,4]
  • 缺点:实现数据共享,当其中一个实例发生改变 另一个也随之跟着改变

构造函数方法(call方法)

  • 缺点:只能继承父类的实例的属性和方法,但是不能继承父类原型的属性和方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Parent1(){
this.name = 'parent1';
}

Parent1.prototype.getName = function () {
return this.name;
}

function Child1(){
Parent1.call(this);
this.type = 'child1'
}

let child = new Child1();
console.log(child); // 没问题
console.log(child.getName()); // 会报错

组合继承(前两种方法)

  • 缺点:多执行了一次构造函数,并且这里的constructor可进行遍历
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function Parent3 () {
this.name = 'parent3';
this.play = [1, 2, 3];
}

Parent3.prototype.getName = function () {
return this.name;
}
function Child3() {
// 第二次调用 Parent3()
Parent3.call(this);
this.type = 'child3';
}

// 第一次调用 Parent3()
Child3.prototype = new Parent3();
// 手动挂上构造器,指向自己的构造函数
Child3.prototype.constructor = Child3;
var s3 = new Child3();
var s4 = new Child3();
s3.play.push(4);
console.log(s3.play, s4.play); // 不互相影响
console.log(s3.getName()); // 正常输出'parent3'
console.log(s4.getName()); // 正常输出'parent3'

原型式继承

  • 使用Object.create方法来实现,多个实例的引用类型属性指向相同的内存,存在篡改的可能 与原型链继承类似
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let parent4 = {
name: "parent4",
friends: ["p1", "p2", "p3"],
getName: function() {
return this.name;
}
};

let person4 = Object.create(parent4);
person4.name = "tom";
person4.friends.push("jerry");
let person5 = Object.create(parent4);
person5.friends.push("lucy");

console.log(person4.name);
console.log(person4.name === person4.getName());
console.log(person5.name);
console.log(person4.friends);
console.log(person5.friends);

寄生式继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 let parent5 = {
name: "parent5",
friends: ["p1", "p2", "p3"],
getName: function() {
return this.name;
}
};

function clone(original) {
let clone = Object.create(original);
clone.getFriends = function() {
return this.friends
};
return clone;
}

let person5 = clone(parent5);
console.log(person5.getName());
console.log(person5.getFriends());

寄生组合式继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function clone (parent, child) {
// 这里改用 Object.create 就可以减少组合继承中多进行一次构造的过程
child.prototype = Object.create(parent.prototype);
Object.defineProperty(child.prototype,'constructor',{
value:child,
enumerable:false
})
}

function Parent6() {
this.name = 'parent6';
this.play = [1, 2, 3];
}
Parent6.prototype.getName = function () {
return this.name;
}
function Child6() {
Parent6.call(this);
this.friends = 'child5';
}

clone(Parent6, Child6);

Child6.prototype.getFriends = function () {
return this.friends;
}

let person6 = new Child6();
console.log(person6);
console.log(person6.getName());
console.log(person6.getFriends());

节流和防抖

  1. 防抖就是在某一时间内快速执行事件,然后仅最后一次事件执行
1
2
3
4
5
6
7
8
9
10
11
const dedounce = function (fn, delay) {
let timer = null
return function () {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fn.call(this)
}, delay)
}
}
  1. 节流控制执行次数,在某段时间内如果执行多次,将仅仅执行一次
1
2
3
4
5
6
7
8
9
10
11
const throttle = function(fn,delay){
let timer = null
return function(){
if(!timer){
timer= setTimeout(()=>{
fn.call(this)
timer = null
},delay)
}
}
}

apply,call,bind方法

  • 都是用来改变this指向问题的
  • apply,call是立即执行,bind返回一个函数

手写实现一个apply

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Function.prototype._apply = function (context, arg) {
console.log(arg)
if (typeof this !== "function") {
throw new TypeError('类型传入错误')
}
// 创建一个唯一的属性
const key = Symbol()
context[key] = this
result = context[key](arg)
delete context[key]
return result
}
let obj = {
name: "zs"
}
function fn(arg) {
this.name = arg[0]
console.log(this.name)
}
fn._apply(obj, ['zs'])

手写实现一个call

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Function.prototype._call = function (context, ...arg) {
console.log(arg)
if (typeof this !== "function") {
throw new TypeError('类型传入错误')
}
// 创建一个唯一的属性
const key = Symbol()
context[key] = this
result = context[key](...arg)
delete context[key]
return result
}
let obj = {
name: "zs"
}
function fn(name, age) {
this.name = name
this.age = age
console.log(this.age)
}
fn._call(obj, 'zs', 20)

手写实现一个bind

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Function.prototype._bind = function (...args) {
if (typeof this !== "function") {
throw new TypeError('Error')
}
let self = this
let context = args.shift() //shift 方法是将数组中的第一个数删除,然后然后返回删除的数
return function (...arg) {
return self.apply(context, args[0].concat([...arg]))
}
}

function fn(...arg) {
this.a = arguments[0]
console.log('a:' + this.a);
console.log('name:' + this.name);
console.log(this)
console.log(arguments)
}
let obj = {
name: 'zs'
}
console.log(fn._bind(obj, ['apply', '0', '1'])(1, 3));

ajax实现过程

  1. 创建一个XMLHttpRequest对象
  2. 在创建好的对象下使用open方法,创建一个http请求
  3. 并且设置请求头的信息,比如content-type
  4. 设置响应http请求状态变化的函数onreadstatechange
  5. 发送http请求,使用send方法
  6. 获取异步调用时返回的结果
  • 状态码 0-4 分别表示 未初始化状态,初始化状态,发送请求时的状态,接受请求时的状态,完成时的状态

axios实现原理

  1. 它是一个基于Promise的http库,可以在浏览器和node.js中使用
  2. 从浏览器中创建XMLhttpRequest 从node.js创建一个http请求
  3. 支持promise API
  4. 可以转换请求数据和响应数据,并对响应的内容自动转换为json类型的数据
  5. 取消请求,request.abort
  6. 客户端支持防御XSRF(跨站请求伪造)

浅拷贝和深拷贝

  1. 深拷贝是指:开辟了一个新的空间,然后将对象指向该地址,并且与原来的对象互不干扰
  2. 浅拷贝是指:比较浅的拷贝,他与原来的变量还是指向同一个地址,两者之间相互影响
  • 原始类型:String,Null,undefined,boolean,number,symbol
  • 引用类型:Object,function,Array

实现浅拷贝的方法:

  • 针对引用数据类型 使用for in的方法实现

实现深拷贝的方法

  • 使用for..in.. + 递归的方式实现
  • 使用JSON.parse(JSON.stringify(对象))实现深拷贝

箭头函数和普通函数的区别

  1. 外形也不同箭头函数使用箭头定义
  2. 箭头函数全是匿名函数;普通函数可以有匿名函数也可以有具名函数
  3. 箭头函数没有this,它的this指向上层作用域;普通函数的this指向调用它的对象,如果用作构造函数,则指向它的实例
  4. 箭头函数没有arguments;普通函数每一个都具有arguments,用来存储实际传递的参数;
  5. 箭头函数没有prototype属性
  6. 箭头函数不能使用new 来构造函数

跨域的解决方法

  1. 使用jsonp解决,缺陷只能进行get请求,实现原理就是创建一个script元素,将请求的参数赋值给src然后配合后端,数据通过函数执行传参的方式实现数据传递
  2. 使用 document.domain + iframe 实现
  3. 使用 window.name + iframe 来实现
  4. 使用nginx 配置 “assess-control-allow-origin“ 设置跨域资源共享
  5. 使用location.hash + iframe 来实现
  6. 使用webSockets来实现
  7. 使用window.postMessage 来实现

什么是柯里化,手写实现

  • 把多参数的函数,转换为单参数的函数(参数复用,闭包原理)
1
2
3
4
5
6
7
8
9
10
11
12
function currying() {
let args = Array.prototype.slice(arguments)
let res = function () {
for (let i of arguments) { args.push(i) }
return res
}
res.toString = function () {
return args.reduce((a, b) => a + b)
}
return res
}
console.log(currying(1)(2)(3).toString());

什么是事件监听、事件委托

  1. 事件委托:将子元素要执行的响应事件,绑定到父元素上,让父元素去代替监听动作;原理是事件冒泡
  2. 事件捕获和事件冒泡的执行顺序:先捕获后冒泡
  3. 事件监听 使用 addEventListener 实现

什么是Event LOOP(事件循环)

  1. 事件循环是javascript的执行机制(先同步后异步,异步先微任务后宏任务)
  2. 常见的宏任务:整段代码script,setTimeout,setInterval
  3. 常见的微任务:promise,process.nextTick()

垂直局中的几种实现方法

  1. 使用flex方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <style>
    .name {
    width: 100px;
    height: 100px;
    border: 1px solid #a71111;
    }
    body{
    margin: 0;
    height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;
    }
    </style>
    <div class="name"></div>
  • 定位实现
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <style>
    .name {
    width: 100px;
    height: 100px;
    border: 1px solid #a71111;
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    margin: auto;
    }
    body{
    margin: 0;
    height: 100vh;
    }
    </style>
    <div class="name"></div>
    <script>
  • 定位实现+transform
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<style>
.name {
width: 100px;
height: 100px;
border: 1px solid #a71111;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
body {
position: relative;
margin: 0;
height: 100vh;
}
</style>
<div class="name"></div>
  • 使用定位+margin方法实现
1
2
3
4
5
6
.name{
position: absolute;
top: 50%;
left: 50%;
margin: -50px 0 0 -50px;
}
  • 使用text-align+line-height

  • 使用grid方法

1
2
3
4
5
6
body {
margin: 0;
height: 100vh;
display: grid;
place-items: center;
}
  • 定位+calc方法

get,post的区别

  1. get一般用来向服务器获取数据,而post则是向服务器发送数据
  2. get请求的时候是将请求的信息都暴露在url上,而post是将数据都放到body中
  3. get地址可以被缓冲,而post不能
  4. get请求长度是有限制的,而post请求的长度是没有限制的

localstorage,sessionStorage,cookie的区别

  • 它们都是存储到客户端
  1. cookie数据始终在同源的http请求中携带(即使不需要), 即cookie在浏览器和服务器间来回传递, 而sessionStorage和localStorage不会自动把数据发送给服务器, 仅在本地保存.cookie数据还有路径(path)的概念, 可以限制cookie只属于某个路径下
  2. 存储大小限制也不同, cookie数据不能超过4K, 同时因为每次http请求都会携带cookie、所以cookie只适合保存很小的数据, 如会话标识.sessionStorage和localStorage虽然也有存储大小的限制, 但比cookie大得多, 可以达到5M或更大
  3. 数据有效期不同, sessionStorage: 仅在当前浏览器窗口关闭之前有效; localStorage: 始终有效, 窗口或浏览器关闭也一直保存, 因此用作持久数据; cookie: 只在设置的cookie过期时间之前有效, 即使窗口关闭或浏览器关闭
  4. 作用域不同, sessionStorage不在不同的浏览器窗口中共享, 即使是同一个页面; localstorage在所有同源窗口中都是共享的; cookie也是在所有同源窗口中都是共享的

UDP和TCP的区别

  • tcp是面向连接的(需要进行三次握手和四次挥手),udp是面向无连接
  • tcp是面向字节流的,udp是面向数据报的
  • tcp的请求是有顺序的
  • tcp相较udp是比较可靠的,不会出现丢包的情况
  • udp具有较好的实时性,比如直播,实时转播画面

相对定位和绝对定位

  • 相对定位(relative):相对定位是相对于元素在文档中的初始位置,元素会占据原来的位置
  • 绝对定位(absolute):绝对定位是相对于元素最近的已定位的祖先元素

ES6中新特性(数组方法)

  1. let,const 块级作用域
  2. 模板字符串
  3. 解构赋值
  4. 箭头函数
  5. 扩展运算符
  6. Promise
  7. 新增对象方法,字符串方法,数组方法
    • 对象新增:Object.assign,Object.is方法
    • 字符串新增:includes,repeat,padStart,padEnd
    • 数组新增:of,from,find,findIndex,copyWithin,fill,includes,flat

数组方法改变原数组和不改变原数组

  • 改变原数组:
    • splice:有删除插入替换功能,第二个参数为 要改变的数组长度
    • pop:删除数组尾部的一个元素,返回删除的元素值
    • shift: 删除头部的一个元素,返回删除的元素值
    • copyWithin:不会改变数组的长度但是会改变数组本身的内容,参数(目标位置,start,end)
    • fill: 会改变原数组,参数(value,start,end)
    • push: 会改变原数组,返回数组的长度
  • 不会改变原数组的方法
    • concat:方法将两个数组进行合并处理,返回一个新的数组
    • filter: 返回一个新的数组,数组长度也可能发送变化
    • map:返回一个新的数组,数组的长度不变
    • find: 返回符合条件的第一个元素的值
    • findIndex: 返回符合条件的第一个元素的索引值
    • slice: 用于截取数组返回一个新的数组

Promise的概念、作用、以及实际运用

手写一个Object.create

1
2
3
4
5
function fn(obj){
const fn = function(){}
fn.prototype = obj
return new fn()
}

1.创建一个临时性的构造函数
2.将传入的对象作为这个构造函数的原型
3.最后返回了这个临时类型的一个新实例。

判断类型的方法

  • typeof
  1. 基本数据类型可以使用 typeof 的方法去判断
  2. typeof null 为:Object
  3. typeof NANnumber
  4. 对于创建的对象一般返回 Object
  • 使用instanceof对已知的对象类型进行判断
  • 使用object原型方法toString,返回类似于 [Object Object] 形式的值,使用切割的方法将类型分割出来

前端模块化

  1. CommonJs(典型代表:node.js早期):
    它通过 require 来引入模块,通过 module.exports 定义模块的输出接口
    这种模块加载方案是服务器端的解决方案,它是以同步的方式来引入模块的
    因为在服务端文件都存储在本地磁盘,所以读取非常快,所以以同步的方式加载没有问题
    但如果是在浏览器端,由于模块的加载是使用网络请求,因此使用异步加载的方式更加合适

  2. AMD(典型代表:require.js):
    这种方案采用异步加载的方式来加载模块,模块的加载不影响后面语句的执行
    所有依赖这个模块的语句都定义在一个回调函数里,等到加载完成后再执行回调函数。require.js 实现了 AMD 规范

  3. CMD(典型代表:sea.js):
    这种方案和 AMD 方案都是为了解决异步模块加载的问题,sea.js 实现了 CMD 规范
    它和require.js的区别在于模块定义时对依赖的处理不同和对依赖模块的执行时机的处理不同

  4. ES6 Module:
    ES6 提出的方案,使用 import 和 export 的形式来导入导出模块