<div>
<span></span>
</div>
div{
position:relative;
}
span{
position:absolute;/position:fiexd;
top:0;
left:0;
right:0;
bottom:0;
margin:auto;
}
div{
display:flex;
justify-content:center;
align-item:center;
}
div{
height:500px;
line-height:500px;
text-align:center;
}
span{
display:inline-block;
}
for (var i = 0, j = 0; i < 5, j < 9; i++, j++) {
k = i+j;
}
//k=16
// 当判断语句含有多个语句时,以最后一个判断语句的值为准,因此上面的代码会执行 8 次。
for (var i = 0, j = 0; i < 9, j < 5; i++, j++) {
k = i+j;
}
//k=8
// 当判断语句含有多个语句时,以最后一个判断语句的值为准,因此上面的代码会执行 4 次。
快速排序是从冒泡排序演变的一种排序方法,主要表现为取中间值来作为基准,然后根据这个基准进行交换位置排序。
套用一个经典例子。
let arr = [1,2,5345456,457,587,678,78,9879,456453,532,423,43,656,8,69,7,32,54,767,8,79,5,324,214,476,587,69,87,854,33,24,365,6578,789,23,534,6,568,7869,667,4354356754,76,878,93,223,5,58,67878,76,87,56,3423,5,568,679,7,95,634,5,3426,8,7,69,70,6759678,74,6,345,587,7698,789,6,4]
function quickSort(arr){
if (!Array.isArray(arr) || arr.length <=1){
return arr;
}
let leftArr = [],rightArr=[];
let poiveIndex=Math.floor(arr.length / 2);
let poiveValue = arr.splice(poiveIndex,1)[0];
for (let z =0;z<arr.length;z++){
if (arr[z]<poiveValue){
leftArr.push(arr[z])
}else{
rightArr.push(arr[z])
}
}
return quickSort(leftArr).concat([poiveValue], quickSort(rightArr))
};
quickSort(arr);
JavaScript的扁平化主要是表现在Array中,代表把多维数组转换成一维数组。
let arr = [1,23,[5345,34,5634,6],345645,[3453,5,346,456,346,[3453426,54,76345,6754,754,36,[325432456534645]]]];
let arrStr = JSON.stringify(arr);
//第1个方法:通过原生方法。(推荐)
arr.flat(Infinity);
//第2个方法:通过正则直接删除所有中括号(推荐)
arrStr.replace(/[\[\]]/g, '').split(',');
//第3个方法:通过循环机制来解套,没有第二个方法干脆,没有第一个方法简单,如果层级过多会浪费很多循环性能。不是很推荐。
let resultArr = [];
function FlatArr(arr){
arr.map((item)=>{
if (Array.isArray(item)){
FlatArr(item)
}else{
resultArr.push(item)
}
})
}
FlatArr(arr);
//第4个方法:和第三个方法很想,但是会比较简单吧。
while(arr.some(Array.isArray)){
arr = [].concat(...arr)
}
//....还有很多很多方法,但是基本上能说出前3种就够唬住面试官了。
Map是Es6中新增加的数据结构,它本质上和Object一样都是以键(key),值(value)组成的集合,不同的在于Object只能使用字符串作为键(key),而Map能使用任意类型作为键(key)。Map还能直接通过size来获取对象长度,这是Object没能提供的方法。
//Object格式
let obj = {
a:1
}
//Map格式
let map = new Map();
map.set(NaN, 1);
map.set(new String(1),1);
map.size;//2;
Set是Es6中新增加的数据结构,它类似数组,但是成员必须是唯一的,没有重复值,Set数据结构不能使用数组的遍历方法来进行循环遍历,需要使用...扩展来转换成普通数组,因为Set的唯一特性,我们可以使用Set来进行去重操作
let set = new Set([123,4123,123,123,123,5,326]);
console.log(set);//Set(4) {123, 4123, 5, 326};
//转普通数组。
[...set];
一般的HTTP缓存说的是get缓存
强缓存 (我喜欢说是时间缓存)
因为强缓存有一个特性,就是依据cache-control/expires的缓存时间没过期的话,就会直接使用浏览器本地缓存数据,不会进行任务请求,通过控制台我们能看到状态码表现是200,在Size会标记成disk cache / memory cache标志 代表用的是本地、内存缓存,没有经过服务器请求。
一般使用 max-age设置缓存时间
expires: Wed, 26 May 2021 03:28:53 GMT
GMT时间标记,表现和cache-control的意思差不多,优先级比cache-control低,但是会和cache-control同时存在。
协议缓存 (我喜欢说是hash标记缓存)
协议缓存出现的场景主要是为了解决强缓存的一些缺点,强缓存是根据时间缓存,会出现很多场景下,过了强缓存的时间,但是服务器的数据依旧没有任何更改,但是因为强缓存的特性,依旧会重新发起一个请求获取数据,随之ETag协议缓存就出现了,浏览器会发起一个请求,与服务器对比资源是否进行修改,如果浏览器的ETag和服务器的不一致则会进行获取请求返回200状态码,如果服务器资源没有进行任何修改,则会返回304状态码告知浏览器使用缓存中的数据。
Last-Modified是从资源中读取到最后修改时间,并且返回到请求中标记。Lst-Modified和ETag的表现大致上是一致的,只在于一个是用hash标记一个是用GMT时间标记。
hash模式
GMT模式
HTTP 2.0与HTTP 1.1区别 - FrankYou - 博客园
HTTP2.0是HTTP协议标准的第二个版本,HTTP2.0主要基于SPDY协议。
HTTP2和HTTP1的不同主要如下
HTTP常用状态码:(说一下常用的即可)
200 请求成功
301 永久重定向
302 临时重定向
304 无更改,缓存重定向到本地缓存
400 请求url错误、参数错误等
401 无用户认证
403 服务器拒绝
404 资源不存在。
500 服务器错误
502 服务器未响应
503 服务器超载304状态码代表数据在服务端无更改,一般浏览器接收到304状态码会重新命中内存缓存。
es6之前就已经存在的定义变量声明,会被变量提升,在全局中定义的变量可以在window下读取到,例如:
var test = 'a';
window.test // 'a';
es6新增加的局部变量生命,不会被变量提升,有暂时性死区的特性,可以进行重新定义,即使在全局中声明也不会挂载到window上,例子:
let test = 'a'
window.test // undefined
test = 'b'
console.log(test) //'b'
es6新增的常量声明,和let一样,不会被变量提升,有暂时性死区的特性,从名字上看出来是常量,代表声明后不可变(如果只是简单声明一个原始类型就不可更改,如果声明的是一个对象类型,可以进行内部属性、方法更改),在全局中声明也不会挂载到window上。例子:
const test = 'a';
window.test //undefined
test = 'c' // Uncaught TypeError: Assignment to constant variable.
const obj = {};
obj.a = 'a';
console.log(obj) // {a:'a'}
//定义
function People(name,age){
this.name = name;
this.age = age
}
People.prototype.eat = function(){
console.log('Ta在吃饭')
}
//使用
let chinaPeople = new People('张三','20');
//继承 (其他的继承方式就不用说了,就说这个寄生式组合继承就行);
function BlackPeople(name, age ,color){
People.call(this, name, age);
this.color = color
}
BlackPeople.prototype = new People();
BlackPeople.prototype.constructor = BlackPeople;
BlackPeople.prototype.language = function(){
console.log('Ta说印度语')
}
let indiaPeople = new BlackPeople('婆娜', '20', 'black');
let arr = [];
arr.map();
arr.forEach()
arr.some();
arr.every();
arr.filter();
arr.keys();
arr.values();
arr.reduce();
arr.pop();
arr.push();
arr.shift();
arr.unshift();
arr.splice();
arr.join();
arr.concat();
arr.fill()
//......很多 、差不多有30来个吧。一般能说一下大概有多少个,然后说一下常用的几个即可
Map
Set
剪头函数()=>{}
Promise
async/await
class
let
const
import / export
...运算符
模版字符串``
解构赋值
这里说的都是常用的。 其他的proxy、Symbol、Generator、修饰器等看自己以往的学习
Promise代表承诺,是Es6中新增的异步操作函数,内部有3个状态,Pending(等待、进行中)、Resolve(已完成)、Reject(已失败),状态一经改变不可二次更改。最大的应用场景是为了解释回调地狱
利用了观察者模式的思想,只要通过特定书写方式来注册对应状态事件处理函数,然后更新状态来调用注册过的处理函数。
答案:
const PENDING = 'pending';
const RESOLVED = 'resolved';
const REJECTED = 'rejected';
class MyPromise {
constructor(fn) {
this.status = PENDING;
this.value = null;
this.resolvedCallBacks = [];
this.rejectedCallBacks = [];
try {
fn(this.resolve, this.reject);
} catch (e) {
this.reject(e);
}
}
resolve = (value) => {
if (this.status === PENDING) {
this.status = RESOLVED;
this.value = value;
this.resolvedCallBacks.map((cb)=>cb(this.value))
}
};
reject = (value) => {
if (this.status === PENDING) {
this.status = REJECTED;
this.value = value;
this.rejectedCallBacks.map((cb) => cb(this.value));
}
};
then = (onFulfilled,onRejected) => {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v=>v;
onRejected = typeof onRejected === 'function' ? onRejected: r=>{throw r;};
if (this.status === PENDING) {
this.resolvedCallBacks.push(onFulfilled);
this.rejectedCallBacks.push(onRejected);
}
if (this.status === RESOLVED) {
onFulfilled(this.value);
}
if (this.status === REJECTED){
onRejected(this.value);
}
}
}
/
const PENDING = 'pending';
const RESOLVED = 'resolved';
const REJECTED = 'rejected';
function MyPromise(fn) {
this.status = PENDING;
this.value = null;
this.resolvedCallBacks = [];
this.rejectedCallBacks = [];
try {
fn(this.resolve,this.reject);
} catch (e) {
this.reject(e)
}
}
MyPromise.prototype.resolve = (value) => {
if (this.status === PENDING) {
this.status = RESOLVED;
this.value = value;
this.resolvedCallBacks.map((cb) => cb(this.value));
}
};
MyPromise.prototype.reject = (value) => {
if (this.status === PENDING) {
this.status = REJECTED;
this.value = value;
this.rejectedCallBacks.map((cb) => cb(this.value));
}
};
MyPromise.prototype.then = (onFulfilled, onRejected) => {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (v) => v;
onRejected =
typeof onRejected === 'function'
? onRejected
: (r) => {
throw r;
};
if (this.status === PENDING) {
this.resolvedCallBacks.push(onFulfilled);
this.rejectedCallBacks.push(onRejected);
}
if (this.status === RESOLVED) {
onFulfilled(this.value);
}
if (this.status === REJECTED) {
onRejected(this.value);
}
};
// 执行优先级
setTimeout(function(){console.log(1)},1000);
new Promise((resolve)=>{
console.log(2);
resolve();
console.log(3);
})
function fo(){
console.log(7)
}
async function foo(){
console.log(5);
await fo();
console.log(6);
}
foo();
console.log(4);
// 请写出打印流程
// 2、3、5、7、4、6、1
setInterval执行后,JavaScript的事件机制会按设定的时间定时触发生成一个回掉函数放置到执行栈中执行。
因为JavaScript是属于单线程模式的语言,所以setInterval的任务可能会受线程中执行的代码阻塞影响。
var a = 10;
var a ;
a = ?
// 10
//解释:内核解析后的代码是这样
var a;
var a;
a = 10;
[] == ![] //?
null == undefined //?
null === undefined //?
> [] == ![]判断逻辑
// true
// 解释: !号优先级最高。所以执行流程如下
[] == false
// 解析器判断到有Boolean类型时,会把Boolean类型转换成Number类型。因此下一步结果是
[] == 0
// 解析器解析到对象格式和数值格式进行判断时,会进行[].valueof()取值,因此下一步结果是
'' == 0;
// 到这一步的结果其实已经明朗了。 ''会转换成0 最终结果是
0 == 0;
// true;
---
> null == undefined
// true
//解释:执行流程如下
null == 0;
0 == 0;
---
>null === undefined的流程就不需要解释了吧。
// false
typeof null //?
//object
// 解释: 这是个历史遗留问题,因为JavaScript语言设计的时候是设计00开头为Object格式,但是因为null 代表000000 全零, 所以typeof就会在判断的时候就会错误认为null是object对象。
//1、
let arr = [{a:1},{a:2},{a:3}];
arr.map((item)=>{item.a++})
console.log(arr) //?
//2、
let arr = [1,2,3,4,5,6];
arr.map(item=>{item++});
console.log(arr) // ?
//解答1、
[{a:2},{a:3},{a:4}]
//解答2、
[1,2,3,4,5,6]
//解释:map本身就不能修改自身的,为什么1能修改呢。。 是因为1的序列是对象。因此修改的是内存指向的属性。2本身就是原始类型,因为map不修改自身的特性,因此++不会生效
作为状态管理容器对象,用来进行同意的状态管理
作为state的计算属性。
vuex中唯一修改state的方法,不能直接调用,需要通过commit来调用,可以使用常量来作为keys。
提交的是mutation操作,而不是直接改变,可以在里面写异步代码。
vuex中唯一修改state的方法,不能直接调用,需要通过commit来调用,可以使用常量来作为keys。
提交的是mutation操作,而不是直接改变,可以在里面写异步代码。
支持缓存,只有当依赖发生变化时才会进行重新计算,
不支持异步,在Computed里面执行异步操作时会无效。
不支持缓存,数据发生变化时会触发相应的操作
支持异步
能监听props、state、computed的值。
Vue数据挟持原理 - 知乎
Vue数据绑定原理之数据劫持 - 掘金
父子级传值
> 通过props 传递给子级数据,通过$emit来返回给父级监听变化。
兄弟级传值
> - this.$parent.$children
> - eventBus
> - VueX
跨组件传值
> - eventBus
> - VueX
> - 如果是孙字辈的层级关系 可以使用proivde/inject来定义传递。
keep-alive是vue-router中的一个api。他主要用来缓存组件内容,避免前进后退时反复刷新操作,
keep-alive原理
附带上掘金高赞答案。有遇到问过的。
hash模式
>通过内部监听hash的变化来切换不同的模块显示,一般不会重新刷新页面,只会进行异步请求,兼容性较好,唯一的缺点就在url上会有一个很丑的#,在对外业务网站上可能需要使用nginx来重定向掩盖这个#号的显示。
history模式
>使用Html5的api history功能实现,比较符合正常url显示使用,需要在服务器上配置支持,要不然刷新会进入404页面。
$nextTick是Vue实现的一个异步方法,内部通过原生Promise.then、MutationObserver、setImmediate来进行异步操作,如果执行环境不支持以上几个方法时,会降级使用setTimeout来代替。
Object.defineProperty对数组、对象的监听有缺陷,对数组的一些方法和对象的新增属性无法监听,Vue重写了数组的方法和内置了Set方法才解决了这个问题。
这个问题我只能说个大概,如果你要详细的完整的 只能去百度或者谷歌完整。
浏览器获取到html文档解析成DOM树
解析dom时请求资源
处理css文件,构建成CSSOM树
将DOM树和CSSOM树合并成渲染树
GPU根据渲染树构建布局层
最后再显示出来。
这三个方法都能改变当前this指向,第一个参数为this指向的对象,如果为null或者undefined则会重新指定为window。
fn.call(this,'a','b')
,call的改变只是临时改变this指向,立即执行。fn.call(this,['a','b'])
,apply和call一样都只是临时改变this指向fn.bind(this,'a','b')
,和call不同在于,它只是改变this指向,不会立即执行,并且是永久改变。这个题得看面试官要问什么,有时候他们是想问隐形强制转换,有些时候他们又只是想问能一些转换方法的了解,所以这个题很模糊。。
如果是隐形强制转换的话 就是 四则、==、判断等场景
如果是转换方法:
- 转Number (Number、parseFloat、parseInt)
- 转String (String、toString)
- 转Boolean (Boolean)
let _null = null;
if (_null === null){};
if (JSON.stringify(_null) === 'null'){};
if (!_null && _null !== undefined && _null !== 0){}
let arr = [123,123,5];
let arr2 = arr;
//使用函数扩展符:
let obj3 = {...obj}
//使用JSON.parse+JSON.stringify
//(很多教程上都把这个方法说是深拷贝,其实根本就只能算是一个伪深拷贝而已)
let obj = {
a:1,
b:[2,3,4]
}
let obj1 = JSON.parse(JSON.stringify(obj))
console.log(obj1)// {a: 1, b: Array(3)}
// 为什么会说JSON.parse+JSON.stringify是伪深拷贝呢,, 通过下面的例子能表现出来。
let _obj = {
a:1,
b:[2,3,4],
c:function(){console.log(obj.a)},//就增加了个c函数
}
let obj2 = JSON.parse(JSON.stringify(_obj));
console.log(obj2)//{a: 1, b: Array(3)}
//和obj1的结果基本一致,c完全就没办法拷贝出来,所以通过这种方法进行的拷贝不是完整意义上的深拷贝。只能说是伪深拷贝或者浅拷贝。
// 完全完整并且考虑一切场景的深拷贝建议使用lodash库(cloneDeep)来使用,
// 原生深拷贝的应该只有Object.assign。
let obj4 = Object.assign({},obj);
Array(10).fill(0)
require是commonJs的标准模块引入方法,
require引入的数据是全部引入,
require模块输出的是拷贝。
require是在运行时加载。import是Es6标准化的模块引入方法,
import能局部引入。
import输出的是模块的引用。
import是编译时输出。
首先localStorage、sessionStorage的存储大小是一致的,以key-value形式存储,一般在5m大小左右,这个根据不同浏览器的支持性不同有上下浮动差异,不会携带到请求上。
localStorage
> 永久存储,不手动清除时会永久保存下去。
sessionStorage
> 临时存储,关闭页面时会自动清除
cookie
> 以字符串方式存储,存储大小有限,http请求每次都会携带cookie到服务器中,这样无形会增加请求的压力,服务端能控制,需要手动设置存储时间,使用较为麻烦,需要自己手动封装函数才能get和set。
跨域的产生主要是浏览器的同源策略限制,它是浏览器的一个约定,也是浏览器最基础的安全功能,不同域名(主域名、子域名)、端口、协议(http、https)都会产生跨域限制,
几种解决跨域的方法
在主域名相同、子域名不同时,可以使用这个方法来进行通信。
//a.test.com
document.domain = 'test.com';
//b.test.com
document.domain = 'test.com';
JSONP
> 最核心的思想,通过创建一个script标签来向浏览器进行get请求(只能get请求),因为script的src请求是不受浏览器的同源策略影响。
<script src="http://test.com/data.php?callback=dosomething"></script>
<script type="text/javascript">
function dosomething(res){
// 处理获得的数据
console.log(res.data)
}
</script>
CORS
> 这个主要是让服务端打开CORS的支持,通过设置Access-Control-Allow-Origin来进行。
Node代理
> 如果是spa项目则只能在开发环境下使用,可以使用node的代理来暂时解决这个跨域问题,但是一般只能在开发环境下使用,发布生产环境下需要服务端CORS或者nginx转发处理
>
> 如果是ssr项目则可以发布到生产上使用。
//1、使用instanceof来判断。
Array instanceof Object //true
//不过instanceof来判断的方法也不准 就和typeof的情况一样。 所以这里就需要使用toString来获取到内部的[[class]]字段来判断了
//2、使用toString来判断内部[[class]];(推荐)
Object.prototype.toString.call([1,2,3,4]) //[object Array]
//通过这个方法来获取内部的[[class]] 然后在if判断使用===即可。
//3、使用原型链的constructor来判断 (注意 原型链会存在被改变的可能。)
[].constructor === Array
根据自己实际情况回答,大致能说出一些差不多了
请求
图片优化
html
css
其他性能优化
首先,这两个的逻辑都是通过延迟执行的思想进行实现的,不同在于应用场景,
function throttle(fn, delay){
let valid = true;
return function(){
if (!valid){
return false;
}
valid = false;
setTimeout(()=>{
fn();
valid = true;
},delay)
}
}
function debounce(fn, delay){
let times = null;
return function(){
if (times){clearTimeout(times)};
times = setTimeout(fn, delay)
}
}
在JavaScript中的二进制浮点数不是十分精确,这是计算机的标准问题。(没必要回答多精准吧,大概只要能说出为啥的就行)
//1、简单解决,加大倍数然后再除回去即可
(1000 + 2000) / 10000
//2、使用第三方精度库bignumber.js库
在程序设计中,若一个函数符合以下要求,则它可能被认为是纯函数:
在计算机科学中,函数副作用指当调用函数时,除了返回函数值之外,还对主调用函数产生附加的影响。例如修改全局变量(函数外的变量),修改参数或改变外部存储。
在某些情况下函数副作用会给程序设计带来不必要的麻烦,给程序带来十分难以查找的错误,并降低程序的可读性。严格的函数式语言要求函数必须无副作用。
#如无特别声明,该文章均为 shufu 原创,转载请遵循 署名-非商业性使用 4.0 国际(CC BY-NC 4.0) 协议,即转载请注明文章来源。
#最后编辑时间为: 2020 年 08 月 23 日