interView - Azurehuihui/LearnJS GitHub Wiki
各种技术官网(阮一峰),视频,博客, 书籍(js高级程序设计、深入理解ES6、vuejs实战), 咨询朋友
1、是独立负责一个项目还是和别人合作 团队协作
2、介绍下项目 项目中的亮点 做过些什么项目
3、项目中遇到的印象比较深刻的问题
4、项目是几个人开发的?角色定位?
5、 与别人相比的优势和劣势
6、谈谈你如何看待前端岗位的工作职责,工作内容?
7、关于项目细节的一些灵魂拷问
面向对象是一种编程思维,他把很多行为都存放到一个对象中,当你需要使用他是,根据对象来获取。根据名词找内容, 面向过程是执行执行动词
大象装进冰箱
面向过程
1、开冰箱
2、放大象
3、关冰箱
面向对象
1、冰箱.开门
2、冰箱.装进
3、冰箱.关门
面向对象有3大特点: 封装、继承、多态
封装: 把一些行为和属性都归置到一个对象中,即事物的内部逻辑和属性对外不直接可见。当你需要的时候从对象中获取内容,这样可以降低程序的耦合度,保护事物的内部数据, 比如 {}, 立即执行函数,普通函数等
继承:对某一类事物的公共方法和属性进行抽象为父类, 子类可以继承父类的属性和方法,还可以扩展自己的属性和方法,js中可以通过原型链来实现继承,es6中通过语法糖class以及extends来实现继承
多态:某个方法在不同的条件下,会进行不同的执行动作,主要是重写(overwrite)和重载(override)
重写: 允许对方法重新实现,但是不能改变方法名, 形参列表和返回类型
重载:允许子类改变方法的形式参数列表、返回值类型,但不允许改变签名,在调用方法时,根据输入的形式参数选择对应的重载(js中不直接支持)
多态是展现在继承中的,即父类和子类的同名方法的重写
前后端分离是一个项目管理层面上的决策,当展示页面的逻辑越来越复杂的时候,前后端的耦合性会非常强,一方面是前后端的界限不明确,那前后端的工作进度不好统一。由此产生了前后端分离的概念,并推动得到了像 vue react等前端框架。
要做到前后端分离需要考虑整个项目的流程, 如方案设计,开发,测试,部署等。
1、前后端的边界在哪里
2、前后端协作之间的会话是怎么产生的?后端是怎么知道前端发过去的请求是谁发的
对于一个数据请求来说,可以分为发起网络请求、后端处理、浏览器响应三个步骤。浏览器缓存可以帮助我们在第一和第三步骤中优化性能。比如说直接使用缓存而不发起请求,或者发起了请求但后端存储的数据和前端一致,那么就没有必要再将数据回传回来,这样就减少了响应数据
所以良好的缓存策略可以减少不必要的数据传输,节省带宽,减少服务器的负担,提升网站性能,降低资源的重复加载,提高客户端网页的整体加载速度,总体来说用户体验友好
缺点:
资源如果有更改但是客户端不及时更新会造成用户获取信息滞后,如果老版本有bug的话,情况会更加糟糕
缓存分为强缓存和协商缓存
强缓存就是强制使用缓存, 当用户在浏览器输入一个url后,浏览器会先去缓存中查找是否有该资源,若有状态码200则直接使用该资源,若没有,就向服务器发起请求,服务器会先根据last-modified和etag确定该资源是否真的不存在,若还是存在,则返回状态码304和Not Modified的字符串,然后又去浏览器缓存中加载该资源,若真的没有,那么浏览器就获取该资源,并返回客户端,状态码是200,客户端会把该资源再存入缓存中。
协商缓存的可走的两个流程:
发请求-->看资源是否过期-->过期-->请求服务器-->服务器对比资源是否真的过期-->没过期-->返回304状态码-->客户端用缓存的老资源。
发请求-->看资源是否过期-->过期-->请求服务器-->服务器对比资源是否真的过期-->过期-->返回200状态码-->客户端如第一次接收该资源一样,记下它的cache-control中的max-age、etag、last-modified等。
1、基本原理
1)浏览器在加载资源时,根据请求头的expires和cache-control判断是否命中强缓存,是则直接从缓存读取资源,不会发请求到服务器。
2)如果没有命中强缓存,浏览器一定会发送一个请求到服务器,通过last-modified和etag验证资源是否命中协商缓存,如果命中,服务器会将这个请求返回,但是不会返回这个资源的数据,依然是从缓存中读取资源
3)如果前面两者都没有命中,直接从服务器加载资源
2、相同点
如果命中,都是从客户端缓存中加载资源,而不是从服务器加载资源数据;
3、不同点
强缓存不发请求到服务器,协商缓存会发请求到服务器。
etag的作用是唯一标识符
最后总结一下浏览器的三级缓存原理:
先去内存看,如果有,直接加载
如果内存没有,择取硬盘获取,如果有直接加载
如果硬盘也没有,那么就进行网络请求
加载到的资源缓存到硬盘和内存
强制缓存优先于协商缓存进行,若强制缓存(Expires和Cache-Control)生效则直接使用缓存,若不生效则进行协商缓存(Last-Modified / If-Modified-Since和Etag / If-None-Match),协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,返回200,重新返回资源和缓存标识,再存入浏览器缓存中;生效则返回304,继续使用缓存。
具体参考资料:
我的项目中没有用到浏览器缓存,但用到了commonJS中的require缓存
为什么不在浏览器也是用CommonJS
回答这个问题之前,我们首先要清楚一个事实,CommonJS的 require 语法是同步的,当我们使用require 加载一个模块的时候,必须要等这个模块加载完后,才会执行后面的代码。如果知道这个事实,那我们的问题也就很容易回答了。NodeJS 是服务端,使用 require 语法加载模块,一般是一个文件,只需要从本地硬盘中读取文件,它的速度是比较快的。但是在浏览器端就不一样了,文件一般存放在服务器或者CDN上,如果使用同步的方式加载一个模块还需要由网络来决定快慢,可能时间会很长,这样浏览器很容易进入“假死状态”。所以才有了后面的AMD和CMD模块化方案,它们都是异步加载的,比较适合在浏览器端使用
commonJS模块(nodejs)和ES6模块(浏览器)的加载和导出
commonJS
导出有两种形式:
1、exports
2、module.exports = {}
导入/加载
let x = require(modulePath)
exports 是module.exports 的别名 exports 是指向module.exports 的应用
相同点 :通过exports 和 module.exports 对外公开的方法都可以访问
区别: module.exports 不是真正的接口 返回模块对象本身
exports 最终调用的是module.exports而不是exports
module.exports 指向是一个对象
exports 指向是对module.exports 的引用
require 返回的是module.exports 在项目中使用exports 可能会报错,
我们一般使用 module.exports
注意点:
1、commonJs中的导入只是值的拷贝,不同的拷贝之间互不影响,且原先exports文件中的改动不会影响导出的内容
2、commonJS模块是运行时加载的,且require()是同步加载模块
3、require会存在缓存,每次都使用第一个加载进来的东西,除非手动删除缓存 require.cache[]可以访问缓存
4、
// a.js
var counter = 3;
function incCounter() {
return ++counter;
}
module.exports = {
counter: counter,
incCounter: incCounter,
};
// 然后在其他文件导入该模块
let a = require('./a')
console.log(a.counter) // 3
console.log(a.incCounter()) // 4
console.log(a.counter); // 3上面的例子充分说明了如果我们对外输出了counter 变量,就算后续调用模块内部的incCounter 方法去修改它的值,它的值依旧没有变化。
ES6
导出:
export 或者 export default
导入:
import XXX
export 和 export default的区别?
export 和 export default 都可以导出常量 ,函数 ,文件 , 模块
export 可以导出多个
export default 仅有一个
export 导出 在导出的时候可以 import {} from 'xxx' 通过结构赋值导入相应的
export default 导出 import xxx from 'xxx'
JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行的时候,再根据这个只读引用,到被加载的那个模块里去取值。
// a.js
export let counter = 3;
export function incCounter() {
return ++counter;
}
// b.js
import { counter, incCounter } from './a';
console.log(counter); // 3
console.log(incCounter()); // 4
console.log(counter); // 4上面代码说明,ES6 模块import的变量counter是可变的,完全反应其所在模块lib.js内部的变化。 而第二个差异,也是为什么ES6模块这么受人欢迎的最大原因之一。我们知道CommonJS其实加载的是一个对象,这个对象只有在脚本运行时才会生成,而且只会生成一次。但是ES6模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成,这样我们就可以使用各种工具对JS模块进行依赖分析,优化代码,而Webpack中的 tree shaking 和 scope hoisting 实际上就是依赖ES6模块化。
注意点
1、import导出的模块是对象的一个引用,引用对象在一处的变化会导致其他引用的地方都发生改变。 且这个引用是只读的,可以修改这个引用的属性值,追加新属性,但不能重新赋值,即 obj = {}是不正确的。
2、import出的内容,不同脚本加载这个接口都是同样一个实例
3、import是异步加载
- 两者的循环依赖
commomJS
// a.js
exports.done = false;
var b = require('./b.js');
console.log('在 a.js 之中,b.done = ' + b.done);
exports.done = true;
console.log('a.js 执行完毕');
// b.js
exports.done = false;
var a = require('./a.js');
console.log('在 b.js 之中,a.done = ' + a.done);
exports.done = true;
console.log('b.js 执行完毕');
// main.js
var a = require('./a.js'); // require时就会执行整个文件
/**
输出:
在 b.js 之中,a.done = false
b.js 执行完毕
*/
var b = require('./b.js');
/**
跟a.js类似,b.js导出一个变量后,在第二行就开始加载a.js,发生了循环依赖
然后系统就会去内存对象的exports 中取done变量的值,
可是因为a.js没有执行完,所以只取到刚开始输出的值false。
接着b.js继续执行后面的代码,执行完毕后,再把执行权交还给a.js ,
执行完后面剩下的代码
*/
console.log('在 main.js 之中, a.done= ' + a.done + ' b.done= ' + b.done);
/**
输出:
在 main.js 之中, a.done= true b.done= true
*/不知道
1). 一个单词词组逆序(例如 "I am dog" => "dog am I") 分别用普通 高阶函数 reduce实现
function wordReverse(str) {
let strArr = str.split(" ");
let result = "";
// plan A
// for (let i = (strArr.length - 1); i >= 0; i--) {
// result += (strArr[i] + " ");
// }
// return result.substr(0, result.length - 1);
// plan B
return strArr.reduce((result, item) => {
return (" " + item) + result;
}, result).substr(1)
}2). 统计一个页面中所有tag的种类和数目 代码尽量精简
var all = document.getElementsByTagName('*');
var tags = [];
for(var i=0;i<all.length;i++){
tags.push(all[i].tagName.toLocaleLowerCase());
}
var res = {};
for(var i=0;i<tags.length;i++){
if(!res[tags[i]]){
res[tags[i]] = 1;
} else {
res[tags[i]]++;
}
}
console.log(res);
// 方法二
let allTags = Array.from(document.querySelectorAll('*')).map(v => v.tagName)
let kinds = new Set(allTags).size
let newObj = {}
for(let i of allTags) {
if (!newObj[i]) {
newObj[i] = 1
} else {
newObj[i]++
}
}
console.log(newObj)
console.log(kinds)PWA(Progressive web apps,渐进式 Web 应用)运用现代的 Web API 以及传统的渐进式增强策略来创建跨平台 Web 应用程序。通俗来说,PWA应用是指那些使用指定技术和标准模式来开发的web应用,这将同时赋予它们web应用和原生应用的特性
elementui组件库, 三方的swiper组件库
调研组件的功能,显示效果,每个属性包括样式和参数的具体功能,有些手机显示 和 网页显示不一样
拿input组件举例:
多层封装,导致flex的3个参数失效, 单层封装的话不能实现type为checkbox和radio的placeholder效果
vue的模板中只能有一个跟元素,后来想到用v-if来区分两类
遇到的问题:
手机的组件显示和web封装的组件显示有区别,主要是两者的渲染引擎不同,所以通过web封装的组件不能完全适配手机中的显示
let 变量提升 没有块级作用域的概念, 变量可以重复定义和声明
const let 没有变量提升,存在暂时性死区, 有块级作用域的概念, 变量不能重复定义
改变this的指向的方法
call和apply 仅参数形式的区别, apply必须是数组
bind会返回一个改变this指向的新方法
in 是下标(其实是键值, 参考对象遍历中的 for in)
of 是数组的每一项(其实是value值)
for...in 循环不仅遍历数字键名,还会遍历手动添加的其它键,甚至包括原型链上的键。for...of 则不会这样
let arr = [1, 2, 3]
arr.set = 'world' // 手动添加的键
Array.prototype.name = 'hello' // 原型链上的键
for(let item in arr) {
console.log('item', item)
}
/*
item 0
item 1
item 2
item set
item name
*/
for(let value of arr) {
console.log('value', value)
}
/*
value 1
value 2
value 3
*/
无论是 for...in 还是 for...of 都不能遍历出 Symbol 类型的值,遍历 Symbol 类型的值需要用 Object.getOwnPropertySymbols() 方法
{
let a = Symbol('a')
let b = Symbol('b')
let obj = {
[a]: 'hello',
[b]: 'world',
c: 'es6',
d: 'dom'
}
for(let key in obj) {
console.info(key + ' --> ' + obj[key])
}
/*
c --> es6
d --> dom
*/
let objSymbols = Object.getOwnPropertySymbols(obj)
console.info(objSymbols) // [Symbol(a), Symbol(b)]
objSymbols.forEach(item => {
console.info(item.toString() + ' --> ' + obj[item])
})
/*
Symbol(a) --> hello
Symbol(b) --> world
*/
// Reflect.ownKeys 方法可以返回所有类型的键名,包括常规键名和Symbol键名
let keyArray = Reflect.ownKeys(obj)
console.log(keyArray) // ["c", "d", Symbol(a), Symbol(b)]
}
两者都可以与break、continue 和 return 配合使用,跳出循环
forEach 循环无法中途跳出,break 命令或 return 命令都不能奏效
总之,for...in 循环主要是为了遍历对象而生,不适用于遍历数组
for...of 循环可以用来遍历数组、类数组对象,字符串、Set、Map 以及 Generator 对象
1、Object.keys(obj).forEach()
2、for item in obj
3、Object.getOwnPropertyNames(obj),返回一个数组,包含对象自身的所有属性(不含Symbol属性,但是包括不可枚举属性)
4、使用Reflect.ownKeys(obj)遍历
返回一个数组,包含对象自身的所有属性,不管属性名是Symbol或字符串,也不管是否可枚举.
var obj = {'0':'a','1':'b','2':'c'};
Reflect.ownKeys(obj).forEach(function(key){
console.log(key,obj[key]);
});
遍历顺序不保证
数字: 升序
字母: 升序
// elementui layout 不行,是固定的
// flex
1. 流体布局
<!DOCTYPE html>
<html lang="en">
<head>
<style>
.left {
float: left;
height: 200px;
width: 100px;
background-color: red;
}
.right {
width: 200px;
height: 200px;
background-color: blue;
float: right;
}
.main {
margin-left: 120px;
margin-right: 220px;
height: 200px;
background-color: green;
}
</style>
</head>
<body>
<div class="container">
<div class="left"></div>
<div class="right"></div>
<div class="main"></div>
</div>
</body>
</html>
左右模块各自向左右浮动,并设置中间模块的 margin 值使中间模块宽度自适应。
缺点就是主要内容无法最先加载,当页面内容较多时会影响用户体验。
2. BFC 三栏布局
BFC 规则有这样的描述:BFC 区域,不会与浮动元素重叠。因此我们可以利用这一点来实现 3 列布局。
<!DOCTYPE html>
<html lang="en">
<head>
<style>
.left {
float: left;
height: 200px;
width: 100px;
margin-right: 20px;
background-color: red;
}
.right {
width: 200px;
height: 200px;
float: right;
margin-left: 20px;
background-color: blue;
}
.main {
height: 200px;
overflow: hidden;
background-color: green;
}
</style>
</head>
<body>
<div class="container">
<div class="left"></div>
<div class="right"></div>
<div class="main"></div>
</div>
</body>
</html>
缺点跟方法一类似,主要内容模块无法最先加载,当页面中内容较多时会影响用户体验。因此为了解决这个问题,有了下面要介绍的布局方案双飞翼布局。
3. 双飞翼布局
<!DOCTYPE html>
<html lang="en">
<head>
<style>
.content {
float: left;
width: 100%;
}
.main {
height: 200px;
margin-left: 110px;
margin-right: 220px;
background-color: green;
}
.left {
float: left;
height: 200px;
width: 100px;
margin-left: -100%;
background-color: red;
}
.right {
width: 200px;
height: 200px;
float: right;
margin-left: -200px;
background-color: blue;
}
</style>
</head>
<body>
<div class="content">
<div class="main"></div>
</div>
<div class="left"></div>
<div class="right"></div>
</body>
</html>
利用的是浮动元素 margin 负值的应用,感兴趣的同学可以上网搜搜原理。
主体内容可以优先加载,HTML 代码结构稍微复杂点。
4. 圣杯布局
<!DOCTYPE html>
<html lang="en">
<head>
<style>
.container {
margin-left: 120px;
margin-right: 220px;
}
.main {
float: left;
width: 100%;
height: 300px;
background-color: red;
}
.left {
float: left;
width: 100px;
height: 300px;
margin-left: -100%;
position: relative;
left: -120px;
background-color: blue;
}
.right {
float: left;
width: 200px;
height: 300px;
margin-left: -200px;
position: relative;
right: -220px;
background-color: green;
}
</style>
</head>
<body>
<div class="container">
<div class="main"></div>
<div class="left"></div>
<div class="right"></div>
</div>
</body>
</html>
跟双飞翼布局很像,有一些细节上的区别,相对于双飞翼布局来说,HTML 结构相对简单,但是样式定义就稍微复杂,也是优先加载内容主体。
5. Flex 布局
<!DOCTYPE html>
<html lang="en">
<head>
<style>
.container {
display: flex;
}
.main {
flex-grow: 1;
height: 300px;
background-color: red;
}
.left {
order: -1;
flex: 0 1 200px;
margin-right: 20px;
height: 300px;
background-color: blue;
}
.right {
flex: 0 1 100px;
margin-left: 20px;
height: 300px;
background-color: green;
}
</style>
</head>
<body>
<div class="container">
<div class="main"></div>
<div class="left"></div>
<div class="right"></div>
</div>
</body>
</html>
简单实用,未来的趋势,需要考虑浏览器的兼容性。
6. Table 布局
<!DOCTYPE html>
<html lang="en">
<head>
<style>
.container {
display: table;
width: 100%;
}
.left, .main, .right {
display: table-cell;
}
.left {
width: 200px;
height: 300px;
background-color: red;
}
.main {
background-color: blue;
}
.right {
width: 100px;
height: 300px;
background-color: green;
}
</style>
</head>
<body>
<div class="container">
<div class="left"></div>
<div class="main"></div>
<div class="right"></div>
</div>
</body>
</html>
缺点:无法设置栏间距
7. 绝对定位布局
<!DOCTYPE html>
<html lang="en">
<head>
<style>
.container {
position: relative;
}
.main {
height: 400px;
margin: 0 120px;
background-color: green;
}
.left {
position: absolute;
width: 100px;
height: 300px;
left: 0;
top: 0;
background-color: red;
}
.right {
position: absolute;
width: 100px;
height: 300px;
background-color: blue;
right: 0;
top: 0;
}
</style>
</head>
<body>
<div class="container">
<div class="main"></div>
<div class="left"></div>
<div class="right"></div>
</div>
</body>
</html>
简单实用,并且主要内容可以优先加载。
1、 ts与js的区别
ts是严格类型的,即必须指定类型
ts多出的类型: tuple类型(元组类型)、enum类型(枚举类型)、any类型(任意类型)
ts中的特殊类型:
void类型(没有任何类型)表示定义方法没有返回值
never类型:是其他类型(包括null和undefined)的子类型,代表从不会出现的值这意味着声明never变量只能被never类型所赋值
lambda(箭头函数)
ts会做代码静态检查(静态编译) js是动态编译
js没有重载概念,ts有可以重载
ts更利于重构
ts是js的超集,即你可以在ts中使用原生js语法。
ts需要静态编译,它提供了强类型与更多面向对象的内容。
ts最终仍要编译为弱类型,基于对象的原生的js,再运行。故ts相较java/C#这样天生面向对象语言是有区别和局限的
ts是由微软牵头主导的,其语法风格与概念主要来自C#
有了强类型,就有了更好的IDE支持,包括语法提示和编译时错误信息。有了接口和继承,代码就更易读易维护。这些都是大型复杂前端工程急需的特性。
语言层面:JavaScript和TypeScript都是ECMAScript(ECMA-262)的具体实现。执行环境层面:浏览器引擎和Node.js都能够直接运行JavaScript,但无法直接运行TypeScript。时序层面:TypeScript被真正执行前,会通过编译转换生成JavaScript,之后才能被解释执行。厂商层面:JavaScript由Netscape率先推出,现在主要由各大浏览器厂商实现。而TypeScript is a trademark of Microsoft Corporation,目前由微软进行设计和维护。 TypeScript是ECMAScript 2015的语法超集,是JavaScript的语法糖。JavaScript程序可以直接移植到TypeScript,TypeScript需要编译(语法转换)生成JavaScript才能被浏览器执行
js声明数组的方式:
let a = [1,2,3]
ts声明数组的方式:
let a: number[] = [1,2,3]
let b: Array<number> = [1,2,3]
2、typescript 常用哪些? 泛型有了解吗? Pick 和 Omit 有用过吗
泛型的使用
// 创建一个泛型类
class Queue<T> {
private data: T[] = [];
push = (item: T) => this.data.push(item);
pop = (): T | undefined => this.data.shift();
}
// 简单的使用
const queue = new Queue<number>();
queue.push(0);
queue.push('1'); // Error:不能推入一个 `string`,只有 number 类型被允许
3、断言可以举个例子吗
类似 强制类型转换,具体没有深入理解过
ECharts 是一个使用 JavaScript 实现的开源可视化库,涵盖各行业图表,满足各种需求
1、echarts 是怎么设计的有了解吗
没有
没有
渐进式前端框架, 是MVVM模式的
生命周期
组件封装
let const
箭头函数 本身没有this, 没有arguments
是js中异步编程的接口
new Promise((resolve, reject) => {
})
三种状态: pending sucess rejected
状态改变就是确定的
通过.then 来执行状态改变后的一些操作
所有对象都有 __proto__
只有函数有 prototype
可以通过position来实现
vue2 Object.defineProperty()
vue3 proxy
vue2中的数组是另外处理的
有
会先去 require.cache中找有没有加载过,(这个寻找可能还回去父类或者子类中寻找),若没有再加载
entry 打包的入口 (可以有多个打包入口)
output 打包的最终内容
节流的方式,间隔一定时间执行一次
都可以通过setTimeout实现,防抖是最后一次触发事件后的delay时间执行回调函数, 节流是间隔一定时间执行一次
属性描述符
id class 标签名
git checkout XXX
git reset XXX
没有用
nextTick()
事件循环
7种
隐藏盒子,display:none 和 visibity:hidden 的区别
dispaly: 非继承属性,设置为none,子组件都会隐藏
none visible
none时不占用空间,只在文档对象中, none -> visible 回流
visibility:继承性属性, 设置为hidden,但子组件设置为visible,子组件自然可以显示,子元素visibility不存在则子元素不可见
hidden visible
hidden是占据空间,只是不显示 hidden -> visible 重绘
该元素虽然被隐藏了,但仍然会影响布局
v-if指令与v-show指令都可以根据值动态控制DOM元素显示隐藏,v-if和v-show属于Vue的内部常用的指令,指令的职责是当表达式的值改变时把某些特殊的行为应用到DOM上。
区别:
- 实现方式: v-if是动态的向DOM树内添加或者删除DOM元素,v-show是通过设置DOM元素的display样式属性控制显隐。
- 编译过程: v-if切换有一个局部编译卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件,v-show只是简单的基于CSS切换。
- 编译条件: v-if是惰性的,如果初始条件为假,则什么也不做,只有在条件第一次变为真时才开始局部编译, v-show是在任何条件下都被编译,然后被缓存,而且DOM元素保留。
- 性能消耗: v-if有更高的切换消耗,v-show有更高的初始渲染消耗。
- 使用场景: v-if适合条件不太可能改变的情况,v-show适合条件频繁切换的情况。
borderRadius
vue中在data里面定义了对象,比如说student,但是没定义对象的属性,当拿到后端的数据之后,用obj.age,obj.name渲染在页面中时,数据不会发生改变,是为什么? 面试官说是因为实现响应式必须要先声明,才能去注册到响应式的里面。
vue2中不能, 劫持key
vue3中可以
不同浏览器会有不同的缩小方式
同步和异步任务分别进入不同的执行"场所",同步的进入主线程,异步的进入Event Table并注册函数。
当指定的事情完成时,Event Table会将这个函数移入Event Queue。
主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。
上述过程会不断重复,也就是常说的Event Loop(事件循环)。
call apply bind
直接把方法拿出来用
// 拼多多
#酷家乐 一面(电话面,比较注重基础):
基本数据类型的复制,是按值传递的
引用数据类型的复制,是按引用(地址指针)传值
深拷贝和浅拷贝都只针对引用数据类型,浅拷贝会对对象逐个成员依次拷贝,但只复制内存地址,而不复制对象本身,新旧对象成员还是共享同一内存;深拷贝会另外创建一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
区别:浅拷贝只复制对象的第一层属性,而深拷贝会对对象的属性进行递归复制。
var fruits = ["Banana", "Orange", "Apple", "Mango"];
var obj=fruits;
obj[1]="water";
console.log("fruits",fruits);
console.log("obj",obj)
var num1 = 123;
var num2 = 123;
var num3 = num1;
num1 === num2; // true
num1 === num3; // true
num1 = 456;
num1 === num2; // false
num1 === num3; // false
var arr1 = [1, 2, 3];
var arr2 = [1, 2, 3];
var arr3 = arr1;
arr1 === arr2; // false
arr1 === arr3; // true
arr1 = [1, 2, 3];
arr1 === arr2; // false
arr1 === arr3; // false
浅拷贝的实现: 对对象的属性中所有引用类型的值,遍历到是基本类型的值为止
1、Object.assign()
2、Array.prototype.concat()和Array.prototype.slice()
Array的slice和contact方法都不会修改原数组,而是会返回一个对原数组进行浅拷贝的新数组。这两种方法同Object.assign()一样,都是对第一层属性依次拷贝,如果第一层的属性是基本数据类型,就拷贝值;如果是引用数据类型,就拷贝内存地址
深拷贝的实现:
1、JSON.parse(JSON.stringify()) 缺点: JSON.stringify()无法正确处理函数
原理:用JSON.stringify()将对象转成字符串,再用JSON.parse()把字符串解析成对象。
2、递归遍历
3、函数库
也可以使用一些函数库,比如函数库lodash,也有提供_.cloneDeep用来做深拷贝
var _ = require('lodash');
var obj1 = {
a: 1,
b: { f: { g: 1 } },
c: [1, 2, 3]
};
var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);
// false
手写浅拷贝
function shallowCopy(fruits) {
let obj = Array.isArray(fruits) ? [] : {};
for (let i in fruits) {
obj[i] = fruits[i];
}
return obj;
}
var fruits = ["Banana", "Orange", "Apple", ["Mango", "blue"]];
var obj = shallowCopy(fruits);
obj[2] = "water";
obj[3][0] = "water";
console.log("fruits", fruits);
console.log("obj", obj);
TCP 连接使用三次握手的首要原因 —— 为了阻止历史的重复连接初始化造成的混乱问题,防止使用 TCP 协议通信的双方建立了错误的连接
我们在这篇文章中讨论了为什么 TCP 建立连接需要经过三次握手,在具体分析这个问题之前,我们首先重新思考了 TCP 连接究竟是什么,RFC 793 - Transmission Control Protocol - IETF Tools 对 TCP 连接有着非常清楚的定义 —— 用于保证可靠性和流控制机制的数据,包括 Socket、序列号以及窗口大小。
TCP 建立连接时通过三次握手可以有效地避免历史错误连接的建立,减少通信双方不必要的资源消耗,三次握手能够帮助通信双方获取初始化序列号,它们能够保证数据包传输的不重不丢,还能保证它们的传输顺序,不会因为网络传输的问题发生混乱,到这里不使用『两次握手』和『四次握手』的原因已经非常清楚了:
『两次握手』:无法避免历史错误连接的初始化,浪费接收方的资源;
『四次握手』:TCP 协议的设计可以让我们同时传递 ACK 和 SYN 两个控制信息,减少了通信次数,所以不需要使用更多的通信次数传输相同的信息;
BFC 即 Block Formatting Contexts (块级格式化上下文),它属于上述定位方案的普通流。
具有 BFC 特性的元素可以看作是隔离了的独立容器,容器里面的元素不会在布局上影响到外面的元素,并且 BFC 具有普通容器所没有的一些特性。
通俗一点来讲,可以把 BFC 理解为一个封闭的大箱子,箱子内部的元素无论如何翻江倒海,都不会影响到外部。
三、触发 BFC
只要元素满足下面任一条件即可触发 BFC 特性:
body 根元素
浮动元素:float 除 none 以外的值
绝对定位元素:position (absolute、fixed)
display 为 inline-block、table-cells、flex
overflow 除了 visible 以外的值 (hidden、auto、scroll)
BFC的特性
同一个 BFC 下外边距会发生折叠
BFC 可以阻止元素被浮动元素覆盖
BFC 可以包含浮动的元素(清除浮动)
1.内部的Box会在垂直方向上一个接一个的放置。
2.垂直方向上的距离由margin决定
3.bfc的区域不会与float的元素区域重叠。
4.计算bfc的高度时,浮动元素也参与计算
5.bfc就是页面上的一个独立容器,容器里面的子元素不会影响外面元素。
static relative absolute fixed sticky
static是html的默认属性,如果省略position属性,浏览器就认为该元素是static定位
static定位所导致的元素位置,是浏览器自主决定的,所以这时top、bottom、left、right这四个属性无效
relative表示,相对于默认位置(即static时的位置)进行偏移,即定位基点是元素的默认位置
absolute表示,相对于上级元素(一般是父元素)进行偏移,即定位基点是父元素
它有一个重要的限制条件:定位基点(一般是父元素)不能是static定位,否则定位基点就会变成整个网页的根元素html
absolute定位的元素会被"正常页面流"忽略,即在"正常页面流"中,该元素所占空间为零,周边元素不受影响
fixed表示,相对于视口(viewport,浏览器窗口)进行偏移,即定位基点是浏览器窗口。这会导致元素的位置不随页面滚动而变化,好像固定在网页上一样。
sticky跟前面四个属性值都不一样,它会产生动态效果,很像relative和fixed的结合:一些时候是relative定位(定位基点是自身默认位置),另一些时候自动变成fixed定位(定位基点是视口)
sticky生效的前提是,必须搭配top、bottom、left、right这四个属性一起使用,不能省略,否则等同于relative定位,不产生"动态固定"的效果。原因是这四个属性用来定义"偏移距离",浏览器把它当作sticky的生效门槛。
它的具体规则是,当页面滚动,父元素开始脱离视口时(即部分不可见),只要与sticky元素的距离达到生效门槛,relative定位自动切换为fixed定位;等到父元素完全脱离视口时(即完全不可见),fixed定位自动切换回relative定位
absolute和fixed会脱离文档流,即在正常页面的文档流中,元素所占的空间为0
fixed失效的场景:
在Chrome(Blink内核)中,position:fixed在:
祖先元素为transform 属性值不为 none 的元素
perspective 值不为 none 的元素
will-change 中指定了任意 CSS
filter 不为 none
设置了 transform-style: preserve-3d 的元素
情况下,定位容器由视口改为该祖先。
二面(微信视频面): 自我介绍
二面主要就是追着项目问一些细节问题
谈薪资
谈工作作息
#字节跳动: 一面(1h+):
SSL是一种加密协议
SSL协议大致就和上一节非对称加密的性质一样,握手的过程中主要也是为了交换秘钥,然后再通讯过程中使用对称加密进行通讯
二面(1h+): 自我介绍
重排和回流其实是一回事
浏览器渲染 浏览器在渲染页面的时候,大致是以下几个步骤:
- 解析html生成DOM树,解析css,生成CSSOM树,将DOM树和CSSOM树结合,生成渲染树;
- 根据渲染树,浏览器可以计算出网页中有哪些节点,各节点的CSS以及从属关系 - 回流
- 根据渲染树以及回流得到的节点信息,计算出每个节点在屏幕中的位置 - 重绘
- 最后将得到的节点位置信息交给浏览器的图形处理程序,让浏览器中显示页面
参考 参考2
DOM的变化影响到了元素的几何属性(宽高),浏览器重新计算元素的几何属性,其他元素的几何属性和位置也会受到影响,浏览器需要重新构造渲染树,这个过程称为重排,浏览器将受到影响的部分重新绘制到屏幕上的过程称为重绘。
引起重排的原因有
添加或者删除可见的DOM元素,
元素位置、尺寸、内容改变,
浏览器页面初始化,
浏览器窗口尺寸改变,重排一定重绘,重绘不一定重排,
减少重绘和重排的方法:
不在布局信息改变时做DOM查询
使用cssText或者className一次性改变属性
使用fragment
对于多次重排的元素,如动画,使用绝对定位脱离文档流,让他的改变不影响到其他元素
<ul>
<li></li>
</ul>
往ul里面插入10000个li,如何实现
实现一个class类Scheduler,完善一个add(promiseCreator)方法,实现一个带并发限制的promise异步调度器,保证同时运行的任务最多有两个
如:add(1000,'1')
add(200,'2')
add(300,'3')
add(400,'4')
输出:2 3 1 4
三面(1h+): 自我介绍
将产品经理转为程序员以解决开发时间长的问题,每分钟,任何与程序员(在4个正方向上)相邻的产品经理都会变成程序员,
返回直到单元格中没有产品经理为止所必须经过的最小分钟数,如果不可能返回-1
eg:[[1,2,1],[1,1,0],[0,1,1]] 输出3
[[0,2],[1,0]] 输出-1
采用bfs思想
会,一般会写一些个人笔记
几轮面试(3 + 1)
是否有转正机会(有)
技术栈(新项目强制使用 React,老项目 vue、jQuery、纯 js)
面试表现
面试反馈时间(五一前,第二天 HR 联系了)
转正机会(看表现,毕业转正)
假如过来实习一般是做什么(mentor 带,酷家乐商业平台:管理系统,商家后台,中后台)