文章目录
- css
- javascript
- 1.new方法会执行什么
- 2. ajax fetch axios区别
- 3. JS由哪三部分组成
- 4. JS有哪些内置对象
- 5.操作数组的方法
- 6.JS对数据类型的检测方式有哪些
- 7.说一下闭包,闭包有什么特点
- 8.前端的内存泄漏怎么理解
- 9.事件委托是什么
- 10.基本数据类型和引用数据类型的区别
- 11.说一下原型链
- 10.new 操作符具体做了什么
- 11.js是如何实现继承的
- 12.js的设计原理是什么
- 13.js关于this指向的问题
- 14.script标签里的async和defer有什么区别
- 15.setTimeout最小执行时间
- 16.ES5和ES6有什么区别
- 17.ES6有哪些新特性
- 18.什么是柯里化,如何实现柯里化
- 19.窃取对象
- 20.如何使用Proxy实现链式调用
- 21.call apply bind三者区别
- 22.递归
- 23.如何实现一个深拷贝
- 24.如何实现事件循环
- 25.ajax是什么,如何实现?
- 26.get和post的区别
- 27.promise内部原理是什么?它的优缺点是什么?
- 28.promise和async和await有什么区别
- 29.浏览器的存储方式有哪些
- 30.token存在sessionstorage还是localstorage
- 31.token的登录流程
- 32.页面渲染的过程是怎样的
- 33.DOM树和渲染树有什么区别
- 34.精灵图和base64的区别是什么
- 35.svg格式了解多少
- 36.了解过JWT吗
- 37.npm的底层环境是什么
- vue
- 项目工程
css
1. css盒模型
1)盒子的组成:
内容content、内边距padding、边框border、外边距margin
2)盒子的类型:
标准盒模型
margin + border + padding + content
IE盒模型
margin + content(包含border+padding)
3)控制盒模型的模式:
box-sizing
:
content-box (默认值,标准盒模型)
border-box; (IE盒模型)
2. css选择器的优先级
1) css的特性:
继承性、层叠性、优先级
优先级:
写css的时候,会给同一个元素添加多个样式,此时谁的权重高就显示谁的样式
!important > 行内样式 > id > 类/伪类/属性 > 标签选择器 > 全局选择器
3.隐藏元素的方法有哪些
常用的:
- display : none; ----元素在页面上消失,不占据空间位置,在dom结构上是存在的,是一种不可见的状态
- opacity : 0;(元素透明度为0) -----元素不可见,占据空间位置
- visibility:hidden; ------让元素消失,占据空间位置
其他的:
postion:absolute (将元素移除我的页面)
clip-path (将元素给剪切掉)
4. px 和 rem的区别是什么
px是像素,显示器上给我们呈现画面的像素,每个像素的大小是一样的,绝对单位长度
rem,相对单位,相对于html根结点的font-size的值,
直接给html节点的font-size:62.5%,则1rem = 10px; 16px*62.5%=10px
弹幕:这里没说1rem=16px(默认),之所以会等于10px,就是根节点设置为62.5%
<style>
html{
font-size: 62.5%
}
.boxs{
font-size:1rem;//相当于10px
}
</style>
<body>
<div class="boxs">像素的单位</div>
</body>
5.重排和重绘有什么区别
**重排(回流):**布局引擎会根据所有的样式 ,计算出 盒模型在页面上的位置和大小
浏览器的渲染机制:
**重绘:**计算好 盒模型的位置、大小和其他的一些属性后,浏览器会根据每个盒模型的特性进行绘制
到Render Tree(渲染树),进行重排(回流),再往后面Painting就开始进行重绘
比如:在dom里面增加一个div,导致浏览器需要重新计算当前元素的位置大小等,所以要进行回流(重绘),
如果只是修改一个div的颜色,字体颜色等,不需要重新计算位置大小等,就是进行重排。
总结:
对dom的大小、位置进行修改后,浏览器需要重新计算元素的这些几何属性,就叫重排。
对dom的样式进行修改,比如color和background-color,浏览器不需要重新计算几何属性的时候,直接绘制了该元素的新样式,
那么这里就只触发了重绘。
6.让元素水平垂直居中的方式有哪些
- 定位 + margin
<style>
* {
margin: 0;
padding: 0;
}
.father {
width: 400px;
height: 400px;
border: 1px solid #ccc;
position: relative;
}
.son {
position: absolute;
width: 200px;
height: 200px;
background-color: red;
top: 0;
right: 0;
bottom: 0;
left: 0;
margin: auto;
}
</style>
</head>
<body>
<div class="father">
<div class="son"></div>
</div>
<script></script>
</body>
- 定位 + transform
.father {
width: 400px;
height: 400px;
border: 1px solid #ccc;
position: relative;
}
.son {
position: absolute;
width: 200px;
height: 200px;
background-color: red;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
3.flex布局
.father {
width: 400px;
height: 400px;
border: 1px solid #ccc;
display: flex;
justify-content: center;
align-items: center;
}
.son {
width: 200px;
height: 200px;
background-color: blue;
}
4.grid布局
5.table布局
弹幕:其实这里分情况,元素的高度有已知或者未知,方法不同。margin+定位那里需要区分子元素宽高已知和未知,flex不需要。
7.css哪些属性可以继承,哪些不可以继承
css的三大特性:继承、层叠、优先级。
继承:子元素可以继承父类元素的样式。
可以被继承的:
1.字体属性:font
2.文本的的一些属性:line-height
3.元素的可见性:visibility:hidden
4.表格布局的属性:border-spacing
5.列表的属性:list-style
6.页面的样式属性:page
7.声音的样式属性
8.有没有用过预处理器
预处理器语言增加了变量、函数、混入 等强大的功能
SASS LESS
变量:
@global:#ccc
.box{
color:@global
}
javascript
1.new方法会执行什么
参考一:
【前端面试题】new 方法会执行什么?(居然之家二面)_哔哩哔哩_bilibili
2. ajax fetch axios区别
前端课堂:ajax、fetch、axios区别是啥?_哔哩哔哩_bilibili
fetch底层就是主动帮你创建了Request对象。
3. JS由哪三部分组成
1.ECMAScript
js的核心内容,描述了语言的基础语法,比如var,for,数据类型(数组、字符串)
2.文档对象模型(DOM)
DOM把整个HTML页面规划为元素构成的文档
3.浏览器对象模型(BOM)
对浏览器窗口进行访问和操作
4. JS有哪些内置对象
String Boolean Number Array Object Function Math Date RegExp…
常用的:
Math
abs()—算绝对值、sqrt()—算开平方、max()、min()
Date
new Date()、getYear()
Array
String
concat()、length、slice() 、split()、
5.操作数组的方法
push()、pop()、sort()、splice()、unshift()、shift()、reverse()、concat()、join()、
map()、filter()、every()、some()、reduce()、isArray()、findIndex()
哪些方法会改变原属组
splice、push、pop、unshift、shift、sort、reverse
6.JS对数据类型的检测方式有哪些
1.typeof() ----》判断基本的数据类型,不能用于引用数据类型。
2.instanceof ----》只能判断引用数据类型,而不能判断基本数据类型
[] instanceof Array //true
'a' instanceof String //false
3.constructor ---》几乎可以判断基本数据类型和引用数据类型
('abc').constructor === String //true
局限性:如果声明了一个构造函数,并把它的原型指向了Array的时候,就判断不出来了。
4.Object.prototype.toString.call()===>完美解决上面的各种局限性
let opt = Object.prototype.toString
console.log(opt.call(2)) //[object Number]
console.log(opt.call(true)) //[object Boolean]
console.log(opt.call('aaa')) //[object String]
console.log(opt.call([])) //[object Array]
console.log(opt.call({})) //[object Object]
7.说一下闭包,闭包有什么特点
什么是闭包:
函数嵌套函数,内部函数被外部函数返回并保存下来时,就会产生闭包。
function fn(a){
return function(){
console.log(a)
}
}
let fo = fn("abcd")
特点:可以重复利用变量,并且这个变量不会污染全局的一种机制:这个变量是一直保存在内存中,不会被垃圾回收机制回收
缺点:闭包比较多的时候,会消耗内存,导致页面性能下降,在IE浏览器中才会导致内存泄漏。
使用场景:防抖,节流,函数嵌套函数避免全局污染的时候。
8.前端的内存泄漏怎么理解
js里已经分配内存地址的对象,但是由于长时间没有释放或者没办法清除,造成长期占用内存的现象,会让内存资源大幅度浪费,
最终导致速度慢,甚至崩溃的情况。
垃圾回收机制
因素:一些未声明直接赋值的变量、一些未清空的定时器、过度的闭包、一些引用元素没有被清除。
9.事件委托是什么
又叫事件代理
原理就是利用事件冒泡的机制来实现,也就是说把字元素的事件绑定到了父元素的身上。
如果子元素阻止了事件冒泡,那么委托也就不成立了。
阻止事件冒泡:event.stopPropagation()
addEventListener(‘click’,函数名,true/false) 默认是false(事件冒泡),true(事件捕获)
事件冒泡优点:提高性能,减少事件的绑定,减少内存的占用。
10.基本数据类型和引用数据类型的区别
基本数据类型:保存在栈内存当中,保存的是一个具体的值
Sring、Number、Boolean、null、undefined
引用数据类型:保存在堆内存当中 ,声明一个引用类型的变量时,它保存的是引用数据类型的地址
Object Function Array
假如声明两个引用类型,同时指向一个地址的时候,修改其中一个,那么另外一个也会改变。
11.说一下原型链
讲解一:9.说一下原型链_哔哩哔哩_bilibili9.说一下原型链_哔哩哔哩_bilibili
原型:就是一个普通的对象,它是为构造函数的实例 共享属性和方法:所有实例中引用的原型都是同一个对象
案例:
function Person() {
this.say = function () {
console.log('唱歌')
}
}
let p1 = new Person()
let p2 = new Person()
p1.say()//唱歌
p2.say()//唱歌
都会打印唱歌,但是在内存当中是占用两个内存的。为了解决这个问题,js发明了prototype。
prototype就是把这些方法挂载在原型上面,这样内存只需要存一份就好了,剩下所有的实例都可以共享它。
Person.prototype.look = function () {
console.log('sz')
}
let p1 = new Person()
let p2 = new Person()
p1.look()//sz
p2.look()//sz
上面在内存里面只占用一份。
__proto__
可以理解为指针,实例对象中的属性,指向了构造函数的原型(prototype
)
p1.__proto__ === Person.prototype //true
原型链:
就是一个实例对象在调用属性和方法的时候,会依次从实例本身、构造函数原型、原型的原型上去查找
讲解二:原型链 一次学习终生受用_哔哩哔哩_bilibili
原型链:
Prototype
称它为原型||原型对象
1.原型链是一个函数的属性
2.原型链是一个对象
3.创建函数的时候,默认添加Prototype属性
__proto__
称之为隐式原型
1.对象的属性
2.指向构造函数的prototype
function test (name){
this.name = name
this.a = 1
}
test.prototype.b = 2
const obj = new test('sz')
Object.prototype.c = 3
console.log(obj)//{name:'sz'}
console.log(obj.__proto__===test.prototype) //true
console.log(test.prototype.__proto__===Object.prototype)//true
console.log(Object.prototype.__proto__)//null 这个是顶层了
console.log(obj.a, obj.b, obj.c)//1 2 3
原型链的顶层:Object.prototype.__proto__
是null,没有东西了。
里面结构是这样的
obj {
a:1,
__proto__:test.prototype = {
b:2,
__proto__:Object.prototype = {
c:3
__proto__:null
}
}
}
**原型链查找规则:**先从自身查找
10.new 操作符具体做了什么
- 创建一个空对象
- 把空对象和构造函数通过原型链进行链接
- 把构造函数的this绑定到空对象
- 根据构造函数返回的数据类型 判断,如果是值(基本)类型,则返回对象,如果是引用类型,就返回这个引用类型
案例:
function newFun(Fun, ...args) {
//创建一个空对象
let newObj = {}
//把空对象和构造函数通过原型链进行链接
newObj.__proto__ = Fun.prototype
//把构造函数的this绑定到空对象
const result = Fun.apply(newObj, args)
//根据构造函数返回的数据类型 判断,如果是值(基本)类型,则返回对象,如果是引用类型,就返回这个引用类型
return result instanceof Object ? result : newObj
}
function Person(name) {
this.name = name
}
Person.prototype.say = function () {
console.log('123123')
}
const p1 = newFun(Person, 'sz')
p1.say()//123123
console.log(p1)//Person{name:'sz'}
11.js是如何实现继承的
- 原型链继承
让一个构造函数的原型是另一个类型的实例,那个这个构造函数new出来的实例就具有该实例的属性
function Parent() {
this.isShow = true
this.info = {
name: 'abc',
age: 18
}
Parent.prototype.getInfo = function () {
console.log(this.info)
console.log(this.isShow)
}
// 创建一个子类
function Child() {}
//子类的原型就等于父类的实例
Child.prototype = new Parent()
let Child1 = new Child()
Child.info.gender = '男'
Child1.getInfo() //{name:"abc",age:18,gender:'男'} true
let Child2 = new Child()
console.log(Child2.info.gender) //男
Child2.getInfo() //{name:"abc",age:18,gender:'男'} false
}
优点:写法简洁,容易理解
缺点:对象实例共享所有继承的属性和方法。无法向父类构造函数传参。
2.借用构造函数继承
在子类型构造函数的内部调用父类型构造函数;使用apply()或call()方法将父对象的构造函数绑定在子对象上。
function Parent(gender) {
this.isShow = true
this.info = {
name: 'abc',
age: 18,
gender: gender
}
}
function Child(gender) {
Parent.call(this, gender)
}
//第一个子类
let Child1 = new Child('男')
Child1.info.nickname = 'xxxx'
console.log(Child1.info) //{name: 'abc', age: 18, gender: '男', nickname: 'xxxx'}
// 第二个子类
let child2 = new Child('女')
console.log(child2.info) //{name: 'abc', age: 18, gender: '女'}
优点:解决了原型链实现继承的不能传参的问题和父类的原型共享的问题。
缺点:借用构造函数的缺点是方法都在构造函数中定义,因此无法实现函数复用。
在父类型的原型中定义的方法,对子类型而言也是不可见的,结果所有的类型都只能使用构造函数模式。
3.组合式继承
将 原型链 和 借用构造函数 的组合到一块。使用原型链实现对原型属性和方法的继承。
通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有自己的属性。
function Person(gender) {
console.log('执行次数')
this.info = {
name: 'abc',
age: 18,
gender: gender
}
}
//使用原型链继承原型上的属性和方法
Person.prototype.getInfo = function () {
console.log(this.info.name, this.info.age)
}
function Child(gender) {
//使用构造函数传递参数
Person.call(this, gender)
}
Child.prototype = new Person()
let child1 = new Child('男')
child1.info.nickname = 'xxxxx'
child1.getInfo()
console.log(child1.info)
let child2 = new Child('女')
console.log(child2.info)
优点:解决了原型链继承和构造函数继承造成的影响。
缺点:无论在什么情况下,都会调用两次父类构造函数:
一次是创建子类原型的时候,另一次是在子类构造函数内部。
4.es6的class继承
Class通过extends关键字实现继承,实质就是先创造出父类的this对象,然后用子类的构造函数修改this
子类的构造方法中必须调用super方法,且只有在调用了super()之后才能使用this,因为子类的this对象是继承父类的this对象,然后对其进行加工,而super方法表示的是父类的构造函数,用来新建父类的this对象。
class Animal {
constructor(kind) {
this.kind = kind
}
getKind() {
return this.kind
}
}
//继承Animal
class Cat extends Animal {
constructor(name) {
//子类的构造函数中必须先调用super方法
super('cat')
this.name = name
}
getCatInfo() {
console.log(this.name + ':' + super.getKind())
}
}
const cat1 = new Cat('buding')
cat1.getCatInfo() //buding:cat
优点:语法简单易懂,操作方便
缺点:不是所有浏览器都支持class关键字
12.js的设计原理是什么
JS引擎 运行上下文 调用栈 事件循环 回调
1.js引擎
对js代码进行语法解析,通过一些编译器把代码编译成可执行的机器码,让电脑去执行。
目前比较流行的是v8引擎。
2.运行上下文
浏览器里面可以调用的api,比如说window dom提供的一些api
这些接口,不是v8提供的,而是浏览器提供的。
可以在运行的时候,让js驱动。
3.调用栈
在设计最初就是单线程去运行的,因为js可以实现很多交互的操作,比如对dom对操作。
如果是多线程就会造成很多的同步问题。
js一诞生就是单线程,主线程主要就是进行一些渲染的工作,如果有阻塞,那么就会导致浏览器直接卡死。
4.事件循环
当调用栈里面的内容空了之后,这个事件循环就把任务放到循环里面,或者放到调用栈里面去执行它。
然后后面会不断的去循环执行这个操作。
总结:
js把代码转化成电脑可以执行的机器码,然后js又调用了一些api,让浏览器可以执行,
js又是单线程的,我们每次从调用栈里面取出来的代码进行调用,就会阻塞这个线程,导致浏览器卡顿,
回调函数就是通过加入到事件队列里面,等待这个事件循环拿出来,放到这个调用栈里面去执行。
只有这个事件循环监听到这个调用栈是空的时候,才会从这个事件队列里面拿出来,放入到这个调用栈里面
再继续去执行。
13.js关于this指向的问题
1.全局对象中的this指向
指向的是window
2.全局作用域或者普通函数中的this指向
指向的全局window(如果函数中有this,但是没有被上一级的对象所调用的时候)
3.this永远指向最后调用它的那个对象
在不是箭头函数的的情况下
4.new关键字改变了this的指向
详情可以了解new操作符具体做了什么
5.apply,call,bind
可以改变this指向,不是箭头函数
6.箭头函数中的this
他的指向,在定义的时候就已经确定好了。
箭头函数它没有this,看外层是否有函数,有过有,那外层函数的this就是箭头函数的this,
如果没有,那this就是window。
7.匿名函数中的this
它永远指向window,匿名函数的执行环境具有全局性,因此this指向window。
14.script标签里的async和defer有什么区别
1.当没有async和defer这两个属性时,浏览器会立即加载并执行指定的脚本。可能会导致阻塞页面渲染导致空白。
2.有async时,加载和渲染后面html元素的过程将和script的加载和执行并行进行(异步)
3.有defer时,它也是异步并行加载,但是它的执行事件要等所有html元素解析完之后才会执行。
15.setTimeout最小执行时间
HTML5规定的内容:
setTimeout最小执行时间是4ms
setInterval最小执行时间是10ms,一旦小于10ms,就会自动调整为10ms。
16.ES5和ES6有什么区别
JS的组成:ECMAScript BOM DOM
ES5 : ECMAScript5, 2009年ECMAScript的第五次修订,也称ECMAScript2009
ES6 : ECMAScript6, 2015年ECMAScript的第六次修订,也称之为ECMAScript2015,是js下一个版本标准
17.ES6有哪些新特性
1.新增块级作用域(let,const)
不存在变量提升
存在暂时性死区的问题
块级作用域的内容
不能在同一个作用域内重复声明
2.新增定义类的语法糖(class)
3.新增了一种基本数据类型(symbol)
symbol代表独一无二的值
4.新增了解构赋值
从数组或者对象中取值,然后给变量赋值
5.新增函数参数的默认值
6.给数组新增了API
7.对象和数组新增了扩展运算符
8.Promise
解决回调地狱的问题。
自身有all,reject,resolve,race方法
原型上有then,catch
把异步操作队列化
三种状态:pending初始状态,fulfilled操作成功,rejected操作失败
状态:pending -> fulfilled; 或者 pending -> rejected。一旦发生,状态就会凝固,不会再变
async await
同步代码作异步的操作,两者必须搭配使用
async表明函数内有异步操作,调用函数会返回promise
await是组成async的表达式,结果是取决于它等待的内容,如果是promise那就是promise的结果,如果是普通函数就进行链式调用。
await后的promise如果是reject状态,那么整个async函数都会中断,后面的代码不执行
9.新增了模块化(import,export)
10.新增了set和map数据结构
set就是不重复
map的key的类型不受限制,可以为任意类型
11.新增了generator
12.新增了箭头函数
箭头函数和普通函数的区别:
1.不能作为构造函数使用,不能使用new==》箭头函数就没有原型
2.箭头函数没有arguments
3.箭头函数不能使用call,apply,bind去改变this的指向
4.箭头函数的this指向外层第一个函数的this
18.什么是柯里化,如何实现柯里化
讲解一:
视频:前端面试:什么是柯里化?如何实现柯里化?_哔哩哔哩_bilibili
文章:https://blog.csdn.net/wtswts1232/article/details/132769444
1.什么是柯里化
柯里化(Currying)是
将使用多个参数的函数转换成一系列使用一个参数的函数。
当参数的个数没有达到length的时候,就会不停的返回新的函数
function sum(a, b, c) {
console.log(a + b + c);
}
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
//如果参数个数达到length,执行原函数
return fn.apply(this, args);
} else {
//如果参数没有达到length,返回新函数
return function (...args2) {
//递归调用curried函数,并且将新的参数和旧的参数拼接到一起传给curry函数
return curried.apply(this, args.concat(args2));
};
}
};
}
let _sum = curry(sum);
let functionA = _sum(1);
let functionB = functionA(2);
functionB(3); // print: 6
2.柯里化的实现
接下来,我们来思考如何实现 curry 函数。
回想之前我们对于柯里化的定义,接收一部分参数,返回一个函数接收剩余参数,接收足够参数后,执行原函数。
我们已经知道了,当柯里化函数接收到足够参数后,就会执行原函数,那么我们如何去确定何时达到足够的参数呢?
我们有两种思路:
通过函数的 length 属性,获取函数的形参个数,形参的个数就是所需的参数个数
在调用柯里化工具函数时,手动指定所需的参数个数
function curry(fn, len = fn.length) {
return _curry.call(this, fn, len);
}
function _curry(fn, len, ...args) {
return function (...params) {
let _args = [...args, ...params];
if (_args.length >= len) {
return fn.apply(this, _args);
} else {
return _curry.call(this, fn, len, ..._args);
}
};
}
3.柯里化的应用场景
https://juejin.cn/post/6844903882208837645#comment
19.窃取对象
讲解一:
视频:【前端小野森森】2023热门面试题『窃取对象』【JS基础】_哔哩哔哩_bilibili
案例:利用原型的方式,对立即执行函数的内部对象进行修改。
//立即执行函数
const api = (() => {
//字面量方式声明的对象 => 原型 => Object.prototype => originObj
//路由信息
const fields = {
USER_LIST: '/user_list',
BOOK_LIST: '/book_list',
MOBILE_LIST: '/mobile_list'
}
return {
get(key) {
return fields[key]
}
}
})();
console.log(api.get('BOOK_LIST')) ///book_list
Object.defineProperty(Object.prototype,'originObj',{
get () {
return this;
}
})
console.log(api.get('originObj'))
/**
*api.get('originObj') 相当于=> return fields['originObj'] = >fields.originObj
* fields.originObj => get originObj => fields.getOriginObj() => this => fields
*/
const fields = api.get('originObj')
fields.Book_LIST = 'book'
fields.a = 1
console.log(fields)//{ USER_LIST: '/user_list',BOOK_LIST: 'book',MOBILE_LIST: '/mobile_list',a:1}
如何防止对象窃取,完全对这个fields进行封闭
有人想到
es2022有一个私有化
proposal-class-fields
proposal-private-methods
上面两个提案决定了一个东西 #
=> 属性方法的私有化 (chrome已经支持)
class API {
//私有变量
#fields = {
USER_LIST: '/user_list',
BOOK_LIST: '/book_list',
MOBILE_LIST: '/mobile_list'
}
get(key){
return this.#fields[key];
}
}
const api = new API();
console.log(api.#fields) //报错 里面有#fields,但是不能在外部访问
console.log(api.get('USER_LIST'))// '/user_list'
但是这种方式任然可以修改fields
Object.defineProperty(Object.prototype,'originObj',{
get () {
return this;
}
})
console.log(api.get('originObj'))//这样仍然可以访问到fields
//所以仍然可以进行修改
const fields = api.get('originObj')
fields.Book_LIST = 'book'
所以想让fields完全不能被修改,有两种方式:
1.通过Object.freeze冻结对象
const api = (() => {
const fields =Object.freeze({
USER_LIST: '/user_list',
BOOK_LIST: '/book_list',
MOBILE_LIST: '/mobile_list'
});
return {
get(key) {
return fields[key]
}
}
})();
Object.defineProperty(Object.prototype,'originObj',{
get () {
return this;
}
})
const fields = api.get('originObj')
//发现下面操作并没有修改和增加对象的属性
fields.Book_LIST = 'book'
fields.a = 1
2.断原型,通过Object.setPrototypeOf
const api = (() => {
const fields ={
USER_LIST: '/user_list',
BOOK_LIST: '/book_list',
MOBILE_LIST: '/mobile_list'
};
Object.setPrototypeOf(fields,null);//没原型了
return {
get(key) {
return fields[key]
}
}
})();
Object.defineProperty(Object.prototype,'originObj',{
get () {
return this;
}
})
const fields = api.get('originObj')//本身也没有这个originObj这个属性,然后沿着fields原型上面去找的时候发现没原型,所以是undefined
fields.Book_LIST = 'book'//报错
也可以通过这种方式断原型
const fields =Object.create(null,{
USER_LIST: {
value:'/user_list',
},
BOOK_LIST: {
value:'/book_list'
},
MOBILE_LIST: {
value:'/mobile_list'
}
})
前置知识点:
知识点1:赋值或访问进行函数化转换有几个优点:
- 语意清晰
- 劫持后可以扩展更多逻辑
Object.defineProperty(obj,'a',{
get(){
//todo....做更多的事情
return 1;
}
})
//或者这样写
const obj = {
get a(){
//todo...
return 1;
}
}
知识点2:以obj为底的原型链。
1.一个对象访问一个属性的原则
自己对象上有的属性,直接访问。
自己对象上没有的属性,逐层沿着原型链节点([[Prototype]])向上找原型有没有该属性。
如果找到Object.prototype上没有这个属性,则返回undefined.
如果在任意原型节点对应的原型属性上有这个属性,直接返回值,比如下面obj.b直接输出2
2.this指向
this叫执行期上下文
this到底指向谁,取决于函数执行的方式和环境的
//obj的原型 =>Object.prototype
const obj = {
a:1
}
Object.prototype.b = 2
Object.prototype.c = function(){
return this;
}
console.log(obj.b)//2
console.log(obj.c())//c函数内部的this指向obj,所以打印obj
//obj.c()=>obj调用c,b函数在执行中,this就指向这个obj
var o = obj.c //这个就相当于var o = function(){return this} 并没有其他的对象去调用它
console.log(o())//this指向window
//它的原型链的样子
obj {
a:1,
//内部属性=>外部访问的属性
//内部属性:[[prototype]](__proto__) [[scope]] [[Contruct]] [[call]]
__proto__:Object.prototype = {
//可以设置一些属性
b:2,
__proto__:null
}
}
20.如何使用Proxy实现链式调用
讲解:前端面试官:如何使用 Proxy 实现链式调用?_哔哩哔哩_bilibili
案例:
function increase(a){
return a+1;
}
function decrease(a){
return a-1;
}
function double(a){
return 2*a;
}
//链式调用
console.log(chain(3).increase.double.end);//8
console.log(chain(2).increase.double.end)//2
什么是proxy?
const target = {
value:42,
};
const handler = {
get:function(target,property){
console.log(target,property);//{value:42} 'value'
return target[property];
}
}
const proxy = new Proxy(target,handler);
console.log(proxy.value)//42
怎么实现链式调用呢
function chain(value){
const handler = {
get:function(obj,prop){
if(prop==="end"){
return obj.value;
}
if(typeof window[prop]==="function"){
obj.value = window[prop](obj.value);
return proxy;
}
return obj[prop];
},
};
const proxy = new Proxy({value},handler);
return proxy;
}
21.call apply bind三者区别
共同点:都是改变this指向和函数的调用,call和apply的功能类似,只是传参的方法不同
call方法传的是一个参数列表
apply传入的是一个数组
这两个传参之后会立刻直接调用,call方法的性能要比apply好一些,所以call用的更多一点
bind传参后不会立刻执行,会返回一个改变了this指向的函数,这个函数还是可以传参的,bind()()
22.递归
如果一个函数内可以调用函数本身,那么这个就是递归函数
函数内部调用自己。
注意:递归必须要有退出条件(return) -> 出口
23.如何实现一个深拷贝
深拷贝就是完全拷贝一个对象,会在堆内存中开辟一个新的空间,拷贝的对象被修改后,原对象不变。
主要针对的是引用数据类型。
深拷贝的方法:
1.扩展运算符
let obj = {
name:'sz',
age:18
}
let obj1 = {...obj}
obj1.name ='aaaa'
console.log(obj)//没变
console.log(obj1)//name属性改变
缺点:只能针对第一层深拷贝,当多层的时候还是浅拷贝
2.JSON.parse(JSON.stringify())
let obj = {
name:'sz',
age:18,
say(){
console.log('ssss')
}
}
let obj1 =JSON.parse(JSON.stringify(obj))
obj1.name ='aaaa'
console.log(obj)//name没有修改
console.log(obj1)//name被修改,但是没有把函数拷贝过来
缺点:没办法拷贝内部函数
3.利用递归函数实现(建议)
let origin = {
name:'sz',
age:18,
say(){
console.log('ssss')
},
arr:[[1,2],3,4,5]
}
//递归函数
function exten(origin,deep){
let obj = {}
if(origin instanceof Array){
//如果是个数组
obj= []
}
for(let key in origin){
let value = origin[key]
obj[key] = (!!deep &&typeof value==='object' &&value!==null)?exten(value,deep):value
}
return obj
}
const oo = exten(origin,true)//true表示要深拷贝
oo.arr[0].push(888)
console.log(origin)//不受影响
console.log(oo)
24.如何实现事件循环
js是一个单线程脚本语言
概念:主线程 执行栈 任务队列 宏任务 微任务
主线程先执行同步任务,然后才去执行任务队列里的任务,如果在执行宏任务之前,有微任务的话,先执行微任务。
等全部执行完之后,再等着主线程去调用,调用完成之后,再去执行任务队列里面查看是否有异步任务,这样的一个循环往复的过程。
25.ajax是什么,如何实现?
创建交互式网页应用的网页开发技术:可以在不重新加载网页的情况下,跟服务器交换数据,并且对页面部分内容进行一个更新
原理:通过XmlHttpRequest对象向服务器发送异步请求,然后从服务器拿到数据,最后通过js操作dom的形式更新页面
过程:
1.创建XmlHttpRequest对象 xmh
2.通过xmh对象里的open()方法和服务器建立连接
3.构建请求所需的数据,并通过xmh对象的send()发送给服务器
4.通过xmh对象的onreadystate里的change事件监听服务器和你的通信状态
5.接收并处理服务器响应的数据结果
6.把处理的数据更新到HTML页面上
26.get和post的区别
1.get一般是获取数据,post一般是提交数据
2.get参数会放在url上,安全性会比较差。post是放在body中,数据长度没有限制
3.get请求刷新服务器或者退回没有影响,post请求退回会重新提交数据
4.get请求时会被缓存,post请求不会被缓存
5.get请求会被保存在浏览器的历史记录里面,post不会
6.get请求只能进行url编码,post请求支持很多种
27.promise内部原理是什么?它的优缺点是什么?
Promise对象,封装了一个异步操作并且还可以获取成功或者失败的结果
Promise主要解决回调地狱的问题,之前如果异步任务比较多,同时他们之间有相互依赖关系,
就只能使用回调函数处理,这样就容易形成回调地狱,代码的可读性差,可维护性也很差。
**有三种状态:**pending(初始状态) fulfilled(成功状态) rejected(失败状态)
状态改变只会有两种情况:
1.pending -> fulfilled;
2.pending -> rejected
一旦发生,状态就会凝固,不会再变。
缺点:
首选即使无法取消promise,一旦创建就会立即执行,不能中途取消。
如果不设置回调函数,promise内部抛出的错误就无法反馈到外面。
若当前处于pending状态时,无法得知目前在哪个阶段。
原理:
构造一个Promise实例,实例需要传递函数的参数,这个函数有两个形参,分别都是函数类型,一个是resolve一个是reject
promise身上还有then方法,这个方法指定promise这个对象的状态改变的时候确定操作。resolve是执行第一个函数,
reject是执行第二个函数
28.promise和async和await有什么区别
1.都是处理异步请求的方式
2.promise是es6的语法,async和await是es7的语法
3.async await是基于promise实现的,它和pormise都是非阻塞的
优缺点:
1.promise是返回对象我们要用then,catch方法处理和捕获异常,并且书写方式是链式调用,容易造成代码重叠,不好维护。
async 和await是通过try catch进行捕获异常
2.async await优点是能让代码看起来像同步一样,只要遇到await就会立刻返回结果,然后再执行后面的操作。
promise只能通过promise.then()的方式返回,会出现请求还没返回,就执行了后面的操作
29.浏览器的存储方式有哪些
1.cookies
h5标准前的本地存储方式
优点:兼容性好,请求头自带cookie
缺点:存储量小,资源浪费,使用麻烦(封装)
2.localstorage
H5加入以键值对为标准的方式
优点:操作方便,永久存储,兼容性比较好
缺点:保存值的类型被限定,浏览器在一些隐私模式下不可读取,不能被爬虫
3.sessionstorage
当前页面关闭后就会立刻清理,会话级别的存储方式
4.indexedDB
h5标准存储方式,它是以键值对进行存储,可以快速读取,适合web场景
30.token存在sessionstorage还是localstorage
token:验证身份的令牌,一般就是用户通过账号密码登录后,服务端把这些凭证通过加密等一系列操作后得到的字符串
1.存localstorage里,后期每次请求接口都需要把它当作一个字段传给后台。(一般存在这)
2.存cookie中,会自动发送,缺点就是不能跨域
存在localstorage中容易被XSS攻击,但是如果做好了对应的措施,是利大于弊的
如果存在cookie中会有CSRF攻击
31.token的登录流程
1.客户端用账号密码请求登录
2.服务端收到请求后,需要验证账号密码
3.验证成功之后,服务端会签发一个token,把这个token发送给客户端
4.客户端收到token后保存起来,可以放在cookie也可以是localstorage
5.客户端每次向服务端发送请求资源时,都需要携带这个token
6.服务端收到请求,接着去验证客户端里的token,验证成功才会返回客户端请求的数据
32.页面渲染的过程是怎样的
浏览器url从输入到页面展示的过程
DNS解析
建立TCP连接
发送HTTP请求
服务器处理请求
渲染页面
浏览器会获取html和css的资源,然后把html解析成DOM树
再把css解析成CSSOM
把DOM和CSSOM合并为渲染树。
布局
把渲染树的每个节点渲染到屏幕上(绘制)
断开TCP连接
33.DOM树和渲染树有什么区别
DOM树是和HTML标签一一对应关系,包括head和隐藏元素
渲染树是不包含head和隐藏元素的
34.精灵图和base64的区别是什么
精灵图:把多张小图整合到一张大图上,利用定位的一些属性,把小图显示在页面上,当访问页面
可以减少请求,提高加载速度。
base64:传输8bit字节代码的编码方式,把原本二进制形式转为64个字符的单位,最后组成字符串
base64是会和html css一起下载到浏览器中,减少请求,减少跨域问题,但是一些低版本不支持。
若base64体积比原图大,不利于css的加载。
35.svg格式了解多少
基于xml语法格式的图像格式,全称是可缩放矢量图,其他图像是基于像素
SVG是属于对图像形状的描述,本质是文本文件,体积小,并且不管放大多少倍都不会失真。
1.svg可直接插入页面中,成为dom一部分,然后用js或css进行操作
<svg></svg>
2.svg可作为文件被引用
<img src='pic.svg' />
3.svg可以转为base64引入页面
36.了解过JWT吗
JSON Web Token 通过JSON形式作为在web应用中的令牌,可以在各方之间安全的把信息作为JSON对象传输
作用:信息传输、授权
JWT认证流程
1.前端把账号密码发送给后端的接口
2.后端核对账号密码成功后,把用户id等其他信息作为JWT负载,把它和头部分别进行base64编码拼接后签名,形成一个JWT
(Token)
3.前端每次请求时,都会把JWT放在HTTP请求头的Authorization字段内
4.后端检查是否存在,如果存在就验证JWT的有效性(签名是否正确,token是否过期)
5.验证通过后,后端使用JWT中包含的用户信息进行其他的操作,并返回对应结果
简洁、包含性、因为Token是JSON加密的形式保存在客户端,所以JWT是跨语言的,原则上是任何web形式都支持。
37.npm的底层环境是什么
node package manager,node的包管理和分发工具,已经成为分发node模块,是js的运行环境。
npm的组成:网站、注册表、命令行工具
vue
1.ref原理(vue3)
1) 为什么需要ref
因为reactive只能让引用类型响应式,不能让基础类型响应式,所以需要ref。
因为reactive的响应式机制是通过 new Proxy,而Proxy第一个参数是不能使原始类型的,不然会报错。如图为响应式原理:
这里给proxy传入的参数必须是个引用类型。
2) ref为什么需要.value
项目工程
1.import引入库到底在引入哪个文件?
一节课搞懂import引入库,到底在引入哪个文件_哔哩哔哩_bilibili
1.搞懂package.json里面的
2.前端大文件上传
参考一:面试官:前端大文件『切片上传/分片上传』如何实现 ?_哔哩哔哩_bilibili
文章:https://blog.csdn.net/wtswts1232/article/details/130663725
前置知识:
1.File对象:
它表示一组文件,我们使用<input type="file">
选择文件时,这些文件被存储在File对象中
2.Blob对象:
Blob表示二进制数据,常用来表示大型数据对象(图片、音频等)。File对象是Blob对象的一个子类,它继承了Blob对象的所有属性和方法。
3.formData对象:
前端先将文件存储在formData对象中,才能传给后端。因为File对象不能直接传给后端,一般都是将二进制传给后端。
通过formData.append("file",file)
方法将File对象添加到FormData对象中。
切片上传
slice方法:File对象的slice方法是从其父类Blob对象继承来的,用于从文件中提取一段范围的数据。参数如下:
- start:开始提取数据的字节偏移量。如果未指定,默认为0。
- end:结束提取数据的字节偏移量。如果未指定,默认为文件或Blob对象的总字节数。
slice方法返回值是一个新的Blob对象,包含从原始文件或Blob对象中提取的指定字节范围的数据。
let input = document.getElementById("fileInput")
let imgElement = document.getElementById("sliceImg")
let file = input.files[0]
//使用File对象的slice方法提取图像的部分数据(例如,前200kb)
let sliceBlob = file.splice(0,1024*200,file.type)
//使用FileReader对象读取sliceBlob
let reader = new FileReader();
reader.onload = function(e){
//读取切割后的文件之后,会将img 的src设置为切割后的图片
imgElement.src = e.target.result;
imgElement.style.display = "block"
};
//读取切割后的文件
reader.readAsDataURL(slicedBlob)
大文件上传的简易流程:
首先进行数据处理。从上文可以知道,我们的每个小切片都是一个 File 对象。但是网络传输的时候,我们传输的是二进制文件。因此我们需要借助 FormData 来进行数据处理。FormData 对象会将数据编译成键值对,可用于发送带键数据。通过调用它的 append() 方法来添加字段。当我们调用 FormData.append(文件名,file对象) 的时候,file 对象会被转化为二进制。
<!DOCTYPE html>
<html lang="en">
<body>
<div class="container">
<h1>大文件上传</h1>
<input type="file" id="fileInput" accept="image/*">
<button id="uploadButton">切片上传</button>
<br>
</div>
<script>
//chunk就是一个切片,也就是小文件
async function uploadChunk(chunk) {
const formData = new FormData();
formData.append('file', chunk);
//这里的地址可以替换为你的后端地址
const response = await fetch('https://file.io', {
method: 'POST',
body: formData
});
const result = await response.json();
return result;
}
document.getElementById('uploadButton').addEventListener('click', async function() {
const fileInput = document.getElementById('fileInput');
const file = fileInput.files[0];
const chunkSize = 100 * 1024; // 100KB
//求出大文件可以切分成多少个小文件
const totalChunks = Math.ceil(file.size / chunkSize);//文件的总体积/每一个小文件的体积
for (let i = 0; i < totalChunks; i++) {
const start = i * chunkSize;
//这里做一个判断,判断结束字节是否已经大于文件的总体积,会取一个最小值
const end = Math.min(start + chunkSize, file.size);
const chunk = file.slice(start, end);
//上传一个切片给后端
const result = await uploadChunk(chunk);
}
});
</script>
</body>
</html>