gz面试笔记

105 篇文章 0 订阅
12 篇文章 0 订阅

字节

一面

1. 什么是协商缓存与强缓存;

详情>

2. 讲讲客户端存储的方式有哪些;

详情>

3. 如何防止脚本获取cookie;

详情>

4. 讲讲状态码301/302/307的区别;

详情>

5. 讲讲csrf、xss网络攻击;

详情>
Content Security Policy>

6. 如何解决前端跨域;

详情>

7. js 事件循环模型;

详情>

8. 实现模板字符串;
	function parseString(str, obj) {
	  Object.keys(obj).forEach(key => {
	    str = str.replace(new RegExp(`{{${key}}}`,'g'), obj[key]);
	  });
	  return str;
	}
	const str = "我的name是{{name}},{{name}}很厉害,才{{age}}岁";
	const obj = { name: "kangkang", age: "15" };
	console.log(parseString(str, obj));
9. 讲讲vue双向绑定原理;

详情>

10. 讲讲vue的计算属性为什么说是无害的;

这个无害是相对于watch和methods,计算属性能减少性能的损耗

我们可以将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是完全相同的。然而,不同的是计算属性是基于它们的依赖进行缓存的。计算属性只有在它的相关依赖发生改变时才会重新求值。这就意味着只要依赖的data还没有发生改变,多次访问计算属性会立即返回之前的计算结果,而不必再次执行函数。

而方法是每次都会执行, watch在依赖的data没有改变也会执行。

11. 讲讲盒子模型;

详情>

二面

1. 算法

假设按照升序排序的数组在预先未知的某个点上进行了旋转。(例如,数组[0,1,2,4,5,6,7]可能变为[4,5,6,7,0,1,2])。搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回-1。你可以假设数组中不存在重复的元素。你的算法时间复杂度必须是O(logn)级别。

2. 代码
      async function async1() {
        console.log('async1start')
        await async2()
        // 等所有的同步任务执行完才会执行异步任务
        console.log('async1end')
      }
      async function async2() {
        console.log('async2')
      }
      console.log('scriptstart')
      setTimeout(function() {
        console.log('setTimeout')
      }, 0)
      async1()
      new Promise(function(resolve) {
        console.log('promise1')
        resolve()
      })
        .then(function() {
          console.log('promise1then')
          return 'promise1end'
        })
        .then(res => {
          console.log(res)
        })
        .then(res => {
          console.log(res)
        })
      console.log('scriptend')
//打印如下:
scriptstart
async1start
async2  //同步任务
promise1
scriptend
async1end // await会等所有同步任务执行完后,才继续执行后面的任务
promise1then //
promise1end
undefined // 前面没有return 则默认返回undefined
setTimeout // promise.then里面是微任务,promise.then.then还是微任务,宏任务会等所有微任务执行完再执行。
3. BFC

详情->

4. 浏览器渲染流程

详情->

5. 手写节流

详情->

6. 性能优化webpack打包过程

详情->

speed-measure-webpack-plugin插件

webpack (多进程打包) thread-loader

7. HTTP2特性浏览器缓存

详情->

8. 最近学什么前端技术

node.js react.js 数据大屏 前端埋点

三面:

1. 自我介绍;

个人技术栈,呆过哪些公司,简单介绍下做过什么项目。过往经历,最近工作,最近项目。

2. 项目;

五洲行-机票旅游订票平台
应用魔方-低代码开发平台
数字清关平台-清关数据大屏-前端埋点系统

3. 算法

现有一个含有字符串的数组,形如:[“ab”,“c”,“ab”,“d”,“c”]要求将其中出现的重复字符串,依次添加上数字序号,如:[“ab1”,“c1”,“ab2”,“d”,“c2”]
要求:

  • 1.没有重复出现的字符串不处理;
  • 2.仅对相同的一组字符串依次添加序号,而不是共用一组序号;
  • 3.保持原数组顺序;
function getSameNumber(arr) {
  let oneArr = [], //包含所有key的数组,
    sameArr = {} // 重复项的对象,如:{ab: [2, 6, 8]}

  // 获取重复项组成的数组,但是重复项都会失去他们的第一项
  arr.forEach((item, index) => {
    if (!oneArr.includes(item)) {
      oneArr.push(item)
    } else {
      !sameArr[item] && (sameArr[item] = [])
      // 保存下标,因为下标唯一,可以作为后面的匹配的参照
      sameArr[item].push(index)
    }
  })
  //补全重复项的第一项,找到重复项出现在arr的第一个元素的下标,补全到重复项的开头
  for (let keys in sameArr) {
    sameArr[keys].unshift(arr.indexOf(keys))
  }

  console.log(oneArr, sameArr)
  for (let keys in sameArr) {
    for (let i = 0; i < arr.length; i++) {
      for (let i1 = 0; i1 < sameArr[keys].length; i1++) {
        // 通过匹配的下标找到对应参数
        if (i == sameArr[keys][i1]) {
          arr[i] = arr[i] + (i1 + 1)
        }
      }
    }
  }
  console.log(arr)
}
getSameNumber(['ab', 'c', 'ab', 'd', 'c', 'c', 'ab', 'c', 'ab', 'd', 'c', 'c'])
4. 你的优缺点;

慢热,学习热情高

5. 你有什么想问我的

团队规模,目前团队技术栈,前端是全栈开发吗

笔试

1. 以下输出
function Person() {}
let p = new Person()

//{constructor: ƒ}
console.log(p.__proto__) 

//{constructor: ƒ}
console.log(Person.prototype)

//{constructor: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ...}
console.log(p.__proto__.__proto__)

//ƒ Object() { [native code] }
console.log(p.__proto__.__proto__.constructor) 

 //{constructor: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ...}
console.log(p.__proto__.__proto__.constructor.prototype)

//ƒ Object() { [native code] }
console.log(p.__proto__.__proto__.constructor.prototype.constructor)

//ƒ Function() { [native code] }
console.log(p.__proto__.__proto__.constructor.prototype.constructor.constructor)

//ƒ Function() { [native code] }
console.log(p.__proto__.__proto__.constructor.prototype.constructor.constructor.constructor)
2. 有如下布局,问 first-child last-child 宽度分别是多少
.container {
    display: flex;
    width: 600px;
    height: 300px;
}
.a {
    width: 500px;
    flex-shrink: 1;
}
.b {
    width: 400px;
    flex-shrink: 2;
}

很明显,a.width + b.width已经大于父元素的宽度, flex-shrink会让子元素进行压缩,以适应父元素的宽度,值越大压缩的越严重。

  • a + b 超出父元素宽度 为 500 + 400 - 600 = 300
  • a压缩后的宽度 = a的宽度500 - a需要压缩的宽度【300*(500 * 1/(500 * 1 + 400 * 2))=115
    • 即 a = 500 - 115 = 385
  • b压缩后的宽度 = b的宽度400 - b需要压缩的宽度【300*(400 * 2/(500 * 1 + 400 * 2))】 = 185
    • 即 b = 400 - 185 = 215

知识点>

3. 回答以下代码运行结果,并详细描述 node 事件循环过程
setImmediate(() => {
  console.log(1);
});
setTimeout(function(){
  console.log(2);
});
new Promise(function(resolve){
  console.log(3);
  resolve();
  console.log(4);
}).then(function(){
  console.log(5);
});
console.log(6);
process.nextTick(function(){
  console.log(7);
});
console.log(8);
// 3 4 6 8 5 7 1 2 

  • 微任务: Promise,process.nextTick
  • setImmediate优先于setTimeout执行。
4. 自己实现一个 Promise.all
// 输入不仅仅只有Array
function promiseAll (args) {
  return new Promise((resolve, reject) => {
    const promiseResults = [];
    let iteratorIndex = 0;
    // 已完成的数量,用于最终的返回,不能直接用完成数量作为iteratorIndex
    // 输出顺序和完成顺序是两码事
    let fullCount = 0;
    // 用于迭代iterator数据
    for (const item of args) {
      // for of 遍历顺序,用于返回正确顺序的结果
      // 因iterator用forEach遍历后的key和value一样,所以必须存一份for of的 iteratorIndex
      let resultIndex = iteratorIndex;
      iteratorIndex += 1;
      // 包一层,以兼容非promise的情况
      Promise.resolve(item).then(res => {
        promiseResults[resultIndex] = res;
        fullCount += 1;
        // Iterator 接口的数据无法单纯的用length和size判断长度,不能局限于Array和 Map类型中
        if (fullCount === iteratorIndex) {
          resolve(promiseResults)
        }
      }).catch(err => {
        reject(err)
      })
    }
    // 处理空 iterator 的情况
    if(iteratorIndex===0){
      resolve(promiseResults)
    }
  }
  )
}
if (!Promise.all) Promise.all = promiseAll;
5. 已知两个单向链表某个节点相同,现在给出两个链表的头节点,请找出两个链表相同的节点

四面

1.不定宽高的元素如何水平垂直居中?

<div class='content'>
    <div class='son'>
        内容区
    </div>
</div>

// 方法1绝对定位
.content{
    	position: relative; //重点
	background:black;
	width:100px;
	height:200px;
        .son{
            position:absolute;
            left: 50%;
            top: 50%;
            transform: translate(-50%,-50%);
            background:red;
        }
    }



// 方法2 flex
.content{
	width:100px;
	height:200px;
	display: flex;
	justify-content: center;
    	align-items: center;
	background:black;
        .son{
            background:red;
        }
    }
2.有没有了解过 vue 源码?

有看过核心源码,具体包括以下几个部分:

  • 响应式
  • diff算法
  • vnode
  • parse函数
3.知道 vue 的生命周期吗?

详情>

4.知道 vue 的双向绑定如何实现的吗?

详情>

5. vuex

5-1. 详情>

5-2. 哪些情况用 vuex ?

5-3. 场景:登录,注册(需要根据登录用户信息来展示菜单和用户名)

  • 多个视图依赖于同一状态。
  • 频繁跨组件间通信

5-4. 除了 vuex ,还有哪些组件间通信方式?详情>

5-5. 你是如何处理兄弟组件间通信的?

  • vuex
  • eventBus
  • localStorage,sessionStorage
6.有使用过 ES6 的 decorator 吗?

decorator相当于一个高阶函数,接收一个函数,返回一个被装饰后的函数。

详情>

7.有了解过 express 源码吗?

没有

8.有自己写过 express 中间件吗?

没有

9.有了解过 webpack 源码吗?

没有

10.浏览器跨域的方式有哪些?

协议,域名,端口不同即跨域

详情>

11.http://ip:port 与指向该 ip:port 的域名是同域吗?

一个ip可以对应多个域名

但域名和对应的ip是不同域的

11.有做过哪些首屏性能优化吗?

为什么要做首屏优化:

首屏时间的快与慢,直接影响到了用户对网站的认知度。
所以首屏时间的长短对于用户的滞留时间的长短、用户转化率都尤为重要。

详情>

12.标准盒模型与怪异盒模型

详情>

17.有一个单向链表,奇数位升序,偶数位降序,请将它转换为一个整体升序的单向链表。

不知道

18.B/S C/S 架构的定义是什么?

详情>

image.png

22.Node 架构是怎样的?

详情>

23. 一句话解释什么是原型

原型属于对象的一个属性,原型让原型链上的对象能继承属性。

  • 封装
  • 继承
  • 多态
24.你为项目做了哪些优化?

项目层面,业务层面,代码优化,webpack优化,重点是从自己出发

精简代码:

1. 封装了一些列的全局弹窗,例如:导出excel,需要弹窗显示excel中那几行导出失败,并且可以复制失败的行id。之前是单独写在每个文件中。

2. 抽离了口岸列表,并且提供可配置的json,之前是每个菜单渲染自身的口岸数量,并且口岸数据写在当前页面,后面改成了封装了口岸按钮组组件,暴露给每个菜单使用。

详情>

25. webpack import 打包后的结果是怎样的?

详情>

26.懒加载加载的的时机是什么时候

发生在执行(import(‘xxModule’))的时候,它会单独打成一个包,采用动态加载的方式,具体过程:当用户触发其加载的动作时,会动态的在head标签中创建一个script标签,然后发送一个http请求,加载模块,模块加载完成以后自动执行其中的代码,主要的工作有两个,更改缓存中模块的状态,另一个就是执行模块代码。

27.webpack怎么加载的?

webpack的打包构建流程>

深入浅出 Webpack手册—重点>

28.tree shaking 是怎样实现的?

详情>

29.为什么 treeshaking 只能用于 ES6 模块?

为什么tree-shaking是最近几年流行起来了?而前端模块化概念已经有很多年历史了,其实tree-shaking的消除原理是依赖于ES6的模块特性。

ES6 module 特点:

  • 只能作为模块顶层的语句出现
  • import 的模块名只能是字符串常量
  • import binding 是 immutable的

ES6模块依赖关系是确定的,和运行时状态无关,可以进行可靠的静态分析,这就是tree-shaking的基础。

所谓静态分析就是不执行代码,从字面量上对代码进行分析,ES6之前的模块化,比如我们可以动态require一个模块,只有执行后才知道引用的什么模块,这个就不能通过静态分析去做优化。

这是 ES6 modules 在设计时的一个重要考量,也是为什么没有直接采用 CommonJS,正是基于这个基础上,才使得 tree-shaking 成为可能,这也是为什么 rollup 和 webpack 2 都要用 ES6 module syntax 才能 tree-shaking。

30.treeshaking发生在哪个阶段?

在代码压缩优化工具uglify,压缩代码的时候进行的。

31.多进程打包是怎么做的?

详情>

32.为什么选择使用 ES ?

不会

33.pdf 解析是怎样做的?

详情>

34.描述一下项目的数据库设计

用的是mongodb,前端大概要什么结构的字段,就设计什么结构,父子结构的通过id关联。

35.未来的职业规划是什么?

全栈工程师,资深工程师,当前在职场经验丰富和能力提成后也能做技术管理

36.为什么想做全栈?

能更好的理解项目,能在技能上得到更大的提升,提升工作效率,提升自身的价值。

Fordeal

一面

1、项目相关

做了哪些东西,亮点有什么。

2、webview内嵌网页,在发布之后,还会出现旧的页面,也就是资源未更新。

不会

3、滚动穿透如何实现

详情>

4、URL请求过程

详情->

5、项目上遇到困难,怎么解决的

长列表优化,时间分片

低代码平台的拖拽嵌套,拖拽时机

6、现场手撕代码
add(1)(2)(3)(4) => 输出10

这个题目主要考察是否掌握了函数柯里化

什么是柯里化呢?

柯里化(Currying)是把接受多个参数函数转变为接受一个单一参数的函数,它会返回一个函数去处理剩下的参数

首先我们分析累加的流程

// 累加的流程就是把每个参数都加起来
function add (a, b, c, d) {
    return a + b + c + d
}
add(1, 2, 3, 4) // 10

最生硬的解法

function add (a) {
	return function (b) {
		return function (c) {
			return function (d) {
				return a + b + c + d
			}
		}
	}
}
add(1, 2, 3, 4) //10

这个思路是没问题了,返回一个函数接收了剩余参数,但太死了,假如要累加100呢。

柯里化的解法

// 累加逻辑,它作为另一个函数的入参
function add (...args) { 
    console.log(args) //[1, 2, 3, 4, 5]
    return args.reduce((a, b) => a + b)
}

function currying (fn) {
	let args = [] //该变量形成闭包,后续函数都维持对它的引用
	return function _c (...newArgs) { // 将每个()里面的参数都转换成数组的格式,例如(4,5)=>[4,5]
		if (newArgs.length) {
			args = [ //合并已经执行到的入参
				...args,
				...newArgs
			]
			return _c
		} else { //没有参数了,说明要执行函数了
			return fn.apply(this, args)
                        // 当然,这里也可以直接写return args.reduce((a, b) => a + b)
                        // 或者这样写:return fn(...args) 结果都是一样的
                        // 之所以是apply,是因为apply接受数组形式的参数,并且可以绑定this,保证add里面的this跟定义时的this一致
		}
	}
}
let addCurry = currying(add)
addCurry(1)(2)(3)(4)()//10
addCurry(1)(2)(3)(4,5)()//10

非柯里化的解法

var add = function (m) {
//一开始,m=3,返回temp(n),接收4,然后返回add(3+4),执行temp(n),接着接收5,返回add(7+5)
    function temp(n) {
        return add(m + n);
    }
    temp.toString = function () {
        return m;
    }
    return temp;
};
 
 
add(3)(4)(5).toString(); // 12

或者:

function add(x) {
    var sum = x; //闭包,保持都它的引用
    function tmp(y) {
        if(!y){ //最后一项没有参数,就需要将累加的参数返回了
            return sum;
        }else{
            sum = sum + y;
            return tmp;
        } 
    };
    return tmp;
}
console.log(add(1)(2)(3)()); //6
7. 排平数组

详情>

8.实现一个函数,参数是wait 等待时间 func 需要执行的函数 times 次数。等待wait时间然后再执行func,这样执行times次。

详情>

映客直播

1.常用的ES6特性

let / const、箭头函数、解构赋值、默认参数、扩展运算符、类、字符串模板、数组新增的高阶函数、Promise

2.JavaScript的数据类型

Object Null Number Undefined String Boolean Symbol BigInt

3. 箭头函数和普通函数的区别——this指向

详情>

4.原型
var webName = "long"; 
// Pig的构造函数 
function Pig(name, age) { 
    this.name = name; 
    this.age = age; 
} 

// 创建一个Pig的实例,小猪佩奇 
var Peppa = new Pig('Peppa', 5); //{name: 'Peppa', age: 5}

普通对象不存在prototype属性

console.log(Pig.prototype) //{constructor: ƒ} 函数的显式原型是个对象
console.log(Peppa.prototype) // undefined  实例不存在prototype属性,只有函数才有
  • 第一层隐式原型都指向它的同类型
  • 函数的显式原型是一个对象
  • 对象不存在显式原型
  • 函数的显式原型 等于 实例的隐式原型
console.log(Pig.__proto__) //ƒ () { [native code] } 第一层隐式原型都指向它的同类型,函数指向函数
console.log(Pig.prototype) // {constructor: ƒ}    函数的显式原型是一个对象
console.log(Peppa.__proto__) // {constructor: ƒ}  隐式原型都指向它的同类型,对象指向对象

console.log(Pig.prototype.prototype) //undefined 对象不存在显式原型


Pig.prototype === Peppa.__proto__ //true 
Pig.__proto__ === Function.prototype //true 

构造函数

console.log(Pig.__proto__) //ƒ () { [native code] } 第一层隐式原型都指向它的同类型,函数指向函数
console.log(Pig.constructor) // Function() { [native code] } 函数的构造函数就是Function
//ƒ Pig(name, age) { this.name = name; this.age = age;} 当前实例的构造函数就是Pig本身
console.log(Peppa.constructor) 
console.log(Pig.prototype) // {constructor: ƒ}    函数的显式原型是一个对象
console.log(Pig.prototype.constructor) // Pig本身
Pig.prototype.constructor === Pig //true

拓展

console.log(Pig.__proto__) //ƒ () { [native code] } 第一层隐式原型都指向它的同类型,函数指向函数
//{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
console.log(Pig.__proto__.__proto__)
console.log(Pig.__proto__.__proto__.__proto__) //null 找到顶层了
console.log(Pig.__proto__.__proto__.__proto__.__proto__) //报错,Cannot read properties of null (reading '__proto__')

Peppa.__proto__//{constructor: ƒ}
Peppa.__proto__.__proto__//{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
Peppa.__proto__.__proto__.__proto__//null
4.HTTP缓存

强缓存和弱缓存详情>

5.浏览器本地存储及其区别

详情>

6.SSR原理

详情

7.Vue的生命周期

详情>

8.组件渲染的错误监听

main.js中,可以监听钩子函数中的错误

//如果在组件渲染时出现运行错误,错误将会被传递至全局 Vue.config.errorHandler配置函数 
Vue.config.errorHandler=function(){ 
    //利用这个钩子函数来配合错误跟踪服务是个不错的主意。 ... 
}

组件中

  created() {
    ninini //被errorHandler捕获到错误
  }
9.项目的优化

可以在简历上体现

从体验,代码,打包,请求速度等多方面来谈

去除大量重复文件,提取可以复用文件,建立前端埋点日志,等等

详情>

佰锐科技

1. SPA页面和传统页面的区别

详情>

2.数组和对象的api

数组

1.  concat: ƒ concat()
1.  every: ƒ every()
1.  filter: ƒ filter()
1.  find: ƒ find()
1.  findIndex: ƒ findIndex()
1.  flat: ƒ flat()
1.  forEach: ƒ forEach()
1.  includes: ƒ includes()
1.  indexOf: ƒ indexOf()
1.  join: ƒ join()
1.  keys: ƒ keys()
1.  lastIndexOf: ƒ lastIndexOf()
1.  length: 0
1.  map: ƒ map()
1.  pop: ƒ pop() //pop() 方法用于删除数组的最后一个元素并返回删除的元素。【修改原数组】
1.  push: ƒ push() //push() 方法可向数组的末尾添加一个或多个元素,并返回新的长度。【修改原数组】
1.  reduce: ƒ reduce()
1.  reverse: ƒ reverse()//reverse() 方法用于颠倒数组中元素的顺序。【修改原数组】
1.  shift: ƒ shift() //shift() 方法用于把数组的第一个元素从其中删除,并返回第一个元素的值。【修改原数组】
1.  slice: ƒ slice() //slice() 方法可从已有的数组中返回选定的元素。【不修改原数组】
1.  some: ƒ some()
1.  sort: ƒ sort()//sort() 方法用于对数组的元素进行排序。【修改原数组】
1.  splice: ƒ splice()//splice() 方法用于添加或删除数组中的元素。【修改原数组】
1.  toLocaleString: ƒ toLocaleString()
1.  toString: ƒ toString()
1.  unshift: ƒ unshift()//unshift() 方法可向数组的开头添加一个或更多元素,并返回新的长度。【修改原数组】
1.  values: ƒ values()

对象

1.  hasOwnProperty: ƒ hasOwnProperty()
1.  isPrototypeOf: ƒ isPrototypeOf()
1.  toLocaleString: ƒ toLocaleString()
1.  toString: ƒ toString()
1.  valueOf: ƒ valueOf()
2. 路由的两种模式:history和hash,hash的原理是什么

详情>

3. 前端项目权限控制

详情>

3. vue的生命周期

详情>

4. slot了解吗?有哪几种类型

详情>

5. 为什么vue3用了proxy性能更好
6. proxy的缺点是什么
7. 为什么proxy比Object.defineProperty更快
8. vue3打包按需引入是如何做到的,为什么Vue2没有做到
9. vue项目中你有做哪些优化(注意是vue项目,真实使用了哪些优化,不要说你知道哪些)
10. 有没有了解资源是缓存策略?html,css,js怎么设置
11. 自定义组件的双向绑定怎么写

有两种方式:

1.采用sync修饰符

sync说明:当一个子组件改变了一个 prop 的值时,这个变化也会同步到父组件中所绑定

父组件:

<button @click="showAddress">显示弹窗</button>
<receivingModal  :showModal.sync="showModal" ></receivingModal>
...
data: {
    return {
        showModal : false
    }
},
methods:{
    addAddressModal(){
     	this.showModal = true
    },
}

子组件

<Modal title="添加商户配置" v-model="visible">
...
</Modal>


props: {
    showModal: {
        type: Boolean,
        default: false,
},
//利用computed,读取的时候读传递进来的值,修改的时候,直接触发修改父组件的传递进来的属性
computed: {
    visible: {
      set(val) {
        //直接更新父组件对应的属性,省去了父组件接收的事件
        return this.$emit('update:showModal', val)
      },
      get() {
        return this.showModal
      }
    }
},

2.采用v-model

父组件:

<PortBtnTab v-model="currentCustom"></PortBtnTab>
data() {
    return {
      currentCustom: 0,
    }
},

子组件:

<template>
      <Button :key="index" @click="handleTabClick(index)">
        {{ item.name }}
      </Button>
</template>

<script>
export default {
  model: {
    prop: 'currentCustom', //对应父组件v-model的key  currentCustom
    event: 'change' // 随便命名事件,对应下面$emit即可
  },

  props: {
    currentCustom: { // 接收
      type: Number,
      required: true
    },
  },

  methods: {
    handleTabClick(index) {
    //事件和上面的event保持一致,index值会传递到父组件的currentCustom属性上
      this.$emit('change', index)
    },

  }
}
</script>


12. 如何把一个请求封装成promise
import axios from "axios"
import Auth from "./auth"
import Lang from "./lang"
import store from "@/vuex/store"
import { TIMEOUT, baseUrl } from "@/utils/env"
import {name, version} from '../../package.json'
const http = axios.create({
  baseURL: baseUrl,
  timeout: TIMEOUT
});

http.interceptors.request.use(config => {
  const token = Auth.getToken();
  token && (config.headers["Authorization"] = token);
  config.headers["SourceFront"] = name
  config.headers["SourceFrontVersion"] = version
  config.headers["Accept-Language"] = Lang.getLang()
  return config;
}, error => {
  Promise.reject(error);
});

http.interceptors.response.use(response => {
  const data = response.data.data;
  if (response.data) {
    return Promise.resolve(response);
  }
  if (code != 200) {
    return Promise.reject(response.data);
  }
  return response.data; //会自动转换为:Promise.resolve(response.data)
}, error => {
  if (error.code === "ECONNABORTED") {
    return Promise.reject({
      message: "请求超时"
    })
  } else {
    return Promise.reject(error);
  }
});

export default http;

13. 说下vue的虚拟dom和diff算法
14. 构建虚拟dom树时用了什么算法
15. diff对比时用了什么算法
16. 深度优先算法和广度优先算法
5. 原型链 以及如何判断属性是否为对象自己的?

详情>

var b = {name:1}
b.hasOwnProperty('name') // true

原型链:

所有的JS对象都有一个__proto__属性,指向它的原型对象。当试图访问一个对象的属性时,如果没有在该对象上找到,它就会搜寻该对象的原型,以及该对象的原型的原型,层层向上查找,直到找到一个名字匹配的属性或到达原型链的末尾null,这样的一种查找关系,就称为原型链。

6.vuex的action和mutation

详情>

奥凯信息服务公司

1.除了业务方面,前端可以做哪些优化?

其它答案有讲

2.你做了前端哪些优化?

综合其它答案,讲一下自己项目中实际用到的

3.vue的diff算法

详情>

4.vue的数据双向绑定

其它答案有讲

5. Set、Map、Symbol数据结构需要学习

详情,基础知识>

详情,实践>

详情,实践>

6.原型链

其它答案有讲

玄武科技-D轮公司

1. 重排重绘的区别以及优化方法

详情>

2. 301和302的区别以及使用场景

详情>

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

详情>

4. BFC以及触发BFC

详情->

5. get和post的区别

误区: 我们经常说get请求参数的大小存在限制,而post请求的参数大小是无限制的。

实际上HTTP 协议从未规定 GET/POST 的请求长度限制是多少

对get请求参数的限制是来源与浏览器web服务器,浏览器或web服务器限制了url的长度。为了明确这个概念,我们必须再次强调下面几点:

  • HTTP 协议 未规定 GET 和POST的长度限制
  • GET的最大长度显示是因为 浏览器和 web服务器限制了 URI的长度
  • 不同的浏览器和WEB服务器,限制的最大长度不一样
  • 要支持IE,则最大长度为2083byte,若只支持Chrome,则最大长度 8182byte
  • get请求类似于查找的过程,用户获取数据,可以不用每次都与数据库连接,所以可以使用缓存。
  • post不同,post做的一般是修改和删除的工作,所以必须与数据库交互,所以不能使用缓存。因此get请求适合于请求缓存。
6. vue组件的通讯方式

详情>

7. 手撕new操作 (new 构造函数)
// 新建构造函数--用来new的函数
// 构造函数是一种特殊的方法:用来在创建对象时为对象成员变量赋初始值
function Dog(name){
  // 这里的this指向window,所以下面才需要重新将this指向自身,这样才能将属性挂载成功
  this.name = name
  this.say = function() {
    console.log("my name is" + this.name);
  }
}

// 手写new函数
function _new(fn,...arg){
  const obj = {};
  // 将构造函数的原型挂载到新对象上
  Object.setPrototypeOf(obj, fn.prototype)
  // 将this指向新对象,执行构造函数
  let result = fn.apply(obj, arg);
  return result instanceof Object ? result : obj
}


// 验证
var dog = _new(Dog, "caigou")
dog.say();// my name is caigou
8. 手撕promise.all

详情>

9. 手撕防抖和节流

image.png

image.png

10. webpack打包过程特别慢咋办

优化打包流程,分包,懒加载,分析哪些包比较大,代码压缩

11. 给出一个字符串,找到重复次数最多的那个,并返回次数
12. 手撕快排,分析快排的过程以及使用场景
function quickSort(arr) {
      if (!Array.isArray(arr)) return;
      if (arr.length <= 1) return arr;
      var left = [], right = [];
      // 以中间位置为下标
      var num = Math.floor(arr.length / 2);
      // arr.splice(num,1)取下标为num的这个数(会改变原数组),结果是数组的形式,所以加[0]
      var numValue = arr.splice(num, 1)[0];
      for (var i = 0; i < arr.length; i++) {
        if (arr[i] > numValue) {
          right.push(arr[i]);
        } else {
          left.push(arr[i]);
        }
      }
      return [...quickSort(left), numValue, ...quickSort(right)]
    }

//测试
let arr = [1,44,6,77,3,7,99,12]
console.log(quickSort(arr))// [1, 3, 6, 7, 12, 44, 77, 99]
14. vue的v-for的key是怎么回事?diff的过程

详情>

16. vue的虚拟列表

虚拟列表其实是按需显示的一种实现,即只对可见区域进行渲染,对非可见区域中的数据不渲染或部分渲染的技术,从而达到极高的渲染性能。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Title</title>
    <style>
      .scroll-content {
        position: relative;
        overflow-y: scroll;
        box-sizing: border-box;
        border: solid 1px black;
      }
      .list {
        position: absolute;
        top: 0;
        left: 0;
      }
      .list li {
        border: solid 1px rgb(0, 89, 255);
        line-height: 30px;
        margin: 5px;
        padding: 5px;
        list-style: none;
      }
    </style>
  </head>
  <body>
    <!-- 滚动容器 -->
    <div id="app" class="scroll-content" ref="scrollContent" @scroll="scrollListener">
      <!-- 虚拟列表 -->
      <div ref="virtualList"></div>
      <!-- 可视列表 -->
      <ul class="list" ref="relList">
        <li v-for="val in showList">{{val}}</li>
      </ul>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
      new Vue({
        el: '#app',

        data() {
          return {
            bigList: [], //超长的列表数据
            itemHeight: 30, //每一列的高度
            showNum: 10, //显示几条数据
            start: 0, //滚动过程显示的开始索引
            end: 10 //滚动过程显示的结束索引
          }
        },

        computed: {
          // 实际显示的数据
          showList() {
            // 取最小的一项是为了防止下标超出
            return this.bigList.slice(this.start, Math.min(this.end, this.bigList.length))
          }
        },

        mounted() {
          // 模拟构造一个超长列表
          for (let i = 0; i < 1000000; i++) {
            this.bigList.push('列表' + i)
          }
          //计算滚动容器高度,即 显示的数量*每条的高度
          this.$refs.scrollContent.style.height = this.itemHeight * this.showNum + 'px'

          // 计算总的数据需要的高度,构造虚拟列表,用来撑开我们的容器的滚动条
          this.$refs.virtualList.style.height = this.itemHeight * this.bigList.length + 'px'
        },

        methods: {
          scrollListener() {
            //获取滚动高度
            let scrollTop = this.$refs.scrollContent.scrollTop

            //开始的数组索引
            this.start = Math.floor(scrollTop / this.itemHeight)

            //结束索引
            this.end = this.start + this.showNum

            //绝对定位对相对定位的偏移量,我们的实际数据也要滚动到相应的数据位置,这样能显示
            this.$refs.relList.style.top = this.start * this.itemHeight + 'px'
          }
        }
      })
    </script>
  </body>
</html>

详情>

解释下简易版的偏移量:

this.startOffset = scrollTop - (scrollTop % this.itemSize);

假设当前是滚动距离是 scrollTop: 250px 行高:100px,当前滚动的是介于第二行和第三行之间的第2.5行.如果不设置偏移量,那么视觉效果是 相应是内容变化了,但是没有滚动效果。为了有滚动效果,我就需要就加偏移量,当滚动到第2.5行我们偏移到第二行,滚动到3.5行偏移到第三行。 这样就能看到内容在往上滚动。

17. vue的router权限控制

步骤

  1. 创建路由白名单列表,任何情况都能访问,然后拿到用户的路由表,通过router.addRouetes动态组装用户完整的路由,需要注意的是404的路由需要放在路由表的最后面。
  2. 通过beforeEach做路由拦截,如果跳转的路由有token就存储在本地。
  3. 如果本地有token了,
    • 3-1. 此时跳转的路由是login说明是退出操作,去除用户信息。
    • 3-2. 如果是其他路由:
      • 3-2-1. 如果账号审核通过,获取app数据(包括该用户下的路由表)和appcode,然后去设置目标app,权限路由表,和左侧菜单列表,最后在跳转前去除路由参数,再跳转
      • 3-2-1.如果账号审核不通过, 跳转前去除路由参数,再跳转
  4. 如果本地没有token,如果路由再白名单,则跳转,否则跳转到首页。
  5. 左侧的菜单通过运行环境判断,如果是dev则取本地的mock数据,如果是fat则取返回的菜单数据

代码

import Vue from 'vue'
import HeroUI from 'gvt-ui-hero'
import App from './App.vue'
import router from './routers'
import store from './vuex/store'
import iView, { Message } from 'iview'
import VueBus from './utils/bus'
import Auth from './utils/auth'
import Lang from './utils/lang'
import './utils/filter'
import { i18n, setI18nLanguage } from '@/utils/i18n'
import * as filters from './filters'
import Viewer from 'v-viewer'
import 'viewerjs/dist/viewer.css'
import VueQuillEditor from 'vue-quill-editor'
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
import 'quill/dist/quill.bubble.css'
import VueCropper from 'vue-cropper'
// 角色信息
import { listRolesSlaveRolesByTenantId } from '@/api/auth'
// 权限控制
import Permissons from './utils/permisson'
Vue.use(VueCropper)
Vue.use(VueQuillEditor)
Vue.use(Viewer)
Vue.use(iView, {
  i18n: (key, value) => i18n.t(key, value)
})
Vue.use(HeroUI)
Vue.use(VueBus)
Vue.use(Permissons)

const cleanLangAndTokenQuery = (to, from, next) => {
  to.query.token && delete to.query.token
  if (to.query.lang) {
    delete to.query.lang
    if (from.path === to.path) {
      iView.LoadingBar.finish()
    }
    next({
      replace: true,
      path: to.path,
      params: to.params,
      query: to.query
    })
  } else {
    next()
  }
}

/**
 * 路径白名单
 *
 * 任意场景都能无阻碍访问
 */
const accessRoutePath = [
  '/login',
  '/forget',
  '/403',
  '/404',
  '/500',
  '/merchantRegister',
  '/registerInfo',
  '/changePassWordNotoken'
]
router.beforeEach((to, from, next) => {
  iView.LoadingBar.start()
  if (to.query.lang) {
    Lang.setLang(to.query.lang)
  } else {
    !Lang.getLang() && Lang.setLang()
  }
  setI18nLanguage(Lang.getLang())

  let state = localStorage.getItem('state')

  if (to.query.token) {
    Auth.setToken(to.query.token)
  }
  if (Auth.getToken()) {
    listRolesSlaveRolesByTenantId().then(response => {
      const roleList = response.data || []
      store.commit('SET_ROLE_LIST', roleList || [])
    })
    if (to.path === '/login' || to.path === '/') {
      //登录跳到官网  官网右上角后退,会退到系统里边
      // next({
      //   path: "/console"
      // });
      store.dispatch('Logout')
      next()
      iView.LoadingBar.finish()
    } else {
      if (store.getters.user.id === '') {
        if (state == 1) {
          store
            .dispatch('FetchUserData')
            .then(apps => {
              const appTarget = to.query.appcode ? to.query.appcode : ''
              store
                .dispatch('InitPermissionByApps', {
                  apps,
                  appTarget
                })
                .then(() => {
                  cleanLangAndTokenQuery(to, from, next)
                })
            })
            .catch(error => {
              Message.error(error.message)
              Auth.removeToken()
              if (error && error.source === 'action') {
                next({
                  path: `/${error.redirect}`
                })
              } else {
                //跳转到官网的登录页
                next({
                  path: '/login'
                })
              }
            })
        } else {
          cleanLangAndTokenQuery(to, from, next)
        }
      } else {
        cleanLangAndTokenQuery(to, from, next)
      }
    }
  } else {
    if (accessRoutePath.indexOf(to.path) > -1) {
      cleanLangAndTokenQuery(to, from, next)
    } else {
      //跳转到官网的登录页
      next({
        path: '/login'
      })
      iView.LoadingBar.finish()
    }
  }
})

// 注入全部过滤器
Object.keys(filters).forEach(key => {
  Vue.filter(key, filters[key])
})

router.afterEach(router => {
  iView.LoadingBar.finish()
})

new Vue({
  el: '#app',
  store,
  router,
  i18n,
  render: h => h(App)
})

19. 项目 懒加载的原理
  • 图片懒加载: 详情
  • 路由懒加载
  • 组件懒加载
20. 有没有做过组件库、脚手架工具之类的?

做过chorme浏览器的插件

易工品

1. vue3和vue2的区别

详情>

2. 你了解我们公司吗?——面试之前一定要了解好人家的公司!不然到嘴的鸭子飞了

先查下资料


有米科技

1. async和defer的区别

详情1>

详情2>

详情3>

2. performance的FCP是什么

浏览器的控制台–性能分析需要专门去看文章

3. JavaScript的数据类型

详情>

map ,set需要了解

详情>

4. commonjs的模块机制和es6模块机制

详情>

5. 浏览器支持的模块机制有哪些
6. UMD是啥
7. csrf攻击是什么以及如何预防

详情>

8. 深拷贝
/ 手写深拷贝函数
function deepClone(obj){
  if(obj == null){
    return null
  }
  if(obj instanceof RegExp){
    return new RegExp(obj)
  }
  if(obj instanceof Date){
    return new Date(obj)
  }
  var objClone = Array.isArray(obj) ? [] : {}
  for(let key in obj){
    if(obj.hasOwnProperty(key)){
    //如果还是对象,就递归
      if(obj[key] && typeof obj[key] === "object"){
        objClone[key] = deepClone(obj[key])
      }else{
        objClone[key] = obj[key]
      }
    }
  }
  return objClone
}

// 测试
var dog = {
  name: {
    chineseName:"狗"
  }
}
var newDog = deepClone(dog)
newDog.name.chineseName = "新狗"
console.log("newDog",newDog)//{ name: { chineseName:"新狗"}}
console.log("dog",dog)//{ name: { chineseName:"狗"}}
9. 中间人攻击是什么?

详情>

10. 浏览器的缓存机制

详情>

11. md5是什么?

详情>

12. 做过容灾吗?

没有

13. 项目部署的具体流程?

。。。

14. 有自己写过gitlab的yml文件吗?

.gitlab-ci.yml

include:
  - 'http://xxxx.yaml'

http://xxxx.yaml

stages: - build_app - build_image - test - deploy 
#--------------------------------------------------------- 
# 任务之前脚本 
#--------------------------------------------------------- 
before_script: - "git clone ssh://xxxx/cicd.git" - "chmod -R +x ./*" - "./cicd/common/v3/pre.sh" 
after_script: - "./cicd/common/v3/post.sh" 
#********************************************************* 
# gvt-apollo-frontend dev环境 
#********************************************************* 
#--------------------------------------------------------- 
# 编译打包阶段 
#--------------------------------------------------------- 
buildapp:gvt-apollo-frontend:dev: stage: build_app tags: - gitrunner-dev script: - "npm install && npm run build:dev && cd docker && ./build_app_post.sh" artifacts: paths: - ./dist/* only: refs: - develop changes: - ./src/**/* variables: - $ENV == "GVT-APOLLO-FRONTEND_DEV" #--------------------------------------------------------- 
# 构建镜像阶段 
#--------------------------------------------------------- 
buildimage:gvt-apollo-frontend:dev: stage: build_image tags: - gitrunner-dev script: - "cd docker && ./build_images.sh xx.1.138:5000 dev" only: refs: - develop changes: - ./src/**/* variables: - $ENV == "GVT-APOLLO-FRONTEND_DEV"

####15. docker部署SPA项目的劣处?

16. vue明明有数据劫持还要vdom 和 diff算法?
17. 常见的状态码

详情>

18. 有80多个HTTP请求,在非暴力的情况下,如何做到有序发送?(发布订阅模式)
19. 垂直居中布局如何实现

详情>

20. 了解过BFC

详情>

21. 开放性问题,有没有做过前端工程化,不限于脚手架、webpack、组件库等

有webpack对项目优化,有做过低代码平台,封装过一系列的默认组件(基于iview)

二面(顺序不一定,想不起来了)20min

1. 在团队承担的角色,有没有带过新人
2. BFF了解吗?为什么用Node.js作为BFF的技术栈
3. 为什么还要用Nuxt.js框架?
4. 跨端交流的例子

三面(顺序不一定,有HR的问题)30min

1. 职业规划
2. 别人如何评价你的性格
3. 缺点优点
4. 比同事好在哪里
5. 期望薪资
6. 还有很多,忘记了

bigo

一面:

1.https加密协议

详情

2.node worker,僵尸进程
3.ts中,error和unknown的区别
4.算法:
  • a.求五位数字中所有的回文数
  • b.实现函数add(1)(2)(3)参数之和
  • c.给定一个整数n,计算所有小于等于n的非负整数中数字1出现的个数。
    例:n=13,数字1出现在1,10,11,12,13中,共6次,返回6
    注意:不要使用转字符串的方式,时间/空间复杂度尽量低。
  • d.版本号如1.1.0,1.2.1,现有两个版本号a,b,判断a相对于b是否是一个新版本
5.重写console.log方法,实现每次打印结果之前增加一个打印时间戳
6.两列布局,左边固定宽度,右边是硬剩余的宽度

详情

二面:

1.babel如何编译

image.png

详情1》

详情2》

2.如何避免团队成员写ts使用any的坏习惯
3.commonJs和es6模块加载有什么区别,如果要同时支持使用,如何实现

详情>

4.单元测试如何测试
5.typescript的最佳实践
6.前端如何分层设计的
7.重新设计现有的这个项目,你会如何设计
8.各种团队实际操作问题
9.项目如何打包部署,有什么不好的地方,有没有推进去解决
10.为什么想跳槽
11.还有在面试其它公司吗
12.babel代码转换如何支持兼容性问题
13.如何实现打包分类
14.手写算法

三面:

1.tcp发送和接收包的情况,是一次性发,还是逐次发,如何排序,具体标志位是什么。
2.http 1.0、1.1、2.0分别解决什么问题,有什么特点

详情>

3.php项目移到node.js如何进行单元测试的保证,通过接口请求结果的幂等性验证
4.obj读取数据快的原因,js是怎么实现的

详情

5.手写算法
6.聊人生

cvte

一面:

a.自我介绍
1.目前情况(工作毕业,期望工作情况,offer情况,是否考虑,看什么书,加班,工作强度)
2.能够接受什么技术栈

常用的vue和react,node,小程序都可以,一些新技术也可以,flutter等

3.个人职业规划
4.期待薪资
5.数据结构

平时有用哪些数据结构,用在哪里

  • input数字输入框: 设置type为Number
  • props接收父组件的传参:设置类型type
  • array: 获取列表数组给数据初始化为空数组

详情

6.浏览器打开 baidu.com ,到页面完全显示,中间发生什么

(网络请求过程,页面渲染过程)

2.http2 有了解过吗,http1 1.1发展特点区别

详情1
详情2

3.浏览器缓存,强缓存和协商缓存
4.三次握手,4次挥手
5.XSS, CSRF
6.this的指向

详情>

2.es6新特性,箭头函数(什么情况不使用),proxy,装饰器,可选链,如何遍历一个对象

详情

箭头函数(什么情况不使用)

装饰器

可选链

如何遍历一个对象

3.MVVM,如何进行数据劫持
4.熟悉移动端适配方案

详情

5.闭包

详情

6.数据类型,如何判断

详情

7.setTimeout不能准时回调(浏览器和node区别)
8.如何实现new一个对象

详情

9.BFC
10. 工程化
11.性能优化
12.设计模式和设计法则
13.混合APP通信(APP是H5混合)
14.node(koa原理)
15.业务和团队推动能力
16.项目最难的地方
17.组件库有做抽取复用处理。

荔枝

1. async和defer的区别

详情1>

详情2>

详情3>

2.用css写出3种隐藏元素的方法
{ display: none; /* 不占据空间,无法点击  */ }\
{ visibility: hidden; /*  占据空间,无法点击  */ }\
{ position: absolute; top: -999em; /*  不占据空间,无法点击  */ }\
{ position: relative; top: -999em; /*  占据空间,无法点击  */ }\
{ position: absolute; visibility: hidden; /*  不占据空间,无法点击  */ }\
{ height: 0; overflow: hidden; /*  不占据空间,无法点击  */ }\
{ opacity: 0; filter:Alpha(opacity=0); /*  占据空间,可以点击  */ }\
{ position: absolute; opacity: 0; filter:Alpha(opacity=0); /*  不占据空间,可以点击  */ }*
3. 上面的方面有什么区别
4. 手撕防抖代码
5. 分清setTimeout和promise的执行时机
6. 从url到渲染页面题,尽可能详细。然后面试官会从每个阶段进行扩展性提问

详情

7.浏览器缓存机制

详情

8. 浏览器白屏

详情

9. 控制台的性能分析和调试

=======todo =======todo

9. 重排及其预防措施
10. 项目的亮点
11.HTTPS如何保证安全性

详情

12. TLS握手过程

详情

14.jsBrigdge底层通讯的原理

详情

15. vue和react的区别
16. 还有很多扩展性、讨论性的项目问题
17.项目亮点、难点
18.模块机制 CMD了解吗?
19.CMD和AMD的区别
20. 除了webpack之外的其他打包、构建工具还用过哪些
21.富文本编辑遇到了什么坑?
21. webpack的组件按需引入是webpack自己的吗?还是es6语法
22. 抛弃框架和ES6语法,现场手撕一个确认弹窗?写到一半让讲思路
23. webpack的分包如何实现?可以说下具体的配置吗?
23.常见的时间复杂度排序
23.如何做技术选型
23.CICD的流程?
23.机器如何部署的?
23.了解过集群吗?
23.如何区分国内、国外用户?
23.项目场景的假设
23.项目亮点、难点
23.抗压能力的体现

cvte

一开始就问项目,最难的地方,问的特别细

1.vue 和 react diff
1.react setstate 之后的流程
1.v8 GC 原理

详情》

1.闭包

详情>

1.你是如何做项目优化的
1.mvvm 理解
1.有学习什么新技术
1.请求20个接口都对第一屏内容很关键如何做优化
1.职业规划
1.豌豆思维
1.整个过程也是围绕项目问
1.如何优化项目构建,难点 ,sso 登录原理
1.讲讲 cicd 自动化部署
1.vue 数据劫持
1.vue 是如何进行收集依赖然后更新的
1.vue key 的作用 ,纯数组如何设置key
1.react setstate 什么时候是异步什么时候是同步
1.作为前端优势是什么

面试总结:
1、简历写的每个点都要经得起深挖
2、面试前一定要整理面试知识点和组织好语言,逻辑清晰,(关键单词好好记)反复练习
3、可以内推千万不要走平台

lineshop

1.微前端

-------参考qiankun

1.做什么性能优化(图片做了什么优化)
1.vue 数据劫持
1.ts
1.react setstate 同步异步
1.fiber 什么时候不能暂停任务
1.tree-sharking 副作用
1.闭包原理和应用(react哪里应用到)
1.可以隐藏 div 的所有方式 以及性能优化层面上考虑
1.es6 特别看好的特性(装饰器和proxy)
1.proxy 是否可以拦截函数被调用
1.vue 3 跟 react hook 相似的 api 对比
1.微任务有哪些
1.node eventloop 与浏览器的区别
1.首屏渲染优化

映客

一面:

1.讲一下 webpack5 的模块联邦的原理(公共依赖升级咋办)
1.webpack loader 和 plugin 的区别
1.webpack 文件指纹(hash ,重新打包就会变,chuckhash,入口文件变就会变,contenthash,代码变了就变 )
1.webpack 构建优化
1.promise 讲一下理解和状态
1.宏任务和微任务讲一下
1.做了哪些构建优化和性能优化
1.讲一下 http 缓存 ,什么时候用协商缓存(no-store 和 no-cache 的区别)
1.讲一下 vue 数据劫持

二面:

1.讲一下你项目,有哪些数据可以说明你说的价值
1.你在团队是一个什么样的角色
1.http 连接整个过程
1.一次http连接一定会4次挥手吗
1.讲一下 http3,UDP 原理
1.get 和 post 本质的区别
1.404 是客户端的错误还是服务端错误,502 、302、304、306 分别指什么
1.一千万条整型类型数据存储中如何找到出现次数最多的值

其他忘了
hr面:
就问一下期望,谈薪相关

荔枝

一面:

1. h5 怎么适配移动端和 pc 端 ,rem 具体实现

详情

2.一个梯型如何实现

详情

3.你是如何通过构建优化达到启动速度快5倍,如何配置分包
4.vue3 新属性

详情

5.为什么要引入 composition api

详情

6.proxy 比 Object.defineProperty 对比,proxy 可以劫持到第二第三层级吗(我答案是,面试官回答是否,我?)
7.vue2 如何监听数组的变化

详情

8.有用过缓存吗
9.http 浏览器有哪些缓存
10.js,css,图片适用于什么缓存
11.一个 tcp 可以发起多少个 http请求?http2.0 有限制吗

详情

12.什么是 http 头部堵塞

详情

13.http2 有什么缺点?什么情况下会出现 tcp 堵塞,网络不好为什么会造成堵塞?
1.如何检测网站的性能?除了谷歌工具如何通过代码检测,如何分析某个包有哪些依赖
1.你如何做网站的性能优化
1.如何做到某个包点击才加载,是如何实现按需加载的
1.我们 react 组件一定要引入 react

二面:(全是问简历的内容)

1.讲一下webpack5 模块联邦,当时为什么要用它
1.还了解 webpack 5 什么新属性
1.umd 和模块联邦对比有什么区别
1.webapck 的垫片和 preset 有什么区别,如何做到不是全局方式引入垫片(helper)
1.vue nextTick 原理
1.react key 和 vue key 的作用
1.平时用什么布局,flex 的组成和原理,除了vw、vh还有什么可以设置像素百分比
1. scss 的 scope 如何做到样式隔离
1.讲一下你说的自选模板cli是怎么实现的
1.你 git 管理是用什么工作流
1.算法时间复杂度大小排序 n 、logn、n2、2n、2n
1.如何让团队规范协同
1.你想要一个什么样的环境
1.还没问道你的点

hr 面:

1.最近两家只待了一年就离职的原因
1.你觉得在哪家公司收获最大
1.如何在哪些事件可以表现你的抗压能力
1.对加班的看法
1.对荔枝的了解
1.有没有拿到其他offer
1.后面都是扯一下谈薪

虾皮

一面 90min 感觉面试官很nice 少数聊的不错的面试官 问的相对比较深入

1.Node server内存机制
1.V8的垃圾回收机制
1.内存泄漏有哪些场景?如何查找?
1.项目得nuxt.js服务流量处理?100万用户如何处理?
1.项目nuxt.js的缓存是如何做的?
1.网页的多国语言处理
1.了解前端缓存吗?
1. 浏览器缓存?以及优先级?
1. HTTP2的特性?服务器推送了解吗?
1.HTTP3了解吗?和HTTP2、HTTP1的区别
1.TLS的握手过程?哪个环节用到了私钥
1.打包工具?webpack的原理?
1.热更新具体描述一些?
1.webpack压缩如何做的?
1.babel如何转es6的代码?
1.跨域方案?
1.项目的错误监控方案?如果sentry的 js文件加载不出来如何错误上报?

笔试
1、八股文
setTimeout(() => {
console.log(‘start’);
Promise.resolve().then(() => {
console.log(‘Promise 1’);
setTimeout(() => {
console.log(‘setTimeout 2’);
})
});
setTimeout(() => {
console.log(‘setTimeout 1’);
Promise.resolve().then(() => {
console.log(‘Promise 2’);
});
});
}, 0)
console.log(‘end’);
2、超级八股文
var s = 0;
var i = 1;
var funcs = [];
var n = 3;
function x(n) {
for (i = 0; i < 3; i++) {
funcs[i] = () => {
s = s + i * n;
console.log(s);
};
}
}
x(1);
funcs0;
funcs1;
funcs2;
3、编程题 实现一个循环检测的函数?
const a = {
b:{
c:{}
}
};
a.b.c.d = a;
console.log(isCycle(a));
4、斐波那契数列数列
/**
问题描述:
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
实例:
输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。

1 阶 + 1 阶 + 1 阶
1 阶 + 2 阶
2 阶 + 1 阶
*/
字节飞书
一上来就手撕题,有一题撕不出来有点尴尬
• leetcode 31

1.手撕发布订阅模式 eventBus

什么是发布订阅模式

比如我们很喜欢看某个公众号号的文章,但是我们不知道什么时候发布新文章,要不定时的去翻阅;这时候,我们可以关注该公众号,当有文章推送时,会有消息及时通知我们文章更新了。

上面一个看似简单的操作,其实是一个典型的发布订阅模式,公众号属于发布者,用户属于订阅者;用户将订阅公众号的事件注册到调度中心,公众号作为发布者,当有新文章发布时,公众号发布该事件到调度中心,调度中心会及时发消息告知用户。

1-1. eventBus的基本使用

我们要实现eventBus,首先肯定要知道eventBus它目前具有哪些api

注册EventBus

//EventBus.js
import Vue from 'vue';
 
export const EventBus = new Vue();

vm.$on( event, callback )

用法:

监听当前实例上的自定义事件。事件可以由 vm.$emit 触发。回调函数会接收所有传入事件触发函数的额外参数。

vm.$emit( eventName, […args] )

用法:

触发当前实例上的事件。附加参数都会传给监听器回调。

vm.$once( event, callback )

用法:

监听一个自定义事件,但是只触发一次。一旦触发之后,监听器就会被移除。

vm.$off( [event, callback] )

用法:

  • 移除自定义事件监听器。
  • 如果没有提供参数,则移除所有的事件监听器;
  • 如果只提供了事件,则移除该事件所有的监听器;
  • 如果同时提供了事件与回调,则只移除这个回调的监听器。

父组件

 import Son from '../components/Son';
 import {EventBus} from '../eventBus';
 
    export default {
       components: {
           Son,
       },
       mounted () {
           //监听grandson事件
           EventBus.$on("grandson",this.handleMmsg);
       },  
       methods: {
           handleMmsg(msg) {
               console.log(msg);
               //移除事件监听,监听以后马上又移除了,故Parent只监听一次
               EventBus.$off("grandson",this.handleMmsg);
           }
       },
    }
</script>

子组件

<template>
    <div>
        <hr>
        我是子组件 
        <br>
        <Grandson></Grandson>
    </div>
</template>
 
<script>
    import Grandson from '../components/Grandson';
    import {EventBus} from '../eventBus';
 
    export default {
        components: {
            Grandson,
        }, 
        mounted () {
            //监听事件
            EventBus.$on("grandson",(msg)=>{
                console.log(msg);
            });
        },     
    }
</script>

孙组件

<template>
    <div>
        <hr>
        我是孙子组件
        <br>
        <button @click="handleMsg">点击发出消息</button>
    </div>
</template>
 
<script>
    import {EventBus} from '../eventBus';
    export default {
        methods: {
            handleMsg() {
                //发出grandson事件
                EventBus.$emit("grandson","我是组件GrandSon")
            }
        },
    }
</script>

ok,现在我们来实现吧!!


1-2.实现

实现思路

  • 创建一个对象
  • 在该对象上创建一个缓存列表(调度中心)
  • on 方法用来把函数 fn 都加到缓存列表中(订阅者注册事件到调度中心)
  • emit 方法取到 arguments 里第一个当做 event,根据 event 值去执行对应缓存列表中的函数(发布者发布事件到调度中心,调度中心处理代码)
  • off 方法可以根据 event 值取消订阅(取消订阅)
  • once 方法只监听一次,调用完毕后删除缓存函数(订阅一次)

注意,on和emit不要搞混了,on才是监听收集事件,emit是发送触发事件,不管有没有emit发布事件,只要有on注册了事件,就会往listeners添加收集(先监听,再发布,发布过程中发现已经监听了就会执行监听回调,因此回调会在emit中执行)

  class EventEmitter {
    constructor() {
      // 维护事件及监听者
      this.listeners = {}
    }
    
    /**
     * 注册事件监听者
     * @param {String} type 事件类型,例如上面写的grandson事件
     * @param {Function} cb 回调函数,例如上面写的on接收的grandson对应的函数
     * 例如: EventBus.$on("grandson",(msg)=>{console.log(msg);})
     */
    on(type, cb) {
      if (!this.listeners[type]) {
        this.listeners[type] = []
      }
      this.listeners[type].push(cb)
    }
    
    /**
     * 发布事件
     * @param {String} type 事件类型
     * @param  {...any} args 参数列表,把emit传递的参数赋给回调函数
     * 例如: EventBus.$emit("grandson","我是组件GrandSon")
     */
    emit(type, ...args) {
      if (this.listeners[type]) {
        this.listeners[type].forEach(cb => {
          cb(...args)
        })
      }
    }
    
    /**
     * 移除某个事件的一个监听者
     * @param {String} type 事件类型
     * @param {Function} cb 回调函数
     * 例如:EventBus.$off("grandson",this.handleMmsg);
     */
    off(type, cb) {
      if (this.listeners[type]) {
        const targetIndex = this.listeners[type].findIndex(item => item === cb)
        if (targetIndex !== -1) {
          this.listeners[type].splice(targetIndex, 1)
        }
        if (this.listeners[type].length === 0) {
          delete this.listeners[type]
        }
      }
    }
    
    /**
     * 移除某个事件的所有监听者
     * @param {String} type 事件类型
     */
    offAll(type) {
      if (this.listeners[type]) {
        delete this.listeners[type]
      }
    }
  }
  
  // 创建事件管理器实例
  const ee = new EventEmitter()
  // 注册一个chifan事件监听者
  ee.on('chifan', function () { console.log('吃饭了,我们走!') })
  // 发布事件chifan
  ee.emit('chifan')
  // 也可以emit传递参数
  ee.on('chifan', function (address, food) { console.log(`吃饭了,我们去${address}${food}`) })
  ee.emit('chifan', '三食堂', '铁板饭') // 此时会打印两条信息,因为前面注册了两个chifan事件的监听者

  // 测试移除事件监听
  const toBeRemovedListener = function () { console.log('我是一个可以被移除的监听者') }
  ee.on('testoff', toBeRemovedListener)
  ee.emit('testoff')
  ee.off('testoff', toBeRemovedListener)
  ee.emit('testoff') // 此时事件监听已经被移除,不会再有console.log打印出来了

  // 测试移除chifan的所有事件监听
  ee.offAll('chifan')
  console.log(ee) // 此时可以看到ee.listeners已经变成空对象了,再emit发送chifan事件也不会有反应了

特点

  • 发布订阅模式中,对于发布者Publisher和订阅者Subscriber没有特殊的约束,他们好似是匿名活动,借助事件调度中心提供的接口发布和订阅事件,互不了解对方是谁。

  • 松散耦合,灵活度高,常用作事件总线

  • 易理解,可类比于DOM事件中的dispatchEventaddEventListener

image.png

缺点:

当事件类型越来越多时,难以维护,需要考虑事件命名的规范,也要防范数据流混乱。

2.观察者模式

和发布订阅有些类似,最大的区别就是,观察者模式是观察者直接观察目标对象,没有中介层。而发布订阅模式多了一个中介层,发布者和订阅者依赖于中介层,并不直接通信。

观察者模式与发布订阅模式相比,耦合度更高,通常用来实现一些响应式的效果。在观察者模式中,只有两个主体,分别是目标对象Subject,观察者Observer

  • 观察者需Observer要实现update方法,供目标对象调用。update方法中可以执行自定义的业务代码。
  • 目标对象Subject也通常被叫做被观察者或主题,它的职能很单一,可以理解为,它只管理一种事件。Subject需要维护自身的观察者数组observerList,当自身发生变化时,通过调用自身的notify方法,依次通知每一个观察者执行update方法。

按照这种定义,我们可以实现一个简单版本的观察者模式。

// 观察者
class Observer {
    /**
     * 构造器
     * @param {Function} cb 回调函数,收到目标对象通知时执行
     */
    constructor(cb){
        if (typeof cb === 'function') {
            this.cb = cb
        } else {
            throw new Error('Observer构造器必须传入函数类型!')
        }
    }
    /**
     * 被目标对象通知时执行
     */
    update() {
        this.cb()
    }
}

// 目标对象
class Subject {
    constructor() {
        // 维护观察者列表
        this.observerList = []
    }
    /**
     * 添加一个观察者
     * @param {Observer} observer Observer实例
     */
    addObserver(observer) {
        this.observerList.push(observer)
    }
    /**
     * 通知所有的观察者
     */
    notify() {
        this.observerList.forEach(observer => {
            observer.update()
        })
    }
}

const observerCallback = function() {
    console.log('我被通知了')
}
const observer = new Observer(observerCallback)

const subject = new Subject();
subject.addObserver(observer);
subject.notify();

观察者模式

特点

  • 角色很明确,没有事件调度中心作为中间者,目标对象Subject和观察者Observer都要实现约定的成员方法。
  • 双方联系更紧密,目标对象的主动性很强,自己收集和维护观察者,并在状态变化时主动通知观察者更新。

1.拿到一个需求功能,你的开发步骤

1.查看原型图,判断哪些地方可以拆分(拆成几个组件),哪些地方可以复用(抽离公共组件)

2.开始写页面,前后端联调。

3.代码优化,格式化,去重等

4.根据测试用例,初测

1.如果有一个用户白屏咋整?(讨论题)

2.印象深刻的故障解决流程

3.技术选型是怎么做的?解决了什么问题?

4.用过PWA吗?

详情

5.SSR的原理

详情

6.同源两个标签页的通讯方式

详情》

网易游戏

1. 项目相关:

项目中有什么亮点,哪一点让你印象深刻

2. 技术选型

参考的几个点:稳定,性能,团队上手,能否支持后续的迭代

  • 公司项目:

    • 旧项目,针对已使用的技术,看是否沿用现有项目的架构
    • 新项目,采用vue+iview+axios
    • 小程序,采用ui-app
    • 服务端渲染,ssr
  • 个人项目,vue+node+express+mongodb

3. 项目层面的优化

去除大量重复文件,提取可以复用文件

4. 安卓和IOS兼容性,举个例子说说

详情>

5. webpack的打包构建流程

详情>

6. webpack的4要点(entry、output、loader、plugins)

详情>

7. webpack打包比较慢怎么办

即webpack的优化

8. CDN的原理

1. 什么是CDN?

CDN是构建在网络之上的内容分发网络,依靠部署在各地的边缘服务器,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度命中率

CDN的关键技术主要有内容存储和分发技术。

2. 基本原理

CDN的基本原理是广泛采用各种缓存服务器,将这些缓存服务器分布到用户访问相对集中地区网络中,在用户访问网站时,利用全局负载技术将用户的访问指向距离最近的缓存服务器上,由缓存服务器直接响应用户请求。

3.基本思路

基本思路是尽可能避开互联网上有可能影响数据传输速度稳定性瓶颈环节,使内容传输的更快、更稳定。

通过在网络各处放置节点服务器所构成的在现有的互联网基础之上的一层智能虚拟网络,CDN系统能够实时地根据网络流量和各节点的连接、负载状况以及到用户的距离响应时间等综合信息将用户的请求重新导向离用户最近服务节点上。

其目的是使用户可就近取得所需内容,解决 Internet网络拥挤的状况,提高用户访问网站的响应速度。

4 . 服务模式

内容分发网络(CDN)是一种新型网络构建方式,它是为能在传统的IP网发布宽带丰富媒体而特别优化的网络覆盖层;而从广义的角度,CDN代表了一种基于质量秩序的网络服务模式。

简单地说,内容分发网络(CDN)是一个经策略性部署整体系统,包括分布式存储负载均衡网络请求的重定向内容管理4个要件,而内容管理全局的网络流量管理(Traffic Management)是CDN的核心所在。通过用户就近性和服务器负载的判断,CDN确保内容以一种极为高效的方式为用户的请求提供服务。

总的来说,内容服务基于缓存服务器,也称作代理缓存(Surrogate),它位于网络的边缘,距用户仅有"一跳"(Single Hop)之遥。同时,代理缓存是内容提供商源服务器(通常位于CDN服务提供商的数据中心)的一个透明镜像。这样的架构使得CDN服务提供商能够代表他们客户,即内容供应商,向最终用户提供尽可能好的体验,而这些用户是不能容忍请求响应时间有任何延迟的。

5.关键技术

(1)内容发布:它借助于建立索引、缓存、流分裂、组播(Multicast)等技术,将内容发布或投递到距离用户最近的远程服务点(POP)处;

(2)内容路由:它是整体性的网络负载均衡技术,通过内容路由器中的重定向(DNS)机制,在多个远程POP上均衡用户的请求,以使用户请求得到最近内容源的响应;

(3)内容交换:它根据内容的可用性、服务器的可用性以及用户的背景,在POP的缓存服务器上,利用应用层交换、流分裂、重定向(ICP、WCCP)等技术,智能地平衡负载流量;

(4)性能管理:它通过内部和外部监控系统,获取网络部件的状况信息,测量内容发布的端到端性能(如包丢失、延时、平均带宽、启动时间、帧速率等),保证网络处于最佳的运行状态。

6 . 主要特点

1、本地Cache加速 提高了企业站点(尤其含有大量图片和静态页面站点)的访问速度,并大大提高以上性质站点的稳定性

2、镜像服务 消除了不同运营商之间互联的瓶颈造成的影响,实现了跨运营商的网络加速,保证不同网络中的用户都能得到良好的访问质量。

3、远程加速 远程访问用户根据DNS负载均衡技术智能自动选择Cache服务器,选择最快的Cache服务器,加快远程访问的速度

4、带宽优化 自动生成服务器的远程Mirror(镜像)cache服务器,远程用户访问时从cache服务器上读取数据,减少远程访问的带宽、分担网络流量、减轻原站点WEB服务器负载等功能。

5、集群抗攻击 广泛分布的CDN节点加上节点之间的智能冗余机制,可以有效地预防黑客入侵以及降低各种D.D.o.S攻击对网站的影响,同时保证较好的服务质量 。

9. flex布局?常用来干啥?常见的属性;grid布局?

详情》

10. 浏览器刷新频率?

一般为:60赫兹

11. JS事件循环机制 + 八股文输入输出题

详情>

12. HTTPS为什么能保证安全

  • 内容加密 建立一个信息安全通道,来保证数据传输的安全;
  • 身份认证 确认网站的真实性
  • 数据完整性 防止内容被第三方冒充或者篡改

详情》

13. HTTPS一定是安全的吗?

不一定。

现在我们是使用ca公钥对ca机构颁发的ca数字证书进行验证,基于对于CA的信任机制。

所以这个信任机制有问题的话,那就可能不安全

第一个方面,
那么ca机构是否存在是假的吗?
会存在,但是一般不会发生,因为公司一般去申请ca证书的时候,都会选择正规的CA机构。几乎不可能存在去假的CA机构进行验证。
另外,虽然正规CA的公钥公开,即使这中间人解开了CA数字证书,也没法改变数据之后进行私钥加密,因为“中间人攻击”的中间人不会得到正规CA的私钥。
只有公司去了假了CA机构申请,才会有私钥。

第二个方面
数字证书不一定用CA机构颁发的,数字证书是可以自定义的。一般这种情况,需要手动安装数字证书,获取这个证书一般是去官网下载。
那么获取数字证书的时候就有可能出问题。
有可能下载数字证书的时候,被劫持,替换为中间人的数字证书。这样,以后传输数据的时候,就会发送中间人攻击了。

14. 说一下中间人攻击

指攻击者与通讯的两端分别创建独立的联系,并交换其所收到的数据,使通讯的两端认为他们正在通过一个私密的连接与对方 直接对话,但事实上整个会话都被攻击者完全控制。

详情》

如何避免:

使用https

为什么https避免:

查看https的优势

编程题

19. leetcode

将array 数组(仅包含整数的无序的数组)进行重新排列,
排序后数组符合特征 array[0] <= array[1] >= array[2] <= array[3]

示例:

输入: array = [2,7,2,1,6,3]

输出: [2,7,1,6,2,3] 或其他符合要求的答案

function sort(array = []) {
  if (!array || !array.length) return []
  if (!array.length === 1) return []
  // 将最小的放前面
  array.sort((a, b) => a - b)
  let i = 1
  // [1, 2, 3, 4, 5]
  // 执行一遍后变成2和3对换 [1,3,2,4,5]
  // 此时发现,3,2的位置都是正确的了,那么下一步其实就是要动 4和5了,也就是i+2
  // 接着i变成3,4和5对换
  // 数组变成 [ 1, 3, 2, 5, 4 ]
  while (i < array.length) {
    swap(array, i, i + 1)
    i += 2
  }
  return array
}
function swap(arr, i, j) {
  [arr[i], arr[j]] = [arr[j], arr[i]]
}
const test = sort([1, 2, 3, 4, 5])
console.log(test) //[ 1, 3, 2, 5, 4 ]

二面

1.前东家的情况巴拉巴拉

2.印象最深刻的项目?

低代码平台

特点:难,逻辑流程复杂且链路长

3.回首过去,那些事情可以做得更好?

总结问题和收获 总结的能力和习惯

4. 印象最深刻的bug?

view表单动态校验 提示信息错位>

5. 项目优化的地方有哪些

1.抽离了公共的口岸列表

2.表格做了响应式处理

6. 项目详情和难度

接下来要 自己挖掘,总结下项目

7. 树结构设计和遍历

let datas = [//是一个树结构的数据
    {
      id: '1',
      pId: '0',
      children: [
        {
          id: '1-1',
          pId: '1',
        },
      ],
    },
  ]
  
let expandedKeys = []//保存所有的id项
  
function setName(datas) {
    //遍历树  获取id数组
    for (var i in datas) {
      this.expandedKeys.push(datas[i].id)
      if (datas[i].children) {
        //存在子节点就递归
        this.setName(datas[i].children)
      }
    }
}

8. 模块联邦实现

网易云音乐

一面:

1. 自我介绍一下你比较得意的项目,你做了哪些事,结果怎样
介绍

低代码开发平台:通过拖拽组件到视图区,来搭建页面结构,通过组件的属性面板来修改组件。

难点:

1-1. 怎么判断当前的操作是嵌套?
1-2. 嵌套的数据怎么处理?
1-3. 组件是怎么渲染成视图的?
1-4. 修改属性面板是怎么同步更新组件视图的?
1-5. 如何通过自定义样式修改组件视图?
  • /deep/
  • 加id,然后用id选择器来写样式,注意style不要加scoped
1-6. 你对这种低代码平台怎么看,发展怎么样?看好吗?成熟吗?
1-7. 你负责的哪些内容?

在应用魔方项目中,负责了属性面板的开发以及组件库的维护。不过后面基于对低代码的兴趣,也做了一款简配的低代码应用。

1-8. 里面用到了哪些技术?

拖拽api,vueX…

后续考虑加上:微服务,webpack相关的优化,node,mongodb


2. http 缓存 ,内存缓存和磁盘缓存的区别,哪些文件存在这两内存
  • http 缓存,即强缓存和协商缓存,详情>

  • 内存缓存和磁盘缓存,详情>

解析:

  • 内存缓存会将编译解析后的文件,直接存入该进程的内存中
  • 对于大文件来说,大概率是不存储在内存中的,反之优先
  • 当前系统内存使用率高的话,文件优先存储进硬盘

在浏览器中,浏览器会在js图片等文件解析执行后直接存入内存缓存中,那么当刷新页面时只需直接从内存缓存中读取(from memory cache);而css文件则会存入硬盘文件中,所以每次渲染页面都需要从硬盘读取缓存(from disk cache)。

为什么一般js和图片文件会放到内存缓存,css 放在硬盘缓存?

样式表一般在磁盘中,不会缓存到内存中去,因为CSS样式加载一次即可渲染出网页。
但是,脚本却可能随时会执行,如果脚本在磁盘当中,在执行该脚本需要从磁盘中取到内存当中来。这样的IO开销是比较大的,有可能会导致浏览器失去响应。因此,脚本一般在内存中。

3. 你写过什么插件
  • chrome浏览器的插件,用于搜索swaigger接口的插件
  • webpack插件,md文档快捷插件
4. vue key 的作用

详情>

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值