【面试积累】

文章目录

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.重排和重绘有什么区别

**重排(回流):**布局引擎会根据所有的样式 ,计算出 盒模型在页面上的位置和大小

浏览器的渲染机制:

**重绘:**计算好 盒模型的位置、大小和其他的一些属性后,浏览器会根据每个盒模型的特性进行绘制

image-20231102081520262

到Render Tree(渲染树),进行重排(回流),再往后面Painting就开始进行重绘

比如:在dom里面增加一个div,导致浏览器需要重新计算当前元素的位置大小等,所以要进行回流(重绘),

如果只是修改一个div的颜色,字体颜色等,不需要重新计算位置大小等,就是进行重排。

总结:

对dom的大小、位置进行修改后,浏览器需要重新计算元素的这些几何属性,就叫重排

对dom的样式进行修改,比如color和background-color,浏览器不需要重新计算几何属性的时候,直接绘制了该元素的新样式,

那么这里就只触发了重绘。

6.让元素水平垂直居中的方式有哪些

  1. 定位 + 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>
  1. 定位 + 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

image-20230708222826212

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

原型链:

就是一个实例对象在调用属性和方法的时候,会依次从实例本身、构造函数原型、原型的原型上去查找

image-20231121081849507

讲解二:原型链 一次学习终生受用_哔哩哔哩_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 操作符具体做了什么

  1. 创建一个空对象
  2. 把空对象和构造函数通过原型链进行链接
  3. 把构造函数的this绑定到空对象
  4. 根据构造函数返回的数据类型 判断,如果是值(基本)类型,则返回对象,如果是引用类型,就返回这个引用类型

案例:

		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是如何实现继承的

  1. 原型链继承

让一个构造函数的原型是另一个类型的实例,那个这个构造函数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)
image-20231128205836162

优点:解决了原型链继承和构造函数继承造成的影响。

缺点:无论在什么情况下,都会调用两次父类构造函数:

​ 一次是创建子类原型的时候,另一次是在子类构造函数内部。

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:赋值或访问进行函数化转换有几个优点:

  1. 语意清晰
  2. 劫持后可以扩展更多逻辑
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第一个参数是不能使原始类型的,不然会报错。如图为响应式原理:

image-20240107215622546

这里给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对象中

image-20240220213011109

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)

大文件上传的简易流程:

image-20240220220819585

首先进行数据处理。从上文可以知道,我们的每个小切片都是一个 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>
  • 16
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值