前端常见面试题总结

js数据类型

基本数据类型有

  • Number
  • String
  • Boolean
  • Null
  • Undefined
  • Symbol(es6引进,表示独一无二的值;对象属性名都是字符串容易造成属性名冲突,主要是避免这种情况发生)
    • 每一个 Symbol() 返回的值都是唯一的;(Symbol(“ceshi”) === Symbol(“ceshi”); // false )
    • 不能被new;(let a = new Symbol();//报错 Symbol is not a constructor);
    let a = Symbol.for('a')
    let b = Symbol('a')
    let obj = {
        [a]:22,
        [b]:33,
        c:33
    }
    for(let i in  obj){
        console.log(i);//3
    }
     console.log(obj);//[c:33,Symbol(a): 22,Symbol(a): 33]
     console.log(obj[a]);//22
  • bigInt(用于当整数值大于Number数据类型支持的范围时。这种数据类型允许我们安全地对大整数执行算术操作,表示高分辨率的时间戳,使用大整数id,等等。)
    • 不允许在bigint和 Number 之间进行混合操作;(10+10n// 报错)
    • 不能将BigInt传递给Web api和内置的 JS 函数,这些函数需要一个 Number 类型的数字。
    • 当 Boolean 类型与 BigInt 类型相遇时,BigInt的处理方式与Number类似只要不是0n,BigInt就被视为truthy的值。
    • BigInt可以正常地进行位运算
    • 元素都为BigInt的数组可以进行sort
  10+10n;    // → Cannot mix BigInt and other types, use explicit conversions
  
  Math.max(2n, 4n, 6n);    // → Cannot convert a BigInt value to a number
  10n + 20n;    // → 30n	
	10n - 20n;    // → -10n	
	+10n;         // → TypeError: Cannot convert a BigInt value to a number	
	-10n;         // → -10n	
	10n * 20n;    // → 200n	
	20n / 10n;    // → 2n	
	23n % 10n;    // → 3n	
	10n ** 3n;    // → 1000n

引用数据类型统称为Object类型,细分的话有

  • Object
  • Array
  • Date
  • Function
  • RegExp
  • 基本数据类型的数据直接存储在栈中;而引用数据类型的数据存储在堆中,在栈中保存数据的引用地址,这个引用地址指向的是对应的数据,以便快速查找到堆内存中的对象。

创建变量的几种方式

● 1、 var 一个变量
● 2、 let 创建变量
● 3、 const 创建常量(不允许被修改)(ES6)
● 4、function 创建函数变量 (ES5)
● 5、import 导入变量 (ES6)
● 6、class创建类(ES6)
● 7、symbol 创建唯一值

判断数据类型的方法

  • typeof 检测数据类型的属性
    • 他的返回值是一个字符串存放的是他的类型
    • typeof检测数组、普通对象、null返回的都是’object’,所以用typeof检测数据类型,无法细分出是普通对象,是数组,是null
    console.log(typeof 12) //'number'
    console.log(typeof '12')//'string'
    console.log(typeof undefined)//'undefined'
    console.log(typeof null)//'object'
    console.log(typeof true)//'Boolean'
    console.log(typeof [])//'object'
    console.log(typeof ({}))//'object'
     // 只要两个及两个以上typeof,那最后的结果就是 'string'
    console.log(typeof typeof typeof 12)//'string'
  • instanceof 检测当前实例是否属于某个类(改变了当前实例的原型链,检测结果就不再准确了; 不能检测字面量方式创建的基本数据类型值)
    • 实例 instanceof 类,如果实例是属于这个类,那就返回true,反之就是false
    • 只要当前类的原型在当前实例的原型链上,都返回true;
 function fn(){
            arguments.__proto__ = Array.prototype;
           console.log( arguments instanceof Array) //true 
        }
  fn(1,23,4)
let num = new Number(11);
 console.log(num instanceof Number)//true
  • constructor 基于构造函数检测数据类型(也是基于类的方式)
    • 原型中天生自带的属性名
    • 不能检测基本数据类型的null和undeined
    • 如果原型扩展以及继承都有可能导致原型链指向发生改变,结果不准确
   function Fn() {
         }
        let f = new Fn;
         console.log(f.constructor === Fn)//true
         function Fn2() {

        }
        Fn2.prototype = {}
         let f2= new Fn2;
         console.log(f.constructor === Fn2) //false
         f2.constructor = 6;
        console.log(f2.constructor) //6
  • Object.prototype.toString.call() (最好用的方式可以简写为toString.call()因为window的__proto__指向能找到Object的原型)
    • Object原型上的tostring方法执行,会默认返回当前方法中的this对应的结果;结果为"[object 检测出的类型]“
        let ary =let ary = [1, 2];
        let obj = {}
        let num = 1
        console.log(Object.prototype.toString.call(num))  
        //他的返回值是一个字符串,里边是 '[object 你当前实例的所属类]'
        //只能检测内置类,不能检测自定义类(call(自定义类的实例)都是object)
        function Fn(){};
        let f =new Fn;
         console.log(toString.call(f));//[object object]

怎么让一个 div 水平垂直居中?

/* 已知宽高 */
.box {
    position: absolute;
    top: 50%;
    left: 50%;
    margin-top: -50px;
    margin-left: -50px;
    width: 100px;
    height: 100px;
}
/* 未知宽高 */
.box {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
}
----------
.box {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    margin: auto;
}
----------
.container {
    display: flex;
    justify-content: center;
    align-items: center;
}

前端性能优化有哪些?

代码方面的性能优化

  • 减少对闭包的使用(因为过多使用闭包会产生很多不销毁的内存,处理不好的话,会导致内存溢出:栈溢出)减少闭包的嵌套,减少作用域链的查找层级
  • 对于动画来说:能用 css 解决的就不用 js 动画(能用 transform 处理的,不用传统的css样式,因为 transform 开启硬件加速,不会引发回流),再或者使用定位的元素也会好很多,因为定位的元素脱离文档流,不会对其他元素的位置产生影响。能用 requestAnimationFrame 解决的不用定时器;requestAnimationFrame还有一个好处,当页面处于休眠无访问状态,动画会自己暂停,直到恢复访问才开始,而定时器是不论什么状态,只要页面不关,就一直处理
  • 减少直接对 dom 操作,原因是减少 dom 的回流和重绘(当代项目基本上都是基于 mvvm / mvc 数据驱动视图渲染的),对 dom 的操作框架本身完成,性能要好很多
  • 低耦合高内聚(基于封装的形式: [方法封装]、插件、组件、框架、类库等封装,减少页面中的冗余代码,提高代码使用率 )
  • 尽可能使用事件委托,性能至少提高50%
  • 项目中尽可能使用异步编程来模拟出多线程的效果,避免主线程阻塞(异步操作基于 promise 设计模式来管理)
  • 尽可能减少选择器的层级:.box a{} 和 a{} 第二个性能好,选择器是从右向左解析
  • 减少使用 eval 主要原因是防止压缩代码的时候,由于符号书写不合规导致代码混乱
  • 函数的防抖和节流

减少 HTTP的请求次数和报文的大小

  • 图片懒加载(延迟加载)技术
  • 对于数据我们也尽可能分批加载(不要一次请求过多的数据,例如分页技术)
  • 图片地图:对于多次调取使用的图片尤其是背景图,我们尽可能把它提取成公共的样式,而不是每一次都重新设置 background
  • 图片 base64 :用 base64 码
  • css sprite (雪碧图、图片精灵)技术

设置各种缓存、预处理和长连接机制

  • 把不经常更改的静态资源做缓存处理:一般做的是304或者 etag 等协商缓存
  • 建立 connection :keep-alive tcp 长链接
  • 设置本地的离线存储(manifest)或者把一些不经常更改的数据做本地临时存储(webstorage、indexdb)等

浏览器输入url到回车发生了神马

  1. 发送请求,浏览器先解析中域名,浏览器判断是否之前访问过,找自身的缓存;如果浏览器缓存没有,那么再找本地硬盘,如果也没有,浏览器发送DNS请求,如果本地DNS服务器先找缓存,缓存再去列表查找,如果也没有,就会去域服务器查找,直到找到,会把这个域名和IP缓存到本地,方便下次进行访问
  2. 得到服务器IP地址以后,需要和服务器建立TCP连接(三次握手,四次挥手)
  3. 握手成功以后,客户端要发送真正的http请求(请求方法,请求头,请求正文)
  4. 服务器根据请求信息进行数据的查找,数据的整合,最后返回给客户端
  5. 关闭TCP连接(四次挥手),客户端和服务器需要相互确认
  6. 当浏览器接受html,css,js,浏览器需要进行解析和加载,先形成DOM树 -> css树 -> render树
  7. 浏览器最终渲染

节流和防抖的意思及实现

  • 防抖简单的理解方式就是:用户多次触发事件,在用户一直触发事件中,事件不会执行,只有在用户停止触发事件一段时间之后再执行这个事件一次。(经常用于搜索框;)
  • 节流简单的理解方式就是:用户多次触发事件,在用户一直触发事件过程中事件会每间隔一段时间执行一次,会执行多次。

防抖实现原理思路

  • 用户每一次触发事件都会延迟执行,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。最终只有用户连续触发这个事件的间隔时间超出我们设置的参数ms毫秒之后,该事件才会触发一次
/******************* 非立即执行 *************************/
function debounce(fn, wait) {
        let timer;
        return function () {
            // 每次进到这个函数中,需要把上次定制的定时器任务给清除掉
            if (timer) {
                clearTimeout(timer);
            }
            timer = setTimeout(() => {
                fn.apply(this)// 通过apply修改了fn执行时里面的this指向,让它指向绑定那个元素
            }, wait);
        }
    };
    /******************* 立即执行版 *************************/
//立即执行版的意思是触发事件后函数会立即执行,然后 n 秒内不触发事件才能继续执行函数的效果。
 function debounce(func, wait) {
        let timeout;
        return function () {
            let context = this;
            let args = arguments;

            if (timeout) clearTimeout(timeout);

            let callNow = !timeout;
            timeout = setTimeout(() => {
                timeout = null;
            }, wait)

            if (callNow) func.apply(context, args)
        }
    }
 /********************* 二合一 **********************************/
   /**
         * @desc 函数防抖
         * @param func 函数
         * @param wait 延迟执行毫秒数
         * @param immediate true 表立即执行,false 表非立即执行
         */
  function debounce(func, wait, immediate) {
      let timeout;
      return function () {
          let context = this;
          let args = arguments;
          if (timeout) clearTimeout(timeout);
          if (immediate) {
              var callNow = !timeout;
              timeout = setTimeout(() => {
                  timeout = null;
              }, wait)
              if (callNow) func.apply(context, args)
          } else {
              timeout = setTimeout(function () {
                  func.apply(context, args)
              }, wait);
          }
      }
  }

节流实现思路

  • 用户每一次触发事件都会设置一个延迟定时器,但是如果已经设置了延迟定时器就会等上一次延迟定时器执行之后才会开启下一个定时器,这样用户一直触发事件,事件会每间隔一段时间执行一次,释函数的执行频率;
 // 定时器版:
    // 在持续触发事件的过程中,函数不会立即执行,并且每 1s 执行一次,在停止触发事件后,函数还会再执行一次。
    function throttle(func, wait) {
      let timeout;
      return function () {
        let context = this;
        let args = arguments;
        if (!timeout) {
          timeout = setTimeout(() => {
            timeout = null;
            func.apply(context, args);
          }, wait);
        }
      };
    }
 // 时间戳版:
        // 在持续触发事件的过程中,函数会立即执行,并且每 1s 执行一次。
         function throttle(func, wait) {
             let previous = 0;
            return function () {
                 let now = Date.now();
                 let context = this;
                 let args = arguments;
                 if (now - previous > wait) {
                    func.apply(context, args);
                     previous = now;
                }
             }
         }
// 双剑合璧版:
        /**
         * @desc 函数节流
         * @param func 函数
         * @param wait 延迟执行毫秒数
         * @param type 1 表时间戳版,2 表定时器版
         */
        function throttle(func, wait, type) {
            if (type === 1) {
                var previous = 0;
            } else if (type === 2) {
                var timeout;
            }
            return function () {
                let context = this;
                let args = arguments;
                if (type === 1) {
                    let now = Date.now();

                    if (now - previous > wait) {
                        func.apply(context, args);
                        previous = now;
                    }
                } else if (type === 2) {
                    if (!timeout) {
                        timeout = setTimeout(() => {
                            timeout = null;
                            func.apply(context, args)
                        }, wait)
                    }
                }
            }
        }

call,bind,apply 区别及实现

  • 相同点:call和apply都是默认执行这个函数并将this指向第一个参数,不返回新的函数;
    • a.call(b,1,2,3,4,5);
    • a.apply(b,[1,2,3,4])
  • 区别:call从第二个参数开始是给函数传递的参数,参数一个一个传过去。apply第二个参为给函数传递的参数参数为数组。
  • bind:返回一个改变了this指向的参数的新函数;第一个为this指向的参数,第二个开始给函数传的参;传参和call一样;
    • let c = a.bind(b,1,2,3, 4,5) ;c();

call,bind,apply 简单实现

  • mybind (返回一个改变了这个指向的新函数)
let a = {
        name: '李明',
        sex: 'man',
        age: '23'
    }
    function b() {
        console.log(this);
    }
    Function.prototype.mybind = function (context) {
        if (typeof this !== 'function') {
            return '这不是一个函数'
        }
        let contThis =  this;
        const args = Array.prototype.slice.call(arguments,1)
        return function F(){
            return contThis.apply(context,args.concat(arguments))
        }
    }
  • mycall,myapply(思路一样,传参方式不同)
Function.prototype.mycall = function (context) {
        if (typeof this !== 'function') {
            return '这不是一个函数'
        }
       let $this = context || window;
       const args = Array.prototype.slice.call(arguments,1);
       $this.fn = this;
       let reset = $this.fn(...args);
       delete $this.fn;
       return reset
    }
 /*****************************************************/
 Function.prototype.myapply = function (context) {
        if (typeof this !== 'function') {
            return '这不是一个函数'
        }
       let $this = context || window;
       $this.fn = this;
       let reset = null;
       if(arguments[1]){
         reset =   $this.fn()
       }else{
         reset = $this.fn(arguments[1])
         }
      delete $this.fn;
      return reset
    }

new内部都实现了什么,手写一个new

  • 当new一个构造函数时,首先创建一个空对象;
  • 让当前函数里的this指向这个对象(使新对象的__proto__指向原函数的prototype);
  • 执行该函数
  • 默认return 这个对象

mynew(简单实现一下基本思路)

function Mynew(fn,...rest){
          //创建一个空对象并将他的__proto__指向fn.prototype;
		  const thisObj = Object.create(fn.prototype);
		  //将thisObj作为fn的this,继承其属性,并获取返回结果为result
		  const result = fn.apply(thisObj,rest);
		  //根据result对象的类型决定返回结果
		  return typeof result === "object" ? result : thisObj;
   }

出现null和undefined的情况

出现undefined的情况
  • 创一个变量不赋值,获取这个变量是undefined
  • 获取对象里的属性名所对应的属性值获取不到,是undefined
  • 函数里,实参没有给形参变量赋值,那就是undefined
  • 函数里,没有return(没有返回值),函数里执行结果就是undefined
出现null的情况
  • 清对象空间地址手动赋值为null
  • 通过getElementById获取元素,没有对应的ID,那就是null
  • 当正则用捕获时,捕获不到内容,默认捕获结果是null.

事件循环机制是神马

-说明: JS代码执行分为主任务队列和等待任务队列;在执行主栈的代码遇到同步的代码会立即执行,遇到异步的代码会先放到等待队列中,放入时区分是宏任务还是微任务,按照不同的任务放到等待队列不同的池子中;当主栈执行完成时,那么要先去等待队列的微任务中去遍历,按照放入时间先后依次执行,把微任务放到主栈中去执行,微任务执行完毕,再去执行宏任务。
在这里插入图片描述

  function fn1(){console.log(666);}
        setTimeout(function(){
            console.log(800);
        },0)
        console.log(900);
        async function fn(){
            console.log(999);
            await fn1();
            console.log(888); 
        }
        let  p = new Promise(function(resolve,reject){
            console.log(200);
            resolve();
            console.log(300);
        })
        p.then(function(){
            console.log(100);// 异步的
        });
        fn();
        // 900  200  300 999  666 100 888  800

垃圾回收机制

  • GC垃圾回收机制;在浏览器内置了一个gC程序,这个程序会每隔一段时间执行一次;
  • 谷歌浏览器-标记法:浏览器每隔一段时间要对所有的空间地址类型进行检测,检查该地址是否被占用;如果没有占用,那么久立即回收掉
  • IE和火狐-计数法:浏览器采用计数的规则,如果空间地址被占用一次,那这个空间地址就默认+1,每空闲一次,空间地址就默认-1,如果浏览器发现有为0的空间地址,就把其回收

面向对象,构造函数及原型链的理解

面向对象
  • 把抽象的对象按照特点进行分类(大类/小类),把类的公共特征进行提取和封装,放到对应的类别中
  • 类就是对对象的一种细分,和公共部分的抽取
  • 在类中具体派生出来的具体事物就是类的实例,而且实例拥有自己私有的特征,还拥有所属类上的特征
  • 我们研究面向对象,其实就是研究对象、类、实例之间的关系和各自的知识点
构造函数
  • 构造函数解决了实例的私有属性
    ● new 操作符——把 new 放在函数的前面,函数执行
    ● 构造函数就是让我们创建自定义类
    ● new 函数执行 叫做构造函数运行模式,此时的Fn就是Fn类(构造函数),函数执行之后的返回结果就是一个对象,叫做实例对象(f就是Fn的实例)
    ● 类就是函数数据类型的
    ● 实例是对象数据类型
    ● 构造函数中的this指向当前实例
    ● 如果这个函数需要参数,那么这个需要有小括号,如果不需要参数,那么小括号可以省略
    ● new后面的函数就是构造函数,也叫类;那么通过new函数得到的返回值就叫实例;实例是构造函数new出来的;
  • 构造函数运行原理
  • -当new一个构造函数时,首先创建一个空对象;
  • 让当前函数里的this指向这个对象(使新对象的__proto__指向原函数的prototype);
  • 执行该函数
  • 默认return 这个对象
构造函数与普通函数区别
  • JS为了区分构造函数和普通函数,一般将构造函数首字母大写
  • 运行不同
    • 普通函数–>形成私有作用域–>形参赋值–>变量提升–>代码执行–>作用域是否销毁
    • 构造函数–>形成私有作用域–>形参赋值–>变量提升–>默认生成一个对象–>把this指向这对象–>代码执行–>默认把这个对象return出去–>作用域是否销毁
  • 执行上的不同
    • 构造函数如果不传实参,可以不加小括号
  • 构造函数如果手动return一个基本数据值,不能改变人家的返回值,但是手动return引用数据类型,可以改变构造函数的返回值,此时return的东西已经不是当前类的实例了【所以不要轻易修改构造函数的返回值】
原型原型链
  • 原型解决了实例的公有属性
    在这里插入图片描述

Class类

class Bar{ // 这既不是一个函数的{},也不是对象的{};
        constructor(x,y){
            // 这个的代码就相当于函数体中的代码;
            // 这里面可以新增实例私有属性
            //this.x=x;
           this.y=y;
        }
        // 在原型上新增方法;
        getX(){// 这不是箭头函数
            console.log("X");
        }
        getY(){
            console.log("Y");
        }
        static x=1// static可以给Bar类新增私有属性
        y=2// 在给实例新增私有属性
    }
  • 每一个函数(普通函数,构造函数)都天生自带一个prototype属性,属性值是一个对象,它里面存储的是实例的公有属性(原型)
  • 每一个原型都天生自带一个constructor属性,其属性值指向当前原型所属的类
  • 每一个对象都天生自带一个__proto__属性,其属性值指向当前实例所属类的原型
原型链

在这里插入图片描述

  • 在对象里查找-一个属性,先看自己私有的有没有,如果自己没有,就通过__ proto__ 属性找 到当前所属类的原型上,如果原型上有,就直接用,如果没有,继续通过原型的__ proto__ 继续往所属类的原型上找,直到找到Object类的原型上找,如果还没有,就是undefined, 这种级- 一级一级向上查找就会形成原型链
原型重定向

● 内置类的原型不能被重定向
● 可以覆盖内置类原型上的方法
● 用新的空间地址覆盖Fn原有的空间地址;会导致constructor的丢失;

js 继承方式有哪些及优缺点

原型继承
  • 让类B的原型指向类A的实例,那么以后类B的实例既可以调取类A实例的私有属性,也可以调取类A实例的公有属性,那这种继承方式就是原型继承
    • 原型继承:继承私有和公有( 通过改变prototype的指向,使其指向其他实例)
      在这里插入图片描述
       function A(){
           this.getX = function(){console.log('恭喜发财')}
       };
       A.prototype.getY = function(){
           console.log('真好')
       };
       function B(){}
       B.prototype = new A;
       let f = new B;
       f.getX()//恭喜发财
         f.getY()//真好
中间类继承
  • 例如arguments类数组,不是一个数组;虽然不是Array的实例,但是我们可以手动把arguments的__proto__指向Array的原型,那这样arguments就可以使用Array原型上的方法了,这就是中间类继承
  • 只能继承公有属性(过自己的__proto__指向类的prototype(原型))
        function fn(){
           console.log(arguments instanceof Array)
           arguments.__proto__ = Array.prototype;
           console.log(arguments.push(23))
            console.log(arguments) 
        }
        fn(1,2,3,4,5)
call继承
  • 在类B中,调用了类A,并且通过call改变了类A中的this指向,使其指向B的实例;这样类B创建的实例就具有类A的私有属性;这种继承就是call继承
        /* 
        原型继承:继承私有和公有
        中间类继承:公有
        call继承:私有属性
        */
        function A(){
            this.x =10 
        }
        A.prototype.getX = function(){
            console.log('万事如意')
        }

        function B(){
            // this->当前实例
            /* 
            类B当做构造函数执行时,此时的this是当前实例,
            */
            this.a = 20
            A.call(this)  // 把函数A当做普通函数执行,并且把A的this指向了类B的实例

        }
 //call继承让类B继承了类A的私有属性,但是不能使用类A的公有属性
// 类B的所有实例都可以使用类A的私有属性
        let f = new B;
        console.log(f)
 f.getX()//f.getX is not a function
 
寄生混合继承
  • 使用call继承继承了私有属性,Object.create继承了公有属性,这种继承方式就是寄生组合继承;
  • 为了防止修改B的原型时,修改了A的原型,所以使用Object.create的方法
  • 继承公有和私有
    在这里插入图片描述
function A(){
            this.a = 10
        }
        A.prototype.getX = function(){
            console.log('恭喜发财')
        }

        function B(){
            /* 
            函数B以构造函数的身份运行
            那类B中的this指向当前实例
            */
            this.x =20;
            A.call(this) // 让函数A以普通函数身份运行,而且把函数A中的this指向了类B的实例
          // 继承私有属性
        }
        B.prototype = Object.create(A.prototype);// 继承公有属性;
        // 创建一个空对象,让空对象的__proto__指向类A的原型,在把这个空对象赋值给类B的原型
        let f = new B;
        f.__proto__.getY = function(){
            console.log(333)
        };
        let m = new A;
        // m.getY()
        console.log(f)
Class继承

在这里插入图片描述

  // ES6中class创造出来的类不能当做普通函数执行
        class A {
            constructor(q) {
                this.x = q;
            }
            getX() {
                console.log(this.x)
            }
        }
        // ES6中的继承
        class B extends A {
            constructor(name) {
                // 子类继承父类,可以不写constructor,但是你要是一旦写了,那在constructor里第一句话就要写super()
                // 你要是不写constructor,那浏览器会默认创建一个constructor(...arg){
                //     super(...arg)
                // }
                super(200) // A.call(this, 200) 把父类当做普通函数执行,给方法传递参数,
              让方法中的this是子类的实例

                this.y = 100;
            }
            getX() {
                console.log(this.y)
            }
        }
        B.prototype = Object.create(A.prototype); // class定义的类不能改原型重定向
        let f = new B(100);
        console.log(f)

闭包的理解

  • 简单来说闭包就是在函数里面声明函数,本质上说就是在函数内部和函数外部搭建起一座桥梁,使得子函数可以访问父函数中所有的局部变量,但是反之不可以,这只是闭包的作用之一,另一个作用,则是保护变量不受外界污染,使其一直存在内存中,在工作中我们还是少使用闭包的好,因为闭包太消耗内存,不到万不得已的时候尽量不使用。

你一般怎么合并对象

  • 展开运算符
let a ={a:1,b:2};
let b ={c:1,d:2,...a};
  • Object.assign()
let a ={a:1,b:2};
let b ={c:1,d:2};
let c = Object.assign(a,b);

跨域碰到过吗,常见解决方案

什么是跨域
  • 浏览器同源策略限制的一类请求场景;跨域是指一个域下的文档或脚本试图去请求另一个域下的资源,这里跨域是广义的。
同源策略
  • 为了保证浏览器的信息安全,浏览器采用同源策略,保证当前源中的资源只能在当前的源中使用;其他源如果需要使用当前源资源,需要特殊技术,这种 A 源访问 B 源的资源的通信称为跨域;
同源策略要求
  • 同源策略要求通信的两个源的协议、域名、端口号要相同,如果三者中任意一个不同就是不满足同源策略;不满足同源策略的通信就是跨域;
解决方法
jsonp
  • JSONP 跨域原理: 利用了script的src属性是非同源策略的,就可以获取到非同源的数据,然后当数据请求成功,再调用callback的回调函数;会把数据传递给这个回调函数的第一个参数;
  • 缺点
    1 . JSONP: 需要后端配合
    2 . JSONP: 只能发送get请求,不能发送post请求;
  • 运行方式
  1. 提前声明一个叫做 fn 的函数,给 fn 设置一个形参;
  2. 在页面给 script 的 src 的指向的路径拼接一个 callback 属性,callback=fn;当浏览器解析到这个 script 标签时,会向 src 指向的路径发起 http 请求;
  3. 服务器收到这个请求后,会返回一个 fn (这里面是服务器返回的数据)
  4. fn({xxx}) 这个是让 fn 执行,小括号里面就是服务器发送给我们的数据
<script>
	function jsonp(options) {
            return new Promise(function (resolve, reject) {
                window[options.cb] = function (data) {
                    resolve(data);
                    document.body.removeChild(script);
                }
                let script = document.createElement("script");
                let str = `${options.url}?`;
                for (let key in options.params) {
                    str += key + "=" + options.params[key] + "&"
                }
                str += "cb=" + options.cb;
                script.src = str;
                document.body.appendChild(script);
            })
        }
        jsonp({
            url: "/login",
            params: {
                user: 11223
            },
            cb: "fn"
        }).then(function (data) {
            // data就是成功获取的数据

        })
    </script>
    <script src="http://www.baidu.com?callback=fn"></script>
CORS
  • 需要浏览器和后端同时支持。浏览器会自动进行 CORS 通信,实现 CORS 通信的关键是后端。只要后端实现了 CORS,就实现了跨域。服务端设置 Access-Control-Allow-Origin 就可以开启 CORS。
proxy代理(适用于本地开发)
nginx 代理跨域,
  • nginx 是服务器应用程序,它可以接受客户端的请求,然后根据规则可以配置自动转发

去重和排序的方法

排序

/********* 插入排序 ************/
 let ary = [12, 23, 45, 11, 10, 23];
        function insert(ary) {
            let handAry = []; // 用来存放手里的牌
            handAry.push(ary[0]); // 抓了第一张牌

            // 这个循环就是抓牌
            for (var i = 1; i < ary.length; i++) {
                let item = ary[i] // 抓的每一张牌
                // 控制的是跟我手里的每一张牌去做比较(从右往左比较)
                for (var j = handAry.length - 1; j >= 0; j--) {
                    let cur = handAry[j] // 手里的每一张牌
                    // 如果抓的牌比手里的牌大了,就插入到这张牌的后面,并且停止比较
                    if (item > cur) {
                        handAry.splice(j + 1, 0, item);
                        break;
                    }
                    // 如果能运行到这,证明抓的这张牌比我手里的牌都小,就直接放到数组的最前面
                    if (j === 0) {
                        handAry.unshift(item);
                    }
                }
            }
            return handAry

        }
        console.log(insert(ary))
/********* 冒泡排序 ************/
let ary = [12,23,11,34,10,9];
        function buble(ary){
            // 控制的比较的轮数
            for (var i = 0; i < ary.length-1; i++) {
                // 控制的是每一轮比较的次数
                for (var j = 0; j < ary.length-1-i;j++) {
                    // 如果前一项大于后一项就两两交换位置
                   if(ary[j]>ary[j+1]){
                       let temp = ary[j];
                       ary[j] = ary[j+1];
                       ary[j+1] = temp;
                   }
                }
            }
            return ary
        }
        console.log(buble(ary))

vue

v-show 和 v-if区别

  • v-show是css切换,v-if是完整的销毁和重新创建使用 频繁切换时用v-show,运行时较少改变时用v-if,v-if=‘false’ v-if是条件渲染,当false的时候不会渲染

vue生命周期

选项式 API(vue2)Hook inside setup(vue3)
beforeCreatesetup
createdsetup
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeUnmountonBeforeUnmount
unmountedonUnmounted
errorCapturedonErrorCaptured
renderTrackedonRenderTracked
renderTriggeredonRenderTriggered
  • 因为 setup 是围绕 beforeCreatecreated 生命周期钩子运行的,所以不需要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该直接在 setup 函数中编写

双向数据绑定原理

vue2

  • vue采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty劫持data属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调;mVue 2.x 里,是通过 递归 + 遍历 data 对象来实现对数据的监控的,如果属性值也是对象那么需要深度遍历

vue3

  • Vue3.0 采用了 Proxy代理的方式Proxy 可以劫持整个对象,并返回一个新的对象。Proxy 不仅可以代理对象,还可以代理数组。还可以代理动态增加的属性。

vue3相比于vue2双向数据绑定的优势

在这里插入图片描述

Vue 组件 data 为什么必须是函数 ?

  • 因为组件是可以复用的,JS 里对象是引用关系,如果组件 data 是一个对象,那么子组件中的 data 属性值会互相污染,产生副作用。
    所以一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝。new Vue 的实例是不会被复用的,因此不存在以上问题

谈谈你对 keep-alive 的了解?

  • keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,避免重新渲染 ,其有以下特性:
    • 一般结合路由和动态组件一起使用,用于缓存组件;
    • 提供 include 和 exclude 属性,两者都支持字符串或正则表达式, include 表示只有名称匹配的组件会被缓存,exclude 表示任何名称匹配的组件都不会被缓存 ,其中 exclude 的优先级比 include 高;
    • 对应两个钩子函数 activated 和 deactivated ,当组件被激活时,触发钩子函数 activated,当组件被移除时,触发钩子函数 deactivated。

vue组件通信方式

  1. props / $emit(适用 父子组件通信,vue2,3通用)
// 父组件 方法通过@xxx传入 变量 :xx传入子组件
 <template v-slot:ui-box>
      <page-required-pop v-model:show="popupShow" @cancel="cancel" />
  </template>
//子组件
setup(props, context) {
props.show//获取传入的值
context.emit("cancel", show.value);//emit调用
}
  1. ref 与 $parent / $children 适用 父子组件通信
  • ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例
  • $parent / $children:访问父(获取父组件实例) / 子实例(获取父组件下的所有子组件实例,返回的是一个数组)
  1. EventBus ($emit / $on) 适用于 父子、隔代、兄弟组件通信(vue3需要自己手动写个发布订阅)
let eventBus = new Vue;// 这就是一个容器;联系了兄弟之间的纽带
     created(){
                eventBus.$on("changeGreen",this.changeGreen)//添加到事件池
            },
            methods:{
                fn1(){
                    eventBus.$emit('changeRed');//执行
                },
                changeGreen(){
                    this.color="绿色"
                }
            },
  1. provide / inject 适用于 隔代组件通信
  • 祖先组件中通过 provider 来提供变量,然后在子孙组件中通过 inject 来注入变量
app.component('todo-list', {
  data() {
    return {
      todos: ['Feed a cat', 'Buy tickets']
    }
  },
 // provide: {
 //   user: 'John Doe'
//  },
 provide() {//推荐写成这种形式
    return {
      user: 'John Doe'
    }
  },
})

app.component('todo-list-statistics', {
  inject: ['user'],//取值
  created() {
    console.log(`Injected property: ${this.user}`) // > 注入  John Doe
  }
})
  1. vuex

Vue 中的 key 有什么作用?

  • key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速;因为带 key 就不是就地复用了,在 sameNode 函数 a.key === b.key 对比中可以避免就地复用的情况。所以会更加准确。利用 key 的唯一性生成 map 对象来获取对应节点,比遍历方式更快,

Vue中的nextTick作用

  • 用于DOM的异步更新;在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
  • 例如:在数据改变后要执行的操作,而这个操作需要等数据改变后而改变DOM结构的时候才进行操作,需要用到nextTick

computed和watch的区别

  • 如果用computed和watch都可以实现,能用computed就不要用watch;原因是computed有缓存;

区别 :

  1. computed 会默认走缓存,减少性能的开销 ;watch :不走缓存,性能开销大
  2. computed 不支持异步;watch支持异步
  3. computed 当一个属性依赖多个属性变化而变化时,这个属性适合用computed;多对一;watch当这个属性发生改变会影响其他属性时,这个属性用watch,一对多
  • watch 如果想深度监听的话,后面加一个deep:true;让watch立即执行immediate:true;

computed的原理?

  • computed 本质是一个惰性求值的观察者。
    computed 内部实现了一个惰性的 watcher,也就是 computed watcher,computed watcher 不会立刻求值,同时持有一个 dep 实例。
    其内部通过 this.dirty 属性标记计算属性是否需要重新求值。
    当 computed 的依赖状态发生改变时,就会通知这个惰性的 watcher,
    computed watcher 通过 this.dep.subs.length 判断有没有订阅者,
    有的话,会重新计算,然后对比新旧值,如果变化了,会重新渲染。 (Vue 想确保不仅仅是计算属性依赖的值发生变 化,而是当计算属性最终计算的值发生变化时才会触发渲染 watcher 重新渲染,本质上是一种优化。)
    没有的话,仅仅把 this.dirty = true。 (当计算属性依赖于其他数据时,属性并不会立即重新计算,只有之后其他 地方需要读取属性的时候,它才会真正计算,即具备 lazy(懒计算)特性。)

MVC与MVVM有什么区别

mvvm在这里插入图片描述

  • M - Model,Model 代表数据模型,也可以在 Model 中定义数据修改和操作的业务逻辑
  • V - View,View 代表 UI 组件,它负责将数据模型转化为 UI 展现出来
  • VM - ViewModel,ViewModel 监听模型数据的改变和控制视图行为、处理用户交互,简单理解就是一个同步 View 和 Model 的对象,连接 Model 和 View
    ○ View 接收用户交互请求
    ○ View 将请求转交给ViewModel
    ○ ViewModel 操作Model数据更新
    ○ Model 更新完数据,通知ViewModel数据发生变化
    ○ ViewModel 更新View数据

mvc

在这里插入图片描述

○ View 接受用户交互请求
○ View 将请求转交给Controller处理
○ Controller 操作Model进行数据更新保存
○ 数据更新保存之后,Model会通知View更新
○ View 更新变化数据使用户得到反馈

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值