面试题整理

1、new关键字干了什么?

let a = new Object() = let a = Object() = let a = {} 都会创建一个新对象

new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。

function Person(name) { // 构造函数Person()
	this.name = name;
    // this.prototype = Object
}
const a = new Person('ycl');
  1. 创建一个空的JavaScript 对象,即{}调用构造函数Object()

    • const a = {};
  2. 设置该空对象的原型__proto__,为构造函数的原型对象prototype 实现原型继承

    • a.__proto__ = Person.prototype = Object; // true
    • 实例化对象的原型__proto__ 就是其构造函数的原型对象prototype
  3. this指向空对象并执行函数体;

    • a.name = 'ycl';
    • a.constructor = f Person(name)
  4. 判断构造函数的返回值,以决定最终返回结果 – 返回新对象

    • 返回基础数据类型,则忽略返回值

      function Person(name) {
          this.name = name;
          return 666;
      }
      const a = new Person('ycl');
      // {name:'ycl'}
      
    • 返回引用数据类型(return的返回),则new操作符无效,返回的是return内容

      function Person(name) {
          this.name = name;
          return { // Object,Array,Function,Data...
          	age: 18 
          };
      }
      const a = new Person('ycl');
      // {age:18}
      

2、闭包是什么?

  1. 闭包是什么

    • 函数和函数内部能访问到的变量的总和。
  2. 如何生成闭包

    • 函数内嵌套函数,函数执行完毕以后,内部函数会被引用,这样内部函数可以访问外部函数中定义的变量。
  3. 闭包的特性

    • 如果生成闭包,外部函数执行完毕,其中的局部变量可能被内部函数使用,所以不能销毁,因此内部函数能一直访问到这些局部变量,直到引用解除。
  4. 缺点:

    • 内存溢出(内存泄漏)
      • 使用闭包但是未及时销毁,导致闭包中的私有变量一直存在于内存中,导致js的内存泄漏。应该及时销毁不用的闭包变量,从而避免内存泄漏。
  5. 优点:

    • 避免全局污染

      • 隐藏变量,避免放在全局有被篡改的风险。
    • 延长生命周期

      function outer() {
          var a = 1;
          function inner() {
              console.log(a);
          }
          return inner;
      }
      
      var b = outer();
      

3、原型链是什么?

JavaScript中, class 是关键字,但也只是语法糖。

JavaScript 仍然是基于原型的。几乎所有 JavaScript 中的对象都是位于原型链顶端的 Object 的实例。

  • 每个实例对象(object)都有一个私有属性(称之为 proto)指向它的构造函数的原型对象(prototype

  • 该原型对象也有一个自己的原型对象(proto),层层向上直到一个对象的原型对象为 null

  • 理解 p 所在的原型链

    1. p 实例对象 (对象)Person {name: 'ycl'}
      • p.__proto__ 实例对象的原型 类似(指针){constructor: ƒ Person(name)}
    2. Person.prototype 实例对象的构造函数的原型对象 (对象){constructor: ƒ Person(name)}
      • Person.prototype.__proto__ 原型对象的原型 类似(指针) {...__proto__: null...}
    3. Object.prototype 对象的原型对象 (对象) {...__proto__: null...}
      • Object.prototype.__proto__ 对象的原型对象的原型 null
    4. null.prototype null的原型对象 // 报错 null没有原型,查找原型链结束
  • PS:

    • __ proto__是一个访问器或索引,用其找到构造函数的prototype
    • prototype是一个构造器,里面包含了构造函数里的各种属性,方法
  • 例子:

    function Person(name) {
        this.name = name
    }
    var p = new Person('ycl');
    console.log(p.__proto__) // Person.protptype (Object)-- 实例对象的原型 就是 实例对象构造函数的原型对象
    console.log(p.__proto__.__proto__) // Object.prototype
    console.log(p.__proto__.__proto__.__proto__) // null -- Object原型对象没有原型了(找到头了)
    console.log(p.__proto__.__proto__.__proto__.__proto__) // err报错 -- null是基础数据类型,没有原型
    console.log(p.constructor) // Person -- 实例化对象的构造函数
    console.log(p.prototype) // undefined -- 实例没有prototype属性
    console.log(Person.constructor) // Function 一个空函数 
    console.log(Person.prototype) // 打印出Person.prototype这个对象里所有的方法和属性
    console.log(Person.prototype.constructor) // Person
    console.log(Person.prototype.__proto__) // Object.prototype
    console.log(Person.__proto__) // Function.prototype
    console.log(Function.prototype.__proto__) // Object.prototype
    console.log(Function.__proto__) // Function.prototype
    console.log(Object.__proto__) // Function.prototype
    console.log(Object.prototype.__proto__) // null
    

4、call、apply、bind的区别是什么?

改变this指向的。(还有箭头函数)

如果函数处于非严格模式下,则this指定为 nullundefined 时会指向全局对象window

  • 函数名.call(要改变的 this 指向,要给函数传递的参数1,要给函数传递的参数2, ...)

    1. 直接写多个参数
    2. 立即执行函数
  • 函数名.apply(要改变的 this 指向,[要给函数传递的参数1, 要给函数传递的参数2, ...])

    1. 多个参数写成数组
    2. 立即执行函数
  • var newFn = 函数名.bind(要改变的 this 指向,要给函数传递的参数1,要给函数传递的参数2, ...)

    1. 直接写多个参数

      • newFn(要给函数传递的参数n)如果参数多余,则不生效(可以在原函数和新函数两个位置传参,优先考虑的是binf内部的参数)
      • 多次使用bind改变this指向,同时传入参数,则函数形参依次叠加
    2. 不会立即执行函数,而是返回一个已经改变了 this 指向的函数

5、js数据类型

  • 值数据类型(基本数据类型):存放在栈(stack)内存中的简单数据段,变量直接存储数据

    1. Number 隐式的编码为浮点类型
    2. String
    3. Boolean
    4. Undefined
    5. null
    6. Symbol (ES6新增)唯一并且不可变的原始值并且可以用来作为对象属性的键
    7. BigInt 可以表示任意大小的整数,存储和操作巨大的整数,甚至超过 Number 的安全整数限制
  • 引用数据类型(复杂数据类型):存放在堆(heap)内存中的对象,栈内存中的变量存放的是堆内存中具体内容的引用地址

    • Object(统称)

      • ObjectArrayfunctionDateRegExp

6、js数据类型判断

  • typeof() 查看字面量或者变量的数据类型

    • number、string、boolean、Symbol**、**undefined及function
    • 注意:对于null及数组、对象,typeof均检测出为object,不能进一步判断它们的类型。
  • .constructor 判断构造函数

    • 注意:undefined和null没有contructor属性

      console.log(false.constructor === Boolean);// true
      
  • instanceof判断一个对象的构造函数是否等于给定的值(一般用于判断自定义构造函数实例。)

    ({}) instanceof Object // true
    [] instanceof Array // true
    new Date() instanceof Date // true
    /123/g instanceof RegExp // true
    
  • Object.prototype.toString.call()

    • 可以用来区分数组、null等引用类型
    • 自定义对象的判断只能得到"[object Object]"的结果。
    Object.prototype.toString.call(1);  '[object Number]'
    Object.prototype.toString.call('1'); '[object String]'
    
  • isArray()

  • isNaA()

7、eventLoop中宏任务和微任务?

事件循环eventLoop是js引擎执行js代码的机制,用来实现js的异步特性。

步骤:

  1. 执行一个宏任务。
  2. 执行微任务,直到微任务队列为空。
  3. 循环1、2。
  • 宏任务:

    • script整体代码setTimeoutsetIntervalrequestAnimationFrameI/O
  • 微任务:

    • Promise.thenPromise.catchnextTick

8、防抖和节流?

  • 防抖(输入框):

    • 短时间内用户多次触发同一个事件,只需要执行最后一次
    • 用户不停触发,可能永远不会收到反馈
    • 用于无法预知的用户主动行为
    • 触发后,启动定时器,若再触发则重置计时器,直到定时器结束,执行请求
  • 节流(验证码):

    • 用户同时段多次触发事件、只会执行第一次事件
    • 用户不停触发,会收到反馈
    • 非用户主动行为或者可预知的用户主动行为
    • 触发后,启动定时器,定时器结束前不会被再次触发

9、深浅拷贝有哪些方法?

深拷贝:

  1. 新对象的属性与源对象的属性不共享相同的引用(指向相同的底层值)的副本。
  2. 当更改源或副本时,一定不会导致其他对象也发生更改。

浅拷贝:

  1. 新对象的属性与源对象的属性共享相同的引用(指向相同的底层值)的副本。
  2. 当更改源或副本时,可能导致其他对象也发生更改。

拷贝的划分都是针对引用类型来讨论的

  • 基础数据类型拷贝:栈中变量存储的是值本省,拷贝会开辟新的内存空间,所以一定是深拷贝

  • 引用数据类型拷贝:栈中变量存储的是堆内存中真正数据内容的引用,所以有深浅拷贝之分

  • 深拷贝:

    • JSON.parse(JSON.stringify(能被序列化的JS对象)) – 先将对象转为JSON字符串,再转回(全新的)JS对象 – 注意有bug

    • 封装递归函数cloneDeep()

      • 思路:若属性为值类型,直接返回;若属性为引用类型,递归遍历。
      function deepClone (obj) {
          // 如果值 值类型 或 null ,直接返回
          if (typeof obj !== 'object' || obj === null) {
          	return obj;
          }
      
          let copy = {};
          // 如果对象是数组
          if (obj.constructor === Array) {
          	copy = [];
          }
      
          // 遍历对象的每个属性
          for (let k in obj) {
          	// 如果 key 是对象的自有属性
          	if (obj.hasOwnProperty(k)) { 
          		copy[k] = deepClone(obj[k]); // 递归调用 deepClone
          	}
          }
      
          return copy;
      }
      
  • 浅拷贝

    • for循环

    • Object.assign() 将所有可枚举的自有属性从一个或多个源对象复制到目标对象,返回修改后的对象。拷贝的是(可枚举)属性值。假如源值是一个对象的引用,它仅仅会复制其引用值

    • Array.prototype.concat()合并两个或多个数组, concat方法会返回一个新数组

10、跨域是什么?解决方案是什么?

  • 跨域是什么

    • 考虑到网站数据安全,服务器不允许浏览器端ajax跨域获取数据(同源策略)
    • 所谓跨域,就是指协议+域名+端口任意一个不同即为跨域
  • 解决跨域的方案

    • JSONP

      • 浏览器对 ```
      • 因为每个 vue 的组件实例上,都包含一个 $refs 对象,里面存储着对应的DOM元素或组件的引用。

        • 所以在默认情况下, 组件的 r e f s 指向一个空对象。可以先在组件上加上 ‘ r e f = " 名字 " ‘ ,然后通过 ‘ t h i s . refs 指向一个空对象 。可以先在组件上加上`ref="名字"` ,然后通过`this. refs指向一个空对象。可以先在组件上加上ref="名字"‘,然后通过this.refs.名字`获取相应元素并进行操作。
      1. ref钩子函数 创建任何类型的ref(响应式)

        • eg

          // 组合式
          <script>
              export default {
                setup() {
                  const input = ref(null)
                  // ...
                  return {
                    input
                  }
                }
              }
          </script>
          
          // 组合式(setup语法糖)
          <script setup>
              import { ref, onMounted } from 'vue'
          
              // 声明一个 ref 来存放该元素的引用
              // 必须和模板里的 ref 同名
              const input = ref(null)
          
              onMounted(() => {
                input.value.focus()
              })
          </script>
          
      2. 组件实例 $parent

        • 子组件获取父组件实例 this.$parent.属性

      34、v-model指令实现的原理?

      v-model是一个数据双向绑定的指令——语法糖

      1. 在input事件上使用v-model,等价于用value传值

        • v-bind 绑定一个value属性

        • v-on 指令给当前元素绑定input事件

        <input v-model="searchText" />
        
        <input
        	:value="searchText"
        	@input="searchText = $event.target.value"
        />
        
      2. 在组件上使用v-model

        • 将内部原生 元素的 value 属性绑定到 modelValue 属性上

        • 当原生的 input 事件触发时,触发一个携带了新值的 update:modelValue 自定义事件

          1. 在父组件内给子组件标签添加 v-model ,其实就是给子组件绑定了 value 属性

          2. 子组件内使用 prop 创建 创建 value 属性可以拿到父组件传递下来的值,名字必须是 value

          3. 子组件内部更改 value 的时候,必须通过 $emit 派发一个 input 事件,并携最新的值

          4. v-model 会自动监听 input 事件,把接收到的最新的值同步赋值到 v-model 绑定的变量上

        <CustomInput v-model="searchText" />
        
        <CustomInput
          :modelValue="searchText"
          @update:modelValue="newValue => searchText = newValue"
        />
        

      35、双向绑定原理?

      setter、getter、观察模式、发布订阅模式

      • vue2是通过 数据劫持defineProperty 结合 发布订阅模式 的方式来实现双向绑定的
      • vue3是通过 Proxy数据代理对象(ES6) 的方式来实现双向绑定的
      • Vue中响应式数据变化是观察者模式
      • vue中的事件总线就是使用的发布订阅模式
      • 发布订阅模式相比观察者模式多了个事件中心,订阅者和发布者不是直接关联的
      • MVVM框架的的核心就是双向绑定, 其原理是通过数据劫持+发布订阅模式相结合的方式来是实现的,简单来说就是数据层发生变化的时候,可同布更新视图层,当视图层发生变化的时候,同步更新数据层

      36、为什么vue中赋值是异步?

      防抖机制、提高渲染性能

      Vue可以响应数据变化,数据变化后会自动更新视图,如果每次修改都触发视图更新,会导致多次重复和不必要的渲染操作,例如一个组件使用了两个data的属性,更新两个属性如果触发两次渲染的话,会影响性能。因此Vue采取异步更新。

      37、路由模式和实现的原理?

      在创建路由器实例时,history 配置允许我们在不同的历史模式中进行选择。

      routerview组件 – 路由渲染

      • Hash 模式 - -用createWebHashHistory创建
        • 在内部传递的实际 URL 之前使用了一个哈希字符
        • 使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载,其显示的网路路径中会有 “#” 号,有一点点丑。这是最安全的模式,因为他兼容所有的浏览器和服务器
        • 原理:改变hash可以监听到hashchange事件
      • History(HTML5 模式) – 用 createWebHistory() 创建
        • URL看起来很正常
        • 需要添加一个简单的回退路由
        • 原理:ServiceWorker – 充当Web应用程序(服务器)与浏览器之间的代理服务器(可以拦截全站的请求,并作出相应的动作->由开发者指定的动作)

      38、组件注册方法?异步组件定义(路由组件懒加载)?动态组件?

      组件注册方法

      • 全局注册(component方法)

        const app = Vue.createApp(options)
        app.component('my-panel', MyPanel)
        app.mount('#root')
        
      • 局部注册(components属性)

        Vue.createApp({
            components: { 
                'my-panel': MyPanel
            }
        }).mount('#root')
        

      异步组件定义(路由组件懒加载)

      • import()

        • const List= () => import("@/components/List");
      • defineAsyncComponent() – 定义一个异步组件,它在运行时是懒加载的。

        import { defineAsyncComponent } from 'vue'
        
        const XXX = defineAsyncComponent(() => {
        	// 返回Promise...
            // 导入组件...
        })
        

      动态组件

      • ,通过:is属性控制模板的样式,传给:is的值可能为:
        • 被注册的组件名
        • 导入的组件对象
      • PS:当使用 来在多个组件间作切换时,被切换掉的组件会被卸载。我们可以通过组件强制被切换掉的组件仍然保持“存活”的状态。

      41、key的作用?

      key是循环渲染中,作为虚拟dom对比的标志,所以可以提高diff算法性能

      42、组件封装过程?

      1. 明确需求
      2. 接收参数props
      3. 自定义事件
      4. 传递数据
      5. 组件slot扩展

      43、生命周期有哪些?有什么作用?

      生命周期就是在组件的不同时期实现不同业务

      1. 创建阶段
        • beforeCreate()

          • 几乎不用,没有什么具体业务执行,所以vue3取消了这个生命周期
        • created()

          • 表示vue组件实例对象构造完成,数据已经绑定到vm层

          • PS:Vue中,没有具体元素,只有虚拟Dom

            • 如果要获取元素,不能使用选择器直接获取,需要使用到绑定属性
            • 关联属性(ref):关联真实Dom和虚拟Dom的关系对象
            <h3 ref="node">hello worldh3>
            // Vue实例
            // this.$refs.node
            
          • 数据已经完成了初始化,但是节点没有完成实例挂载,所以不会触发

          • 如果created中存在异步赋值,那么会导致二次渲染,原因:所有生命周期都是同步代码,所以异步执行不会得到等待

          • 综上:created非异步赋值不会导致二次渲染,而mounted一定会导致二次渲染

      2. 挂载阶段

        • beforeMount()

          • 无用的生命周期
        • mounted()

          • 已完成挂载
          • 注意:这里不能写this,因为这里绑定的代码会重新编译
      3. 修改阶段

        • beforeUpdate()
          • 指页面更新,非数据更新,但是数据更新会触发页面更新
          • 数据更新会触发更新生命周期吗?————不对,只有当数据绑定到dom节点上(数据被页面使用了),它的更新才会触发页面更新
        • updated()
      4. 销毁阶段(v2 destroy) / 卸载阶段(v3 unmount)

        • beforeDestory()
        • destroyed()
        • unmount()
        • beforeUnmount()

      44、created和mounted的区别?更新生命周期触发要求?

      • created和mounted的区别
        • created非异步赋值不会导致二次渲染,而mounted一定会导致二次渲染
      • 更新生命周期触发要求
        • 由于数据和dom绑定,数据变化后,需要更新其 DOM 树。

      45、extends和minxis的区别?

      组件类继承、单一继承、属性合并、多组件合并

      mixins可以接收一个混入对象的数组(组件类继承),可以混入多个mixin来多组件合并,进行属性合并

      extends接收的是对象或函数(单一继承),只能继承一个

      46、事件总线?

      全局组件通信 : 信两个任意的组件,不管是否有关联( 父子、爷孙, 子子)的组件,都可以直接进行交流通信

      • EventBus - vue2
        • $root
          • 访问根父组件
        • $on
          • 绑定自定义事件监听
        • $emit
          • 分发自定义事件
      • mitt - vue3
        • globalProperties
          • 配置全局事件总线
        • getCurrentInstance
          • 支持访问内部组件实例
          • 获取到ctx代替this

      47、项目优化?

      性能优化

      • 按需加载
        1. 图片按需加载,只下载可视区附近的图片
        2. 组件懒加载,路由懒加载,其实路由懒加载本质也是组件懒加载。
        3. 其他资源懒加载,避免由于模块引用关系不当,导致首屏页面加载了首屏用不到的CSS、字体图标、图片等资源。
      • 精灵图
        • 把多张小图合成一张大图,通过css中的background-position属性,显示精灵图中某一个小图标。
      • 字体(使用iconfont图标字体代替图片)
        1. 图标缩放不失真,颜色可更改
        2. 字体图标相对图片图标体积更小
        3. 减少请求次数,一个文件可包含所有的图标
        4. 减少大量使用图片,从而节省代码量,降低维护成本
      • 减少请求
        1. 合并请求,由于每次请求时候,实际传输的内容只占整个请求过程的较少一部分时间,因此合并内容让多个请求变成一个,可以节约请求中建立连接、排队等待等耗时。
        2. 雪碧图,图片合成,避免每个图片都要发一次请求。
        3. 内联较小的js css、图片(转成base64)等资源,避免再发一次请求获取资源。
      • 分页降低传输数量
        • 请求列表传参pagesize
      • 其他
        • 移除控制台输出
        • 不在css文件内使用@import引入,会造成额外的i请求
        • 异步加载js(asyncdefer放到body底部动态创建script标签…)

      48、如何实现文件按需引入?

你可能感兴趣的:(原型模式,javascript,前端)