二阶段面试题——JavaScript

■ 符号说明

课题

常见重要

需要有印象的

Week1

说出JS数据类型有哪些

原始类型/基本类型:null、undefined、boolean、number、string、symbol、bigint

对象类型/引用类型/复杂/复合类型:

可调用/执行对象「函数」:function
标准普通对象:object
标准特殊对象:Array、Date、Math、RegExp、Error……
非标准特殊对象:Number、String、Boolean……

JS引用类型object、array还有哪些

可调用/执行对象「函数」:function
标准特殊对象:Array、Date、Math、RegExp、Error……
非标准特殊对象:Number、String、Boolean……

如何把数据强制转换为数值型

说明:面试概率低、工作概率高

语法:parseInt、parseFloat、Number

说出数据转换为布尔型的结果

null、undefined、0、NaN、空字符串结果是false、其他都是true

说出数据转换为字符串型的结果

都字符串

String(内容)、内容.toString()

说出数据转换为数值型的结果

Number(数据)

数据 结果
null 0
undefined NaN
true/false 1/0
123/NaN 123/NaN
‘’/‘123’/‘123a’ 0/123/NaN

说出下述隐式转换的结果

var result = 100 + true + 21.2 + null + undefined + “Tencent” + [] + null + 9 + false;

[] + [] // “”

[] + {} // “[object object]”

{} + [] // 0 对于编译器而言,代码块不会返回任何的值, 接着+[]就变成了一个强制转number的过程

true+true+true===3 // true

true-true // 0

true==1 // true

true===1 // false

9+“1” // 91

91-“1” // 90

[]==0 // true Number([]) 0

null 和 undefined 有什么区别?

undefined 表示未定义,新定义的变量没有赋值就是undefined

null表示清空,当一个变量不用的时候,除了等待网页关闭销毁,也可以把它赋值为null。此时游览器会进行一个回收也就是清空内存。

了解1

typeof(null)会是一个object。最初这么设计的原因为:通常用作一个空引用一个空对象的预期,就像一个占位符。typeof的这种行为已经被确认为一个错误,虽然提出了修正,出于后兼容的目的,这一点已经保持不变。

了解2

null == undefined    true
null === undefined   false

为什么 0.1 + 0.2 !== 0.3? 你如何解决这个问题?

因为 0.1 这样的数值用二进制表示你就会发现无法整除,

最后算下来会是0.0001100110011001…由于存储空间有限,位数是无限的,只能取近似。

代码:0.1 + 0.2 == 0.3 // false

代码:0.625 + 0.625 == 1.25 // true

进制转换规则:https://www.runoob.com/w3cnote/decimal-decimals-are-converted-to-binary-fractions.html

十进制整数 -> 转二进制

举例:(5).toString(2) // 结果 101
转换:除2反向取余
2| 5    --- 余1
 ------
2| 2    --- 余0
 ------
   1    
   
举例:(13).toString(2) // 结果 1101
转换:除2反向取余
2| 13    --- 余1
 ------
2| 6     --- 余0
 ------
2| 3     --- 余1
 ------
   1    
   
核对:https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fwww.pianshen.com%2Fimages%2F732%2Faf802c5be33b4934e6a6fec52a208734.png&refer=http%3A%2F%2Fwww.pianshen.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1642634303&t=293eccde3cc561a3ed2d38f8cf57eea9

十进制小数 -> 转二进制

举例:(0.625).toString(2) // 结果 0.101
转换:乘2正向取整
0.625*2=1.25======取出整数部分1 
0.25*2=0.5========取出整数部分0 
0.5*2=1==========取出整数部分1 

举例:(0.7).toString(2)  // 结果 0.101100110011001100110011 0011 ...
0.7*2=1.4========取出整数部分1 
0.4*2=0.8========取出整数部分0 
0.8*2=1.6========取出整数部分1 
0.6*2=1.2========取出整数部分1 
0.2*2=0.4========取出整数部分0  
0.4*2=0.8========取出整数部分0 
0.8*2=1.6========取出整数部分1 
0.6*2=1.2========取出整数部分1 
0.2*2=0.4========取出整数部分0
...

实战购物车小数计算精度丢失如何解决?

推荐解决: 浮点数转化成整数

实战使用:购物车结算时,商品价格(33.01)-优惠券价格(5),本应该是28.01,但是实际的结果是28.009999999999998

33.01 - 5 = 28.009999999999998

(33.01*100 - 5*100) / 100 = 28.01

es6 解决办法

es6 提供了 Number.EPSILON,这个值等于 2^-52 ,这个值非常非常小,在底层计算机已经帮我们运算好,并且无限接近 0,但不等于 0,这个时候我们只要判断误差值在这个范围内就可以说明是相等的。

function numbersequal(a,b){
    return Math.abs(a-b)

四舍五入

采用四舍五入方法,取了一个 10 位小数

function numTofixed(num) {
    if (typeof num == 'number') {
        num = parseFloat(num.toFixed(10))
    }
    return num;
}

numTofixed(0.1 + 0.2);
// 0.3

使用第三方库

math.js
bignumber.js
....

Week2

  • day1

说出变量在内存中的分配

栈:存 原始类型【名字】&【数据】、对象类型的【名字】&【堆地址】

堆:对象类型数据

说出变量赋值内存分配

原始类型:变量赋值,栈开辟内存直接存数据 -> 数据互补影响

对象类型:变量赋值,栈开辟内存,存放堆地址 -> 数据相互影响

切记切记切记:上课讲的形参也会出现传值、传地址问题

var num = 100      
function fn(n) {  
  // var 形参 = 实参
  // var n = num也就是100   原始类型 传值 不影响
  n = 200		 
  console.log(n)   // 打印: 200
}
fn(num)             
console.log(num)   // 打印: 100

// --------------------------

var obj = { name: 'Jack' }    

function fn(o) {	 	  
  // var 形参 = 实参
  // var o  = obj    对象类型赋值 传地址  相互影响
  o.name = 'Rose'	  	//    
  console.log(o.name) //  打印:Rose
}
fn(obj)				  
console.log(obj.name) //  打印:Rose

栈   		   						  								  	 堆
obj:地址1	  																  地址1:{ name: 'Jack改成Rose' }  
fn函数里面的o变量:地址1 函数调用完毕o变量就销毁了 
  • day2

判断是否是数组

方法1:通过语法 Array.isArray()

方法2:instanceof

方法3:原型链(constructor)

Array.isArray([1, 2 ,3])     	 // true
[1, 2 ,3] instanceof Array 	   // true
[1, 2 ,3].constructor === Array // true

Array.isArray('aa')     	 // false
'aa' instanceof Array 	   // false
'aa'.constructor === Array // false

如何交换两个变量的值

  • 临时变量法

    var a = 1
    var b = 2;
    var temp = a
    a = b
    b = temp

  • 加减法

    var a = 1
    var b = 2;
    a = a+b // a = 1+2 = 3
    b = a-b // b = 3-2 = 1
    a = a-b // a = 3-1 = 2

  • 解构赋值法 week3

    var a = 1
    var b = 2;

    [a, b] = [b, a]

  • 数组法

    var a = 1
    var b = 2
    a = [a, b]
    b = a[0]
    a = a[1]

  • 对象法

    var a = 1
    var b = 2
    a = { a: b, b: a }
    b = a.b
    a = a.a

  • 等等太多了

说出数组有哪些方法

·数据操作:shift/unshift/pop/push

遍历数据: forEach/map/filter/find/findIndex

工作常用:concat/join/indexOf/includes

学习常用:reverse/splice

了解:sort

说出数组哪些方法会改变原数据

数据操作:shift/unshift/pop/push

学习常用:reverse/splice

了解:sort

如何实现数组去重

方法1:通过es6新增的Set数据结构、和展开运算符去重 […new Set(重复数组)] week3

方法2:通过filter配合indexOf实现数组去重

方法3:定义空数组,通过forEach遍历重复的数组,通过indexOf判断当前值是否在数组中,不在就push

方法4:定义空数组,通过forEach遍历重复的数组,通过includes判断当前值是否在数组中,不在就push

方法5:利用对象的属性去重

方法6:…

// 方法1:通过es6新增的Set数据结构结果     [...new Set(重复数组)]
var arr = [1,2,3,2,3]
console.log([...new Set(arr)])


// 方法2:通过filter配合indexOf实现数组去重
var arr = [1,2,3,2,3]
var newarr = arr.filter(function(item, i) {
    // i=0  item值是1  arr.indexOf(item)  返回0  真
    // i=1  item值是2  arr.indexOf(item)  返回1  真 
    // i=2  item值是3  arr.indexOf(item)  返回2  真
    // i=3  item值是2  arr.indexOf(item)  返回1  假
    // i=4  item值是3  arr.indexOf(item)  返回2  假
    return i === arr.indexOf(item);
}) 
console.log(newarr)


// 方法3:定义空数组,通过forEach遍历重复的数组,通过indexOf判断当前值是否在数组中,不在就push
var newarr = []
var arr = [1,2,3,2,3]
arr.forEach(function(item, i) {
    if (newarr.indexOf(item) === -1)
    {
        newarr.push(item)
    }
})
console.log(newarr)


// 方法4:定义空数组,通过forEach遍历重复的数组,通过includes判断当前值是否在数组中,不在就push
var newarr = []
var arr = [1,2,3,2,3]
arr.forEach(function(item, i) {
    if (!newarr.includes(item))
    {
        newarr.push(item)
    }
})
console.log(newarr)

// 方法5:利用对象的属性去重
var arr = [1,2,3,2,3]
var newarr = []
var obj = {}
arr.forEach(function(item) {
	if (!obj[item]) {
		obj[item] = item // 出现记录
    newarr.push(item)
	}
})
  • day3

说出字符串常用方法

工作常用:数组、查找、替换、截取、大小、空格

split、replace、substr、toUpperCase/toLowerCase、trim、slice

学习:.length、lastIndexOf、repeat

JS如何去空格

说明: 通过 trim 去左右空格

留心:中间不能去

追问:JS如何去所有空格

解决:通过2021年 ES12新增语法replaceAll来解决

’ a b c ‘.replaceAll(’ ', ‘’)

解决:通过正则

’ a b c '.replace(/\s/g, ‘’)

  • day4&5

伪数组

长得像数组,能用少部分的属性 例如length 但是所有方法都不可以使用,在js中常见的伪数组有

document.qsa、arguments等等

谈谈你对this指向的理解

面试: 调用当前方法的对象 也就是谁调用的打印的就是谁

举例:

function fn1() {} 
fn1() // 最大全局变量window调用的

标签对象.事件类型 = 处理函数  // 标签对象触发后调用的

学习

普通函数   window
对象函数   对象本身
事件函数   事件源
定时器函数  window
箭头函数   父function中的this  父没有function就是window
自定义     call/apply/bind
构造函数   this === 实例化对象 === 公共空间/原型对象上方法中的this 


其他:事件、构造函数、公共空间/原型对象上方法-优先使用普通函数写法,当需要明确改变this指向的时候再换箭头函数,
其他全不优先使用箭头函数。

图片懒加载原理

好处:减少HTTP请求

原理:

1、监控滚动条滚动
2、获取总可视内容高度(可见视口高度+滚动条滚动高度)
3、获取所有图片
4、遍历步骤3(或这说:遍历伪数组)
5、在步骤4中判断,图片.offsetTop <= 步骤2    成立-修改src属性为data-src、失败-不管
6、节流防抖优化

留心:有时候图片高度获取怎么不到?



性能优化:网站首屏加载过慢如何解决

function lazyload() 
{
    // 1 每次获取最新的 可见视口 + 滚动的高度
    // 2 获取所有图片
    // 3 遍历
    // 4 判断:当前图片的.offsetTop < 步骤1(可见视口 + 滚动的高度)
    // 不成立-不管
    // 成立-修改图片的src地址 改成真是的  


    // 1 每次获取最新的 可见视口 + 滚动的高度
    var temp1 = window.innerHeight || document.documentElement.clientHeight  // 兼容ie
    var temp2 = document.body.scrollTop || document.documentElement.scrollTop // 兼容doctype
    var maxShowHeight = temp1 + temp2
    // 2 获取所有图片
    var imgs = document.querySelectorAll('img')
    // 3 遍历
    imgs.forEach(function(item, index) { // item就是每一个图片标签对象
        // 4 判断:当前图片的.offsetTop < 步骤1(可见视口 + 滚动的高度)
        // 不成立-不管
        // 成立-修改图片的src地址 改成真是的  
        // console.log(item, item.offsetTop , maxShowHeight)
        if (item.offsetTop < maxShowHeight)
        {
            // item.src = item.src-real  切记非标签默认属性 不能直接点
            item.src = item.getAttribute('src-real')
        }
    })
}

// TODO: 待后续进一步优化
// 首次
lazyload() 
// 后续
// window.onscroll = lazyload()  错误 undefined
window.onscroll = lazyload

Week3

谈谈你对事件流机制的理解

术语:事件发生时会在元素节点之间按照特定的顺序传播,这个传播过程即DOM事件流

学习:多个标签嵌套,事件触发-先检查当前标签,然后继续向上挨个检查祖先标签有没有事件 有就触发的现象就是事件流

DOM事件流分为三个阶段,分别为:

捕获阶段:事件从Document节点自上而下向目标节点传播的阶段;

目标阶段:真正的目标节点正在处理事件的阶段;

冒泡阶段:事件从目标节点自下而上向Document节点传播的阶段。

谈谈你对事件委托的理解

概念:利用事件冒泡机制处理指定一个事件处理程序,来管理某一类型的所有事件。

事件委托的好处:

  • 利用冒泡的原理,将事件加到父级身上,这样只在内存中开辟一块空间,既节省资源又减少DOM操作,提高性能
  • 可以为动态添加的元素绑定事件

正则

匹配手机号:/^1\d{10}$/.test(数据)

匹配邮箱:/^\w{2,20}@\w{2, 20}.(com|org|cn|edu)$/.test(数据)

匹配中文:/1+$/.test(数据)

去所有空格:str.replaceAll(’ ', ‘’) 或 str.replace(/\s/g, ‘’)

购物车去非数字:str.replace(/\D/g, ‘’)

es6新增语法

https://github.com/tc39/proposals/blob/main/finished-proposals.md

# 2015年 ES6

    修饰符:let、const
    解构赋值:let {键:变量名=默认值,...,键:变量名=默认值} = {键:值,....,键:值} !!!
    字符串相关扩展:模板字符串
    函数相关扩展:箭头函数!!!、函数形参默认值、rest参数 arguments
    对象相关扩展:对象简写!!!、链判断操作符 ?.  空判断操作符??
    数组相关扩展:find/findIndex/indexOf/includes
    新增原始类型:symbol、bigint  
    -----------
    //       重要      中等        中等       重要          重要
    其他新增:展开运算符、新增数据结构、循环forof、module模块化、class类等等
    
# 2016年 ES7:Array.prototype.includes
# 2017年 ES8:Async functions
# 2018年 ES9:Promise.prototype.finally
# 2019年 ES10
# 2020年 ES11:String.prototype.matchAll、import()、BigInt、Promise.allSettled、
# 2021年 ES12:String.prototype.replaceAll、Promise.any、
# 2022年 ES13:

es6常见问题汇总

  • 新增了哪些数据类型:symbol、bigInt

  • 新增的数据类型属于哪一类:原始类型

  • 数组如何去重:Set、filter+indexOf、forEach+indexOf、forEach+includes、对象

  • 新增数据类型有哪些分别有什么用

    Set数组去重
    Map对象的键不限于字符串

let、const

面试题1:let和const区别

相同点-都是块级作用域/不能重复定义
不同点-let修改/const不能修改

面试题2:const真的不能修改吗

原始类型:不可以
对象类型:可以

原理:const不可以修改栈里面的地址,但是可以修改堆里面的数据

面试题3:暂时性死区

let前面的区域就是暂时性死区,必须先定义再使用

面试题4:let和const如何选

普遍用let、明确后期不改用const 例如Date、xhr、promise对象等

const d = new Date() 

面试题5:var和let区别

var 函数作用域   可以重复定义 可以修改
let 块级作用域  不能重复定义 可以修改

bind 和 call/apply 的区别

fun.call(thisArg, param1, param2, ...)
fun.apply(thisArg, [param1,param2,...])

fun.bind(thisArg, param1, param2, ...)

是否立刻执行

  • call/apply 改变了函数的 this 上下文后 马上 执行该函数。
  • bind 则是返回改变了上下文后的函数, 不执行该函数 。

返回值的区别

  • call/apply 返回 fun 的执行结果。
  • bind 返回 fun 的拷贝,并指定了 fun 的 this 指向,保存了 fun 的参数。

手写bind/call/apply 原理

  • call

apply


bind


Week4

原型、原型链概念

原型:js给每个函数分配的公共空间,减少内存占用

原型链:多个原型的集合,当调用对象的属性或方法时,先自身找,找不到去原型链上找,一直找到Object构造函数得原型链

构造函数new干了啥

1 搞obj空对象

2 给obj增加proto属性 并且指向 构造函数的.prototype

3 将 构造函数this中的数据 全部放到obj上

4 返回obj对象

// let Sym = fn
function Sym() {
  this.a = 1;
  this.b = 2;
  this.c = 3;
}
// 第一种使用方式:当变量赋值用
let data1 = Sym;
console.log(data1); // 打印:函数本身
// 第二种使用方法:当函数用
let data2 = Sym();
console.log(data2); // 打印:undefined  但是window多了a、b、c三个属性
// 第三种使用方法:new关键词
let data3 = new Sym();
console.log("new关键词:", data3); // 打印:对象

/*
栈                  堆
data1: 地址1        地址1:function Sym() {  this.a = 1; this.b = 2;  this.c = 3; }
data2:undefined    地址2:{a:1,b:2,c:3, __proto__:}
data3:地址2
 */

function _new(当前new的构造函数) {
  // let obj = {};
  // obj.__proto__ = 当前new的构造函数.prototype;
  
  let obj = Object.create(当前new的构造函数.prototype); // !!!!
  //   obj.a = 1;
  //   obj.b = 2;
  //   obj.c = 3;
  当前new的构造函数.call(obj);
  return obj;
}
let data4 = _new(Sym);
// 需求:封装_new普通函数仿写new关键词效果
// 分析:_new普通函数里面做哪些时
// - 必须返回一个对象
console.log("通过_new仿写new:", data4);

箭头函数与普通函数区别?能不能作为构造函数

  1. 语法更加简洁、清晰
  2. 箭头函数不会创建自己的this
  3. 箭头函数继承而来的this指向永远不变
  4. .call()/.apply()/.bind()无法改变箭头函数中this的指向
  5. 箭头函数不能作为构造函数使用
  6. 箭头函数没有自己的arguments
  7. 箭头函数没有原型prototype
  8. 箭头函数不能用作Generator函数,不能使用yeild关键字

https://juejin.cn/post/6844903805960585224##heading-2

更精确判断数据类型

// typeof 原始类型:除了null是object其他都是自身

// console.log(typeof null); // object
// console.log(typeof undefined);
// console.log(typeof true);
// console.log(typeof 123);
// console.log(typeof "sdafadsf");
// console.log(typeof Symbol());
// console.log(typeof 1111n);

// typeof 对象类型:除了function是function其他都是object

// 问题:你就无法根据不同类型做不同逻辑处理
// 比如:判断是数组  判断是null
// 解决:Array.isArray()  等等单独处理
// 然后:也可以统一处理

// 语法:Object.prototype.toString.call(数据)

// console.log(Object.prototype.toString.call(null));
// console.log(Object.prototype.toString.call(undefined));
// console.log(Object.prototype.toString.call(true));
// console.log(Object.prototype.toString.call(123));
// console.log(Object.prototype.toString.call("sdfdsf"));
// console.log(Object.prototype.toString.call(Symbol()));
// console.log(Object.prototype.toString.call(1111n));
// console.log(Object.prototype.toString.call(function () {}));
// console.log(Object.prototype.toString.call({}));
// console.log(Object.prototype.toString.call([]));

function getType(data) {
  let result = Object.prototype.toString.call(data);
  //   console.log(result.substr(8, result.length - 1 - 8));
  console.log(result.slice(8, -1));
}

getType(null);
getType(undefined);
getType(true);
getType(123);
getType("sdfdsf");
getType(Symbol());
getType(1111n);
getType(function () {});
getType({});
getType([]);

Object构造函数上有哪些语法

重要

Object.defineProperty() 三阶段才详细讲 现在【禁止】百度这个

Object.keys() 获取对象的所有键 返回数组

Object.values() 获取对象的所有值 返回数据

Object.create() 创建对象(特色 基于指定原型造对象 场景1:new原理优化,场景2:三阶段vue )


Object.assign() 合并对象

Object.prototype.constructor 所属构造函数

Object.prototype.toString() 转字符串

Week5 高频周

  • day1

输入网址浏览器干哈了(简单版)

打开浏览器输入网址回车 ->  去DNS服务器找网址对应的IP地址 -> 找不到【无法访问此网站】 找到了【根据ip地址去请求服务器】 -> 服务器返回数据 -> 【浏览器解析】

脚下留心:浏览器其实返回的是index.html的数据,然后在解析的过程中,遇到link、script、img等 再次发送请求拿数据然后解析

谈谈你对HTTP理解

面试:HTTP是超文本传输协议,规定了客户端和服务端如何通信,然后由两个部分组成分别是请求、响应

学习:就是一个规则,你必须按照这个规则才可以和后端交互拿数据






概念:超文本传输协议
作用:规定客户端和服务端通信技术
场景:网页、APP等
请求组成:请求行(地址/状态吗/请求方式)、请求头(ua、content-type、token、cookie)、请求体(接口参数)
响应组成:响应行(同上)、响应头(暂略)、响应体(接口数据调试错误)

HTTP周边:HTTP动词

HTTP周边:状态码

HTTP周边:请求头参数

  • HTTP动词(请求方式 method)

    明确:form标签也就是w3c就是遵循http规则设计的 但是它的请求方式仅仅只有两种get、post 其他不支持
    但是:我们目前也用get、post 等到三阶段就用n多
    然后:面试就问你有哪些
    种类:常用的get查询、post增加数据、put修改数据、delete删除数据
    实际:java攻城狮就用get查询 增删改都用post

  • 状态码

    2xx 200 成功 201 成功并且服务器创建了新数据
    3xx 301 站内跳转 302 站外跳转 304 浏览器缓存
    4xx 400 你传递给后端的参数 401 密码错误 403 没有权限 404文件不存在 405 请求方式有误
    5xx 500 服务器有误

  • 请求头参数

    ua、content-type、token、cookie

HTTP周边:强制缓存、协商缓存

强制缓存:就是文件直接从本地缓存中获取,不需要发送请求。

响应头 Cache-Control : 86400

expires

协商缓存/对比缓存

在响应头部 Response Headers 中,有两种资源标识:

  • Last-Modified 资源的最后修改时间,对应请求头为 If-Modified-Since ;
  • Etag 资源的唯一标识,所谓唯一,可以想象成时人类的指纹,具有唯一性;但 Etag 的本质是一个字符串;对应请求头为 If-None-Match 。

Last-Modified 和 Etag

  • 当响应头部 Response Headers 同时存在 Last-Modified 和 Etag 的值时,会优先使用 Etag ;
  • Last-Modified 只能精确到秒级;
  • 如果资源被重复生成,而内容不变,则 Etag 更精确。

https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fupload-images.jianshu.io%2Fupload_images%2F5727550-fd8811d7defb8956.png&refer=http%3A%2F%2Fupload-images.jianshu.io&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1650391279&t=0419cce2bf932f3c0e83594c4d177742

请求行/响应行:method、url、status

请求头headers、cookie、content-type、ua、if-none-match、if-modified-since 
请求体:请求参数

响应头:content-type 告诉浏览器如何解析数 、etag、last-modified、 Cache-Control...
响应体:响应的数据

get和post有什么区别

安全角度:post相对比get安全    原因get会在地址栏 因为在地址栏就有访问历史记录  post请求体不会存在来留痕
数据角度:post相对传输的数据比get多      get会受到不同浏览器地址栏长度限制,post服务器配置


上传图片:2M 一般只能上传png、jpg  gif不允许

谈谈你对http、https的理解,有什么区别

http超文本通讯协议	80
https也是超文本通讯协议  相对http更加安全  443

-day2

谈谈你对骨架屏的理解

骨架屏就是在页面数据尚未加载前先给用户展示出页面的大致结构,直到请求数据返回后再渲染页面,补充进需要显示的数据内容。常用于文章列表、动态列表页等相对比较规则的列表页面。 很多项目中都有应用:ex:饿了么h5版本,知乎,facebook等网站中都有应用。

参考

https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2018/10/15/16676db12e4232bb~tplv-t2oaga2asx-watermark.awebp

谈谈你对节流防抖的理解

回答:节流防抖都是用来进行项目优化,减少代码触发的频率,同时又不影响实际效果

节流:一段时间内只执行一次

防抖:一段时间内可重复执行,但是要把之前的取消掉

思路:事件中代码用定时器包起来,然后再前面加判断

问题:输入教主会执行 jiaozhu教主 这么多次
标签对象.oninput = function() {
    console.log(1)
}

节流:一段时间内 1次
let t
标签对象.oninput = function() {
    if (t) return
    t = setTimeout(() => {
        console.log(1)
        t = null
    }, 3000)
} 

防抖:一段时间内可重复执行,但是要把之前的取消掉
let t
标签对象.oninput = function() {
    if (t) clearTimeout(t)
    t = setTimeout(() => {
        console.log(1)
        t = null
    }, 3000)
}

谈谈你对高阶函数的理解

简而言之,高阶函数是那些将其他函数作为参数或返回其他函数的函数。在高阶函数中作为参数传递的函数被称为回调。

高阶函数的优势:

  • 它们可以帮助我们写出简洁的代码。
  • 由于是简洁的代码,调试工作会更加容易。

现在 JavaScript 有一些内置的高阶函数,你可能已经在不知不觉中就使用它们了,例如 filter()、reduce()、sort() 和 forEach()。

function+ajax+callback

/**
 * 发送get异步请求
 * @param {stirng} url 请求地址
 * @param {object} params 请求参数,格式  {参数名:数据,....,参数名:数据}
 * @param {function} callback 回调函数
 * @param {object} headers 自定义请求头   {键:值, token: '数据', 'content-type': '数据'}
 */
function get(url, params, callback, headers = {}) {
  // 一、创建xhr对象
  const xhr = new XMLHttpRequest();
  // 二、设置请求方式、请求地址
  let temp = [];
  for (let key in params) {
    temp.push(`${key}=${params[key]}`);
  }
  xhr.open("get", `${url}?${temp.join("&")}`);
  // 三、监听请求状态
  xhr.onreadystatechange = () => {
    // 判断返回
    if (xhr.readyState === 4) {
      // 判断状态
      if (xhr.status === 200) {
        // 获取数据
        let res = JSON.parse(xhr.responseText);
        // 逻辑处理
        // console.log(res);
        callback(res);
      } else {
        console.log(xhr.status);
      }
    }
  };
  // 四、发送
  for (let key in headers) {
    xhr.setRequestHeader(key, headers[key]);
  }
  xhr.send();
}

function+ajax+promise

/**
 * 发送get请求
 * @param {stirng} url 请求地址
 * @param {object} params 请求参数
 * @param {object} headers 自定义请求头
 * @returns 
 */
function get(url, params, headers = {})
{
    const p = new Promise((resolve, reject) => {
        // 一、创建xhr对象
        const xhr = new XMLHttpRequest
        // 二、设置请求方式、请求地址
        let temp = []
        for (let key in params) {
            temp.push(`${key}=${params[key]}`)
        }
        xhr.open('get', `${url}?${temp.join('&')}`)
        // 三、监控请求状态
        xhr.onreadystatechange = () => {
            // 返回
            if (xhr.readyState === 4)
            {
                // 状态码
                if (xhr.status === 200)
                {
                    // 获取数据
                    let res = JSON.parse(xhr.responseText)
                    // 逻辑处理
                    // console.log(res)
                    resolve(res)
                } else {
                    // console.log(xhr.status)
                    reject(xhr.status)
                }
            }
        }
        // 四、发送请求
        for (let key in headers) {
            xhr.setRequestHeader(key, headers[key])
        }
        xhr.send()
    })

    return p  // 后期谁调用get就会拿到promise对象 通过then就可以获取数据 实现不同的业务逻辑
}

-day3

谈谈你对promise的理解

概念:ES6异步编程解决方案

作用:常用于封装ajax异步请求

说一下promise原理

底层创建了Promise构造函数,并且给该构造函数绑定了then、catch、finally等原型方法,和reject、resolve、all、race等静态方法。

说一下promise几个状态

进行中、成功了、失败了

const p = new Promise((resolve, reject) => {
	// 发送异步请求
	// 默认触发的  所以是进行中状态
})

追问:为什么状态不可逆

回答:底层Promise构造函数中会判断当前是否是pending进行中状态,不是就会终止代码执行 所以不可逆

// 明确:底层Promise源码大概是这么写的
function Promise(callback) {

    this.PromiseState = 'pending'
    this.PromiseResult = undefined
    
    const resolve = data => {
        if (this.PromiseState != 'pending') return
        this.PromiseState = 'fulfilled'
        this.PromiseResult = data
    }
    const reject = error => {
        if (this.PromiseState != 'pending') return
        this.PromiseState = 'rejected'
        this.PromiseResult = error
    }

    try {
        // callback(参数1, 参数2)
        // callback(() => {}, () => {})
        callback(resolve, reject)
    } catch(error) {
        reject(error.toString())
    }
}

// 然后:你写
const p = new Promise((resolve, reject) => {
	resolve(数据1)
	reject(数据2)
})

追问:状态之间怎么转换?

回答:通过promise的then机制,来实现状态切换

const p = new Promise((resolve, reject) => {
	resolve(数据1)
})

p
.then(res => {
	return Promise.resolve('失败的')
})

Promise.all、Promise.allSettled区别

Promise.all( 数组里面是一个个Promise对象 ) 有一个失败就走失败

Promise.allSettled( 数组里面是一个个Promise对象 ) 没有失败

Promise.race、Promise.any区别

Promise.race( 数组里面是一个个Promise对象 ) 根据第一个最快返回的决定状态

Promise.any( 数组里面是一个个Promise对象 ) 有一个成功就是then 都失败 才是catch

  • day4

跨域相关

  • 跨域导致原因

    概念:当请求一个url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域
    原因:浏览器安全策略/同源策略
    后果:不能跨网站操作发送ajax请求、WEB存储等

  • 跨域解决方案

    前端
    前端代理 http-proxy-middleware
    谷歌插件
    谷歌命令

    jsonp

    websocket
    postMessage

    留心:上述不管哪种方式仅自己可以使用
    最终:开发就选上面其中一种,最终后端:cors法 原理响应头告诉浏览器任何人都可以使用;nginx 反向代理

谈谈你对同步异步的理解

会被加入到浏览器队列的代码称之为异步代码,例如 ajax、setTimeout/setInterval、Promise.then 等等,他们不按书写✍顺序执行打印结果的代码

按照书写顺序执行打印的代码称之为同步代码

谈谈你对async,await的理解

async、await是generator的语法糖,通过async修饰function、await修饰promise,

底层将await后面的表达式会先执行一遍,再将await下一行代码加入到微任务中。

谈谈你generator的理解

es6新增的语法,通过*号修饰函数,当调用函数的时候返回一个generator对象,通过next函数迭代获取函数内部的数据,当遇到yield就会暂停,再次写next才会继续

说出浏览器运行机制

浏览器主进程,负责创建和销毁tab进程、负责交互前进后退、负责网页文件下载等

渲染进程:每个tab对应一个渲染进程,下面有GUI渲染线程、JS引擎线程、事件线程、定时器线程、异步请求线程

GPU进程:负责3D图绘制

第三方插件进程:负责第三方插件处理,例如跨域、广告拦截插件等

说出浏览器输入网址干了啥

浏览器输入网址回车
去DNS服务器找网址对应的IP地址 
根据IP地址加端口访问服务器软件 
服务器返回数据
浏览器通过renderer是渲染进程处理,
其中GUI线程负责页面渲染、JS引擎线程负责解析JS代码

说出JS为什么是单线程

先看一个比喻

进程就是一个公司,每个公司都有自己的资源可以调度;公司之间是相互独立的;而线程就是公司中的每个员工(你,我,他),多个员工一起合作,完成任务,公司可以有一名员工或多个,员工之间共享公司的空间

什么是进程?

进程:是cpu分配资源的最小单位;(是能拥有资源和独立运行的最小单位)

什么是线程?

线程:是cpu调度的最小单位;(线程是建立在进程的基础上的一次程序运行单位,一个进程中可以有多个线程)

浏览器是多进程的

放在浏览器中,每打开一个tab页面,其实就是新开了一个进程,在这个进程中,还有ui渲染线程,js引擎线程,http请求线程等。 所以,浏览器是一个多进程的。

大家都在说js是单线程的,但是为什么要设计成单线程?

这主要和js的用途有关,js是作为浏览器的脚本语言,主要是实现用户与浏览器的交互,以及操作dom;这决定了它只能是单线程,否则会带来很复杂的同步问题。 举个例子:如果js被设计了多线程,如果有一个线程要修改一个dom元素,另一个线程要删除这个dom元素,此时浏览器就会一脸茫然,不知所措。所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变.

为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。

说出JS是单线程 为什么不存在执行效率问题

JS是单线程执行程序代码,形成一个执行栈,挨个处理;

但是遇到特别耗费时间的代码 ,例如异步请求,事件等,

不会堵塞等待执行,而是交给浏览器其他线程处理后,再丢到执行栈中处理,从而保证还行效率

谈谈你对Event Loop的理解

Event Loop即事件循环,

是指浏览器或Node的一种确保javaScript单线程运行时不会阻塞的一种机制,

也就是我们经常使用异步的原理。

种类:浏览器的Event Loop、Node.js中的Event Loop

谈谈你对浏览器的Event Loop理解

浏览器输入网址服务器响应数据后,

浏览器会通过render进程开始解析工作

GUI线程负责页面渲染

JS引擎线程负责执行JS代码

遇到异步代码会交给其他线程处理,然后放到队列中,

事件循环主要是从队列中取出代码放到执行栈中交给js引擎线程处理

说出宏任务、微任务各有哪些

单词含义:I input 输入、 O output 输出

用户角度IO操作:鼠标键盘-是计算机输入信息,显示器-是输出设备

电脑角度IO操作:CPU、内存与其他设备之间数据转移过程就是IO操作,例如数据从磁盘读到内存,或者内存写到磁盘

编程角度IO操作:进程读取数据操作

  • 宏任务:

    整体代码(script)
    定时器(setTimeout、setInterval)
    I/O操作(DOM事件、AJAX异步请求)

    setImmediate(node环境)
    requestAnimationFrame(浏览器环境)

  • 微任务

    Promise.then catch finally
    async/await(底层还是promise)
    process.nextTick(node环境)
    MutationObserver(浏览器环境)

说出先执行宏任务还是微任务

算整体代码script:1宏n微

不算整体代码script:先n微,再1宏 -> n微,再1宏 -> n微

  • day5

前端存储有几种

回答1:常用的2种,主要是cookie、h5存储;浏览器其实还有web sql、indexdb

H5
cookie
web sql
indexdb

cookie和h5的区别

回答2:答案在下面

性能角度:相对而言H5存储性能比COOKIE高

存储空间:H5单条数据5M左右、COOKIE单条数据4KB

生命周期:

cookie 			       自己设置,如果不设置浏览器关闭销毁
h5 localStorage    永久
h5 sessionStorage  窗口 

如何实现localStorage7天过期

回答3:

cookie.  1*1000*60*60*24*7


login.html

步骤1:存数据的时候 也额外存一个键叫 expires  记录时间
	localStorage.setItem('uname', 'value')
	localStorage.setItem('expires', (new Date).getTime() + 1*1000*60*60*24*7 )
member.html
步骤2:使用的时候增加判断
	// 公式:当前使用时间 > 存储时间 (过期了)
	// 举例:比如你是2月1号存 
	// 然后:你加了7天  2月8号过期
	// 最后: 2月5号 > 2月8号   不成立 没过期
	// 最后: 2月11号 > 2月8号  成立  过期

如何实现七天免登录

1、登录的时候存一个时间 当前时间+7天时间戳

localStorage.setItem('uname', 'value')
localStorage.setItem('expires', (new Date).getTime() + 1*1000*60*60*24*7 )

2、后期用的时候判断是否过期

会员中心

登录是如何实现的

步骤1:登录按钮绑定点击事件

步骤2:事件处理函数中 获取表单数据,请求接口

步骤3:失败-弹框提示,成功-提示、存储、跳转

Week6 较为低频

xhr、$ .get、$.ajax区别

问题1:XMLHttprequest和JQ代码的区别
回答1:
相同点:都可以发送异步请求
不同点:JQ是基于XMLHttprequest封装了  1 更简单、 2 解决兼容问题等

问题2:$.get、$.ajax区别
回答2:一样,唯一区别$.ajax比$.get语法上更强

jq你会吗,比如操作样式用什么属性…

获取标签:CSS选择器、筛选选择器、过滤选择器等
操作标签:样式css方法、类addClass、属性attr/prop、内容val/html、节点append/remove
事件:$().事件类型()     $().on(事件类型,子,处理函数)
异步请求:$.get、$.post、$.ajax
动画:$.animate() 
网页加载完毕:$(function(){})    (留心:和window.onload区别 
对象相互转换:$()[索引]   或者  $(JS标签对象

Week7 高频

说出几个常用的git命令

配置SSH  ssh-keygen
用户配置  git config global user.name/email ''
获取代码 
git clone 仓库地址
git init 
git remote add origin 仓库地址
git remote remove origin 仓库地址
增删改:git add . / commit  / push

周边(忽略文件) .gitignore
周边(代码冲突) git pull git status 
周边(日志回滚   git log 、 git reset 
周边(分支)    git branch / git checkout ...
周边(其他)    git stash、git tag、git pull、git fetc、git merge

说出git工作流,说一下你们日常开发工作流

git flow

功能分支 -> 合并到dev分支
dev分支 -> 合并到release分支
release分支 -> 合并到master分支

说下git如何解决代码冲突

1 pull
2 status、手动解决
3 重新add/commit/push

说一下你在开发登陆时,线上出现bug了如何维护

基于master创建hotfix分支,
解决了再 push 并 merge 到 master、dev 上

克隆获取的是默认分支代码,如何获取其他分支代码

git checkout -b 分支名 分支名

用过哪些git图形化可是软件

编辑器
sourcetruee  
等等

说下git和svn区别

git是分布式、svn是集中式
svn相对容易冲突
等

  1. \u4e00-\u9fa5 ↩︎

你可能感兴趣的:(javascript,前端,java)