JS高频面试题

JS

文章目录

数据类型

什么是基本数据类型?什么是引用数据类型?以及各个数据类型是如何存储的?(5星)

基本数据类型:Number(数值)、String(字符串)、Boolean(布尔值)、Null(空值)、Undefined(未定义)、Symbol(独一无二的值)、

引用数据类型统称为Object 通过new关键字创建的对象(系统对象、自定义对象),如Object、Array、function,等
数据类型 USONB 你很牛逼 U:undefined S:string symbol O:object N:number null B:boolean
数据储存:
1.基本数据类型的数据直接存储在栈中;而引用数据类型在栈中仅保存数据的引用地址,真实的数据存储在堆中,这个引用地址指向的是对应的真实数据,以便快速查找到堆内存中的对象。

2.栈内存由操作系统分配释放。 堆内存一般由程序员分配释放,若程序员不释放,由垃圾回收机制回收。

JS有哪些内置对象

基本对象Object,内置对象有Array Math Number String Date JSON

类型转换

在JS中为什么0.2+0.1>0.3?(4星)

在于在JS中采用的IEEE 754的双精度标准,计算机内部存储数据的编码的时候,0.1在计算机内部根本就不是精确的0.1,而是取了近似值0.1。出现了精度缺失

// 转成十进制正好是 0.30000000000000004

为什么0.2+0.3=0.5呢?(4星)

答:0.20.3分别转换为二进制进行计算:在JS中实际取值只能存储52位尾数位,它们相加又恰巧前52位尾数都是0,就是0.5

既然0.1不是0.1了,为什么在console.log(0.1)的时候还是0.1呢?(3星)

console.log的时候会二进制转换为十进制,十进制再会转为字符串的形式,在转换的过程中发生了取近似值,所以打印出来的是一个近似值的字符串

为什么typeof null是Object?(4星)

  • 因为在JavaScript中,如果二进制前三位都是0的话,系统会判断为是Object类型,而null的二进制全是0,自然也就判断为Object

判断数据类型有几种方法 ?(5星)

  • typeof

    • 缺点:typeof null的值为Object,无法分辨是null还是Object
  • instanceof 判断一个实例是否属于某种类型
    Object instanceof Object //true

    • 缺点:只能判断对象是否存在于目标对象的原型链上,不能判断字面量的基本数据类型
  • Object.prototype.toString.call()

    • 一种最好的基本类型检测方式 Object.prototype.toString.call() ;它可以区分 null 、 string 、boolean 、 number 、 undefined 、 array 、 function 、 object 、 date 、 math 数据类型。

    • 缺点:不能细分为谁谁的实例

      // -----------------------------------------typeof
        typeof undefined // 'undefined' 
        typeof '10' // 'String' 
        typeof 10 // 'Number' 
        typeof false // 'Boolean' 
        typeof Symbol() // 'Symbol' 
        typeof Function // ‘function' 
        `typeof null // ‘Object’ `
        typeof [] // 'Object' 
        typeof {
          
          } // 'Object'
        // -----------------------------------------instanceof
        function Foo() {
          
           }
        var f1 = new Foo();
        var d = new Number(1)
        console.log(f1 instanceof Foo);// true
        console.log(d instanceof Number); //true
        console.log(123 instanceof Number); //false   -->不能判断字面量的基本数据类型
        // -----------------------------------------constructor
        var d = new Number(1)
        var e = 1
        function fn() {
          
          
          console.log("ming");
        }
        var date = new Date();
        var arr = [1, 2, 3];
        var reg = /[hbc]at/gi;
        console.log(e.constructor);//ƒ Number() { [native code] }
        console.log(e.constructor.name);//Number
        console.log(fn.constructor.name) // Function 
        console.log(date.constructor.name)// Date 
        console.log(arr.constructor.name) // Array 
        console.log(reg.constructor.name) // RegExp
        //-----------------------------------------Object.prototype.toString.call()
        console.log(Object.prototype.toString.call(undefined)); // "[object Undefined]" 
        console.log(Object.prototype.toString.call(null)); // "[object Null]" 
        console.log(Object.prototype.toString.call(123)); // "[object Number]" 
        console.log(Object.prototype.toString.call("abc")); // "[object String]" 
        console.log(Object.prototype.toString.call(true)); // "[object Boolean]" 
        function fn() {
          
          
          console.log("ming");
        }
        var date = new Date();
        var arr = [1, 2, 3];
        var reg = /[hbc]at/gi;
        console.log(Object.prototype.toString.call(fn));// "[object Function]" 
        console.log(Object.prototype.toString.call(date));// "[object Date]" 
        console.log(Object.prototype.toString.call(arr)); // "[object Array]"
        console.log(Object.prototype.toString.call(reg));// "[object RegExp]"
    

instanceof原理(5星)

instanceof原理:判断对象是否存在于目标对象的原型链上,一层一层向上查找,一直找到null为止 扩展到原型链上
a instanceof b a为对象 b为目标对象
a是否是b的实例对象

   function Foo() {
    
     }
    var f1 = new Foo();
    console.log(f1 instanceof Foo);// true
   function myInstance(L, R) {
    
    //L代表instanceof左边,R代表右边
      var RP = R.prototype
      var LP = L.__proto__
      while (true) {
    
    
        if(LP == null) {
    
    
          return false
        }
        if(LP == RP) {
    
    
          return true
        }
        LP = LP.__proto__ 
      }
    }
    console.log(myInstance({
    
    },Object)); 

null和undefined的区别(4星)

null:

  1. 此处不应该有值
  2. typeof null = object
  3. 转为数值为0
  4. 作为函数的参数,表示该函数的参数不是对象;作为对象原型链的终点

undefined:

  1. 此处应该有一个值但是还没有定义
  2. typeof undefined = undefined
  3. 转为数值为NaN
  4. 例如变量被声明了但没有赋值,就等于undefined;函数没有返回值默认返回undefined;对象没有赋值的属性,该属性的值为undefined

=====有什么区别?(5星)

最大的区别在于,全等号不执行类型转换。
相等是经过类型转换数值相等 返回true
全等号由三个等号表示(===),只有在无需类型转换运算数就相等的情况下,才返回 true

非全等号由感叹号加两个等号(!==)表示,只有在无需类型转换运算数不相等的情况下,才返回 true。

==是非严格意义上的相等,

  • 先检查数据类型是否相同

  • 如果相同,则比较两个数是否相等

  • 如数据类型类型不同,则先转换成相同的数据类型,再进一步进行比较。

    • Null == Undefined ->true
    • String == Number ->先将String转为Number,在比较大小
    • Boolean == Number ->先将Boolean转为Number,在进行比较
    • Object == String,Number,Symbol -> Object 转化为原始类型
      ===是严格意义上的相等,会比较两边的数据类型和值大小
  • 先检查的数据类型是否相同

  • 若相同,则比较二者是否相等,若相等返回true

  • 若不同,直接返回false

1=='1'//ture
1==[1]//true
1==false//false
1==true//true

1==='1'//false
1===[1]//false

//特例
null==undefined //true
null===undefined //false
NaN==NaN //false
NaN===NaN //false
//NaN(not a number)是一个特殊的数值,这个数值用于表示一个本来要返回数值的操作数未返回数值的情况,避免报错。NaN与任何值都不相等,包括NaN自身。

手写call、apply、bind (5星)

call、apply、bind都是改变this指向的方法
call

fun. call (thisArg, argl, arg2, ..) 
thisArg:在fun函数运行时指定的this值arg1,arg2:传递的其他参数

call可以直接调用函数,可以改变函数内的this指向  
call的主要作用可以实现继承
 var o = {
    name: 'andy'
  }
  function fn(a, b) {
    console.log(this)//原本this指向window 当执行fn.call后指向o
    console.log(a + b)
  }
  fn.call(o, 1, 2)

bind
bind:语法和call一模一样,区别在bind不调用函数

fn.call(obj, 1, 2); // 改变fn中的this,并且把fn立即执行
fn.bind(obj, 1, 2); // 改变fn中的this,fn并不执行 等待事件触发

apply
和call基本上一致,唯一区别在于传参方式
apply把放到一个数组(或者类数组)中传递进去

fn.call(obj, 1, 2);
fn.apply(obj, [1, 2]);

因为我们需要让所有的地方都可以去访问到,所以我们把自定义的函数写在Function的原型上,因为所有的函数 instanceof Function都是会返回true的,所以定义在Function的prototype上call和bind我们接收第二个以后的参数,所以可能会用到slice方法,而apply直接接受第二个参数就行了。那么如何显式的绑定this呢,如果调用者函数,被某一个对象所拥有,那么该函数在调用时,内部的this指向该对象。拿取参数的话,我们还是用最简单的方式arguments,没有参数的情况下我们就直接将函数无参返回,如果没有传入对象的话直接指向window,这样的话我们的手写函数就和原来的call,apply和bind没什么两样了。

Function.prototype.MyCall = function(context){
//直接将函数放在Function的prototype上,方便调用
		const ctx = context || window;
//这里是将传入对象存到ctx里边,如果没有传入的话就指向window,因为原来的call也是这样的
		ctx.func = this;
//改变this的指向,换句话说相当于是把这个函数直接给到传入的对象
		const args = Array.from(arguments).slice(1);
//这里就是要去拿到函数的参数,把类数组转为数组之后,减去第一个,给到args备用
		const res = arguments.length > 1 ? ctx.func(...args) : ctx.func();
//就是说如果有参数,把args解构传进去,没有的话就不传了
		delete ctx.func;
//这个时候的func已经没有用了,直接返回res
		return res
//一步一注释应该是简单易懂的
	}

apply跟call是一样的,只是参数的处理简单一点,就不一步一注释了。

Function.prototype.MyApply = function(context){
	const ctx = context || window;
	ctx.func = this;
	const res = arguments[1] ? ctx.func(...arguments[1]):ctx.func();
	delete ctx.func;
	return res
}

bind有所不同,bind函数返回一个绑定函数,最终调用需要传入函数实参和绑定函数的实参。如果调用者函数,被某一个对象所拥有,那么该函数在调用时,内部的this指向该对象。

Function.prototype.MyBind = function(context){
	console cxt = JSOn.parse(JSON.stringify(context)) || window;
	cxt.func = this;
	const args = Array.from(arguments).slice(1);
	return function(){
		const allArgs = args.concat(Array.from(arguments));
		return allArgs.length > 0 ? cxt.func(...allArgs):cxt.func();
	}

字面量创建对象和new创建对象有什么区别?手写一个new ?new时内部发生了什么?(5星)

区别:

字面量:

var obj = {
    name: "小明",
    sayHi:function () {
        console.log("我是:" + this.name);
    },
   }
  • 字面量创建对象更简单,方便阅读
  • 不需要作用域解析,速度更快

new:创建对象的过程复杂,过程是这样的

  • 创建一个新对象
  • 使新对象的__proto__指向原函数的原型对象
  • 改变this指向,指向新的对象并执行该函数,执行结果保存起来作为result
  • 判断执行函数的结果是不是null或Undefined,如果是则返回之前的新对象,如果不是则返回之前的执行结果result

手写new

    // 手写一个new
       var obj = new fn()
    function myNew(fn, arg) {
    
    
      // 创建一个空对象
      let obj = {
    
    }
      // 使空对象的隐式原型指向原函数的显式原型
      obj.__proto__ = fn.prototype
      // this指向obj
      let result = fn.call(obj, arg)
      // 返回
      return result instanceof Object ? result : obj
    }
        console.log(null instanceof Object)//false
        console.log(undefined instanceof Object)//false

字面量和new出来的对象和 Object.create(null)创建出来的对象有什么区别 ?(3星)

  • 字面量和new创建出来的对象会继承Object的方法和属性,他们的隐式原型会指向Object的显式原型,
  • Object.create(null)创建出来的对象原型为null,作为原型链的顶端,自然也没有继承Object的方法和属性

原型链和原型

什么是原型?什么是原型链?如何理解?(5星)

原型: 原型分为隐式原型(__proto__属性)和显式原型(原型对象prototype),每个对象都有一个隐式原型,它指向自己的构造函数的显式原型。

原型链: 多个__proto__组成的集合成为原型链

实例对象的__proto__指向构造函数原型对象

构造函数原型对象的__proto__指向Object原型对象

Object的原型对象__proto__指向null

每一个构造函数都有原型对象prototype,把所有不变的的方法都直接定义在原型对象上,然后从构造函数中new出来的实例对象就可以共享这些方法
实例对象都会有__proto__属性,指向构造函数的原型对象prototype,之所以实例对象可以使用构造函数原型对象的属性和方法,就是因为对象有__proto__属性的存在。

构造函数.prototype === 实例对象.__proto__

原型对象中有一个constructor属性,指向原来的构造函数

示意图:

在这里插入图片描述

JavaScript的成员查找机制(3星)

1.当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。

2.如果没有就查找它的原型(也就是__proto__指向的构造函数的原型对象)

3.如果还没有就查找原型对象的原型(即Object的原型对象)

4.依此类推一直找到Object为止(Object的原型对象__proto__=>查找机制的终点为null).

5.__proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。

执行栈和执行上下文

什么是作用域,什么是作用域链? (4星)

  • 规定变量和函数的可使用范围称为作用域
  • 查找变量或者函数时,需要从局部作用域到全局作用域依次查找,这些作用域的集合称作用域链
  • 作用域链的作用是保证执行环境里有权访问的变量和函数是有序的,作用域链的变量只能向上访问,变量访问到window对象即被终止

什么是执行上下文,什么是执行栈?(4星)

执行上下文(也就是代码的执行环境)

  • 全局执行上下文

    创建一个全局的window对象,并规定this指向window,执行js的时候就压入栈底,关闭浏览器的时候才弹出

  • 函数执行上下文

    每次函数调用时,都会新创建一个函数执行上下文

  • eval执行上下文

    eval的功能是把对应的字符串解析成JS代码并运行

在这里插入图片描述
执行栈:具有先进后出结构,用于存储代码执行期间创建的所有执行上下文

  • 当进入一个执行环境,就会创建出它的执行上下文,然后进行压栈,当程序执行完成时,它的执行上下文就会被销毁,进行弹栈
  • 栈底永远是全局环境的执行上下文,栈顶永远是正在执行函数的执行上下文
  • 只有浏览器关闭的时候全局执行上下文才会弹出

闭包

什么是闭包?闭包的作用?闭包的应用? (5星)

内层作用域下可以获取外层作用域下的变量,而外层作用域下无法获取内层作用域下的变量
而闭包的出现,就解决了这个问题
内部函数引用外部函数的参数和局部变量,称为闭包,可以理解为“定义在一个函数内部的函数”
全局变量可以在任何地方获取,局部变量只能在函数内部获取

(函数内部可以直接读取全局变量,但是在函数外部无法读取函数内部的局部变量。这里有一个地方需要注意,函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明了一个全局变量!)

特性:
函数内再嵌套函数
内部函数可以引用外层的参数和变量
参数和变量不会被垃圾回收机制回收
作用
能够实现封装和缓存,避免全局变量的污染

缺点:
消耗内存、不正当使用会造成内存泄漏(有一块内存空间被长期占用,而不被释放)
解决方法是,在退出函数之前,将不使用的局部变量全部删除
应用

  • 防抖和节流

  • 封装私有变量

  • for循环中的保留i的操作

  • JS设计模式中的单例模式

    单例模式:用一个变量来标志当前是否已经为某个类创建过对象,如果是,则在下一次获取该类的实例时,直接返回之前创建的对象

  • 函数柯里化

基本步骤分析:

  1. 定义外部函数,要保护的变量以及可外部访问的内部函数。通过内部函数操作受保护变量,并将内部函数返回。
  2. 调用外部函数获取内部函数,然后基于内部函数操作受保护变量。
    function outer() {
    
    //外部函数
      var count = 0; //受保护变量(这个变量外部不可直接使用)
      return function () {
    
    //内部函数
        count++; //通过内部函数操作受保护变量
        console.log(count);
      };
    }//上面整个函数就是闭包设计
    var inner = outer(); //调用外部函数获取内部函数
    //匿名函数作为outer的返回值被赋值给了inner相当于inner=function () { count++;  console.log(count);} 
    //匿名函数被赋予全局变量,并且匿名函数内部引用着outer函数内的变量count,所以变量count无法被销毁

    inner(); //调用内部函数操作受保护变量

1.闭包作为返回值

在这里插入图片描述
在这段代码中,a()中的返回值是一个匿名函数,这个函数在a()作用域内部,所以它可以获取a()作用域下变量name的值,将这个值作为返回值赋给全局作用域下的变量b,实现了在全局变量下获取到局部变量中的变量的值

在这里插入图片描述
一般情况下,在函数fn执行完后,就应该连同它里面的变量一同被销毁,但是在这个例子中,匿名函数作为fn的返回值被赋值给了fn1,这时候相当于fn1=function(){var n = 0 … },并且匿名函数内部引用着fn里的变量num,所以变量num无法被销毁,而变量n是每次被调用时新创建的,所以每次fn1执行完后它就把属于自己的变量连同自己一起销毁,于是乎最后就剩下孤零零的num,于是这里就产生了内存消耗的问题
return function 只要函数遇见return就把后面的结果返回给函数的调用者

2.闭包作为参数
在这里插入图片描述
在这段代码中,函数fn1作为参数传入立即执行函数中,在执行到fn2(30)的时候,30作为参数传入fn1中,这时候if(x>num)中的num取的并不是立即执行函数中的num,而是取创建函数的作用域中的num这里函数创建的作用域是全局作用域下,所以num取的是全局作用域中的值15,即30>15,打印30

     var a = 1,
        b = 0;
      function A(a) {
    
    
        A = function (b) {
    
    
          alert(a + b++);
        };
        alert(a++);
      }
      A(1);//'1'
      A(2);//'4'

函数A中的A没有用var声明,所以属于全局变量,重写赋值函数A,又形成了闭包

闭包机制:闭包创建后,可以保存创建时的活动对象。
自加操作符:i++先执行再相加,++i先加在执行。如var a = 0; var b = 1; var c = a+b++,console.log(c,b) // 1 2

第一次调用函数A时,函数不调用自己不执行,所以 A = function (b) {alert(a + b++);}没有执行,执行到alert(a++)时,a++先执行后自加,所以a++为1,a为2,故A(1)=‘1’

第二次调用函数A时,A覆盖了函数A;

由于标识符A是在全局作用域定义的,所以在函数内部被重新赋值,在全局作用域也可以访问到重新赋值后的函数。 此时,也创建了一个闭包,该闭包保存了创建环境的活动对象。

执行到A(2),即b=2,alert(a + b++)=‘4’,A(2)输出’4’

继承

说一说 JS 中的常用的继承方式有哪些?以及各个继承方式的优缺点 (5星)

通过继承 ,子类不仅可以具有父类的方法和属性还可以加入新的属性和方法或者修改父类的属性和方法。
答:
原型链继承,就是让对象实例通过原型链的方式串联起来,当访问目标对象的某一属性时,能顺着原型链进行查找,从而达到类似继承的效果。
子的原型对象为new一个父的实例 Son.prototype = new Father(‘zs’);

function Father(name) {
  this.name = name;
}
Father.prototype.money = function () {
  console.log(1000);
};
function Son() {}
Son.prototype = new Father('zs');
var son = new Son('zs');
console.log(son.name);
son.money();

缺点:当某一个实例修改原型链上某一个属性时,如果实例类型是引用类型,那么其它实例的属性也会被修改。

借用构造函数实现继承父类型属性
原理:通过call()把父类型的this指向子类型的this,这样就可以实现子类型继承父类型的属性

 function Father(name) {
    this.name = name;
 }

 function Son(name) {
    Father.call(this,name);//此时父类的this指向子类的this 并调用函数
 }

var son= new Son('zs');
 console.log(son.name);//zs

缺点:不能继承父级原型链上的属性

组合继承

使用原型链继承共享的方法,通过借用构造函数继承实例属性
原理: 子类构造函数中使用Parent.call(this);的方式可以继承写在父类构造函数中this上绑定的各属性和方法;

  
function Child(name,age){
    
    
    // 继承属性
    Parent.call(this, name)
    this.age=age
}
// 继承方法
Child.prototype = new Parent()
Child.prototype.constructor = Child;

es6 extend继承

es6 class 实现继承使用关键字 extend 表明继承自哪个父类,并且在子类构造函数中必须调用 super 关键字,super关键字用于访问和调用对象父类上的函数。

    //  子类只要继承父类,可以不写constructor,一旦写了,则在constructor 中的第一句话必须是 super
    class Son3 extend Father {
    
    
      constructor(y) {
    
    
        super(200)  // super(200) => Father.call(this,200)
        this.y = y
      }
    }

内存泄露、垃圾回收机制

什么是内存泄漏? (5星)

答:

内存泄漏是指不再用的内存没有被及时释放出来,导致该段内存无法被使用

为什么会导致的内存泄漏? (5星)

答:
某段内存我们无法在通过js访问某个对象,而垃圾回收机制却认为该对象还在被引用,因此垃圾回收机制不会释放该对象,导致该块内存永远无法释放,积少成多,系统会越来越卡以至于崩溃

哪些操作会造成内存泄漏?(4星)

  • 未使用 var 声明的全局变量

  • 作用域未释放(闭包)

  • 定时器未清除

  • 事件监听为空白

    如何优化?

    1. 全局变量先声明在使用
    2. 避免过多使用闭包。
    3. 注意清除定时器和事件监听器。

垃圾回收机制都有哪些策略? (5星)

垃圾回收机制:当程序创建对象,数组等引用类型实体时,系统会在堆内存中为之分配一块内存区,对象就保存在内存区中,当内存不再被任何引用变量引用时,这块内存就变成了垃圾,等待垃圾回收机制去进行回收。

(了解)新生代的对象为存活时间较短的对象,老生代中的对象为存活时间较长或常驻内存的对象.V8采用了一种分代回收的策略,将内存分为两个生代:新生代和老生代

新生代算法:GC复制算法

将内存分为两个大小相同的空间 From 和 To, 利用 From 空间进行分配,当 From 空间满的时候,GC将其中的存活的对象复制到 To 空间,然后把From空间一次清理掉,之后将两个空间互换即完成GC(垃圾回收)

老生代算法:标记清除算法

垃圾回收器会在运行的时候给内存中的所有变量加上标记,然后去掉执行环境中的变量以及被执行环境中变量所引用的变量(闭包),在这些完成之后仍存在标记的就是要删除的变量了

答:

  • 标记清除法

    • 垃圾回收器会在运行的时候给内存中的所有变量加上标记,然后去掉执行环境中的变量以及被执行环境中变量所引用的变量(闭包),在这些完成之后仍存在标记的就是要删除的变量了
  • 引用计数法

    ​ 原理:跟踪记录每个值被引用的次数,当计数变为0的时候,说明无法访问该值了,垃圾回收机制清除该对象。

    • 当声明一个变量并给该变量赋值一个引用类型的值时候,该值的计数+1,当该值赋值给另一个变量的时候,该计数+1,当该值被其他值取代的时候,该计数-1,当计数变为0的时候,说明无法访问该值了,垃圾回收机制清除该对象

深拷贝和浅拷贝(5星)

浅拷贝只克隆了对象的第一层,常见对象一般有3-4层,如果该对象为多维对象,即二维及以上的对象。这时就需要使用深拷贝的方法。浅拷贝把对象的属性和属性值拷贝到另一个对象中

let obj = {
    
    
    a: 100,
    b:[10,20,30],
    c:{
    
    
        x:10
    },
    d: /^\d+$/
}

浅拷贝

浅拷贝因为对象只会被拷贝最外部的一层,至于更深层的对象,则依然是通过引用指向同一块堆内存,这就造成了如果对新对象进行修改,也会影响原来的对象

方法1:使用对象的解构

对象的扩展运算符(…)用于取出对象的所有可遍历属性,拷贝到当前对象中。

let obj2 = {
    
    ...obj}

方法2:使用循环

对象循环我们使用 for in 循环,但for in 循环会遍历到对象的继承属性,我们只需要它的私有属性,所以可以加一个判断方法:hasOwnProperty 保留对象私有属性hasOwnProperty() 方法只检测检测当前对象本身,只有当前对象本身存在该属性时才返回 true,

let obj2 = {
    
    }
for (let i in obj){
    
    
    if(!obj.hasOwnProperty(i)) break;  // 这里使用continue也可以
    obj2[i] = obj[i]
}

深拷贝

方法1:_.cloneDeep

lodash 提供了_.cloneDeep方法用于深拷贝

let deep = _.cloneDeep(obj)
console.log(deep === obj) //false

方法2:JSON.stringify,JSON.parse

let obj2 = JSON.parse(JSON.stringify(obj))

存在的问题: 遇到正则会变为空对象,函数为空,日期会变为字符串

方法3:万能方法

首先过滤特殊情况 =null 类型不属于Object 还有去掉正则 然后for in循环 利用hasOwnProperty获取对象的继承属性

function deepClone(obj){
    
    
    //过滤特殊情况
    if(obj === null) return null;
    if(typeOf obj !== "Object") return obj;
    if(obj instanceof RegExp){
    
    
        return new RegExp(obj)
    }
    //不直接创建空对象的目的是拷贝的结果和之前保持相同的所属类
    let newObj = new Obj.constructor 
    //也可以这么写:
    //let newObj = obj instanceof Array?[]:{}
    for (let i in obj){
    
    
        if(obj.hasOwnProperty(i)){
    
    
            newObj[i] = deepClone(obj[i])
        }
    }
    return newObj;
}
let obj2 = deepClone(obj)
console.log(obj.c === obj2.c) //fasle 表示指向的不同一内存

事件循环Event Loop

为什么JS是单线程的?(5星)

因为JS里面有可视的Dom,如果是多线程的话,这个线程正在删除DOM节点,另一个线程正在编辑Dom节点,导致浏览器不知道该听谁的

异步执行机制(5星)

JS的异步是通过回调函数实现的
一般而言,异步任务有以下三种类型
1、普通事件,如click、resize等
2、资源加载,如load、error等
3、定时器,包括setinterval、setTimeout等
异步任务相关回调函数添加到任务队列中(任务队列也称为消息队列)。

事件循环:

(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。

(2)主线程之外,还存在一个任务队列(task queue)。只要异步任务有了运行结果,就在’‘任务队列’'之中放置一个事件。

(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",异步任务态,进入执行栈,开始执行。主线程从"任务队列"中读取事件,这个过程是循环往复的,这种执行机制成称为事件循环.
在这里插入图片描述

栈:先进后出

队列:先进先出

链表:通过.next方式串联节点

具体执行顺序:

  1. 主线程运行的时候会生成堆(heap)和栈(stack);
  2. js从上到下解析方法,将其中的同步任务按照执行顺序排列到执行栈中;
  3. 当程序调用外部的API时,比如ajax、setTimeout等,会将此类异步任务挂起,继续执行执行栈中的任务,等异步任务返回结果后,再按照执行顺序排列到事件队列中;
  4. 主线程先将执行栈中的同步任务清空,然后检查事件队列中是否有任务,如果有,就将第一个事件对应的回调推到执行栈中执行,若在执行过程中遇到异步任务,则继续将这个异步任务排列到事件队列中。
  5. 主线程每次将执行栈清空后,就去事件队列中检查是否有任务,如果有,就每次取出一个推到执行栈中执行,这个过程是循环往复的.

宏任务和微任务(5星)

宏任务:setTimeout,setInterval,setImmediate,I/O,UI交互事件,事件绑定
setImmediate该方法用来把一些需要长时间运行的操作放在一个回调函数里,在浏览器完成后面的其他语句后,就立刻执行这个回调函数,

微任务:Promise.then,process.nextTick(node)

同一次事件循环中,微任务永远在宏任务之前执行。

任务执行顺序?(重要)

1.将同步任务排队到执行栈中,异步任务排队到任务队列中。

2.浏览器环境中执行方法时,先将执行栈中的同步任务清空,再将微任务推到执行栈中并清空,之后检查是否存在任务,若存在则取出一个宏任务,执行完成检查是否有微任务,以此循环…

3.Event Loop在浏览器与node环境中的区别:

  • ​ 浏览器环境每次执行一个宏任务,就去检查微任务
  • ​ node会清空当前所处阶段的队列,即执行所有task(宏任务),再去检查微任务,node11版本Event Loop运行原理发生了变化,与浏览器一致.

Promise async/await执行过程(3星)

Promise 和 async 中的立即执行:

Promise 中的异步体现在thencatch中,所以写在 Promise 中的代码是被当做同步任务立即执行的。

而在 async/await 中,在 await 出现之前,其中的代码也是立即执行的。 await+表达式执行完之后才能继续执行函数中其他代码

而await +表达式会先执行一遍,将 await +表达式 后面的代码加入到 微任务中,然后就会跳出整个 async 函数来执行后面的代码。所以 await +表达式后面的代码是微任务.

案例分析(5星)

案例一

  console.log(1);
    setTimeout(() => {
    
    
      console.log("setTimeout"); 
      let promise1 = new Promise((resolve) => {
    
    
          console.log(2); 
          resolve();
        })
        .then((data) => {
    
    
          console.log(100); 
        })
    }, 10);
    setTimeout(() => {
    
    
      console.log("setTimeout1"); 
      let promise1 = new Promise((resolve) => {
    
    
          console.log(3); 
          resolve();
        })
        .then((data) => {
    
    
          console.log(200); 
        })
    }, 0);//注意时间先后
    let promise = new Promise((resolve) => {
    
    
        console.log(4); 
        resolve();
      })
      .then((data) => {
    
    
        console.log(500);
      })
      .then((data) => {
    
    
        console.log(600);
      });
    console.log(7); 
    //1 4 7 500 600  setTimeout1 3 200 setTimeout 2 100

setTimeout(() => {
    
    
  console.log(1)
}, 0);
const p = new Promise((resolve, reject) => {
    
    
    console.log(2);
    setTimeout(() => {
    
    
      resolve()
      console.log(3)
    }, 0);
  })
  .then(() => {
    
    
    console.log(4)
  })
console.log(5)//2 5 1 3 4 遇见resolve()才执行.then

上面的 ,按照js由上到下的执行顺序,遇到同步任务先输出1。setTimeout是宏任务,会先放到宏任务队列中。而new Promise是立即执行的,所以会先输出4。而Promise.then是微任务,会依次排列到微任务队列中,继续向下执行输出7。现在执行栈中的任务已经清空,再将微任务队列清空,依次输出500和700。之后取出第一个宏任务,输出setTimeout1,每次执行一个宏任务,再去检查微任务,输出setTimeout1、3 、200。然后取出第二个宏任务输出setTimeout,再去检查微任务,输出setTimeout 、2 、100。

案例二

//node环境下
setImmediate(() => {
    
    
   console.log(1);
   process.nextTick(() => {
    
    
     console.log(4);
   });
 });
 process.nextTick(() => {
    
    
   console.log(2);
   setImmediate(() => {
    
    
     console.log(3);
   });
 });

js按照从上到下的执行顺序,将setImmediate排列到node事件环的I/O callbacks队列中,process.nextTick排列到微任务队列。首先检查执行栈,发现没有任务,然后检查微任务队列并执行清空,输出2,并将setImmediate排列到I/O callbacks队列,检查timers队列,发现并没有任务,继续检查I/O callbacks队列,发现有任务,执行清空,输出1,将process.nextTick排列到微任务队列,继续执行,输出3,最后将微任务清空,输出4。

案例三

//函数不调用 事件不执行
async function async1(){
    
    
  console.log("async1 start");
  await async2();
  console.log("async1 end");
}
async function async2() {
    
    
  console.log("async2");
}
console.log("script start"); 
setTimeout(function () {
    
    
  console.log("setTimeout");
}, 
async1();
new Promise(function (resolve) {
    
    
  console.log("promise1");
  resolve();
}).then(function () {
    
    
  console.log("promise2");
});
console.log("script end");
//script start/async1 start/async2/promise1/script end/async1 end/promise2/setTimeout
  1. 我们看到首先定义了两个 async 函数,接着往下看,然后遇到了 console 语句,直接输出 script start。输出之后,script 任务继续往下执行,遇到 setTimeout,其作为一个宏任务,则会先将其任务分发到对应的队列中

  2. script 任务继续往下执行,执行了 async1()函数, async 函数中在 await 之前的代码是立即执行的,所以会立即输出async1 start。 遇到了 await 时,会将 await 后面的表达式执行一遍,所以就紧接着输出async2,然后将 await 后面的代码也就是console.log('async1 end')加入到 微任务中的 Promise 队列中,接着跳出 async1 函数来执行后面的代码。

  3. script 任务继续往下执行,遇到 Promise 实例。由于 Promise 中的函数是立即执行的,而后续的 .then 则会被分发到 微任务的 Promise 队列中去。所以会先输出 promise1,然后执行 resolve,将 promise2 分配到对应队列。

  4. script 任务继续往下执行,最后只有一句输出了 script end,至此,全局任务就执行完毕了。
    在 script 任务执行完毕之后,开始查找清空微任务队列。此时,微任务中, Promise 队列有的两个任务async1 endpromise2,因此按先后顺序输出 async1 end,promise2

  5. 最后执行宏任务,只有一个 setTimeout,取出直接输出即可,至此整个流程结束。

在第一次 macrotask 执行完之后,也就是输出script end之后,会去清理所有 microtask。所以会相继输出promise2async1 endpromise4,其余不再多说。

案例四

async function a1 () {
    
    
    console.log('a1 start')//2
    await a2()
    console.log('a1 end')//微2 7
}
async function a2 () {
    
    
    console.log('a2')//3
}
console.log('script start')//1
setTimeout(() ={
    
    
    console.log('setTimeout')
}, 0)//宏1 10
Promise.resolve().then(() ={
    
    
    console.log('promise1')
})//微1 6
a1()
let promise2 = new Promise((resolve) ={
    
    
//将现有对象转为 Promise 对象
    resolve('promise2.then')//微3 8
    console.log('promise2')//4
})
promise2.then((res) ={
    
    
    console.log(res)
    Promise.resolve().then(() ={
    
    
        console.log('promise3')//微4 9
    })
})
console.log('script end')//5
//script start/a1 start/a2/promise2/script end/promise1/a1 end/promise2.then/promise3/setTimeout

案例五

  setTimeout(() => {
    
    
      console.log('1');//5
      new Promise(function (resolve, reject) {
    
    
        console.log('2');//6
        setTimeout(() => {
    
    
          console.log('3'); //9
        }, 0);
        resolve();
      }).then(function () {
    
    
        console.log('4')//7
      })
    }, 0);//宏1
    console.log('5'); //1
    setTimeout(() => {
    
    
      console.log('6');//8
    }, 0); //宏2
    new Promise(function (resolve, reject) {
    
    
      console.log('7');//2
      // reject();
      resolve();
    }).then(function () {
    
    
      console.log('8')//微1 4
    }).catch(function () {
    
    
    //catch在Promise状态为rejected时执行,then方法捕捉到Promise的状态为rejected,就执行catch方法里面的操作
      console.log('9')                                                       
    })
    console.log('10');//3
//5 7 10 8 1 2 4 6 3

案例六

 for (var i = 0; i < 5; i++) {
    
    
        setTimeout(function () {
    
    
          console.log(i++);//5 6 7 8 9
        }, 1000);
      }
      console.log(i);//5

在这里插入图片描述

先执行主线程上的任务,for循环和 console.log(i),其中每一次for循环添加一个宏任务进入任务队列中,执行结果先后顺序为 console.log(i);//5 i++=5,i++=6,i++=7,i++=8,i++=9 i++先运运算在相加

总结:任务执行,先整体过一遍,同步任务在主线程按顺序执行,异步任务中微任务永远在宏任务之前执行,理清它们进入任务队列的顺序,主线程中的微任务宏任务排在前面。

ES6

ES6新特性

变量和函数怎么进行提升的?优先级是怎么样的?(4星)

在ES6之前,JavaScript没有块级作用域(一对花括号{}即为一个块级作用域),只有全局作用域和函数作用域。
变量提升:变量的声明会被提升到当前作用域的最上面,变量的赋值不会提升

  • 对变量进行提升,只声明,不赋值,值为undefined

函数提升:函数的声明会被提升到当前作用域的最上面,但不会调用函数

  • 开辟堆空间

  • 存储内容

  • 将地址赋给变量

  • 函数提升优先级高于变量提升

 //变量提升:变量的声明会被提升到当前作用域的最上面,变量的赋值不会提升
    var fn = function () {
    
    
      console.log(1);
    }
    fn();
    //=> var fn;
    // fn();
    // fn=function(){
    
    
    //   console.log(1);
    // }
    //函数提升:函数的声明会被提升到当前作用域的最上面,但不会调用函数
    fn();

    function fn() {
    
    
      console.log(1);
    }
    //=> function fn() {
    
    
    //   console.log(1);
    // }
    // fn();

 //案例1
 function Foo() {
    
    
      getName = function () {
    
    
        console.log(1);
      }
      return this;
    }
    Foo.getName = function () {
    
    
      console.log(2);
    }
    Foo.prototype.getName = function () {
    
    
      console.log(3);
    }
    var getName = function () {
    
    
      console.log(4);
    }

    function getName() {
    
    
      console.log(5)
    }
    //变量提升
    //[1]function Foo() {
    
    
    //   getName = function () {
    
    
    //     console.log(1);
    //   }
    //   return this;//window
    // }
    //[2]var getName;
    //[3]function getName() {
    
    
    //   console.log(5)
    // }
    //[4]Foo.getName = function () {
    
    
    //   console.log(2);
    // }
    //[5]Foo.prototype.getName = function () {
    
    
    //   console.log(3);
    // }
    //[6]getName= function () {
    
    
    //   console.log(4);
    // }
//运算类型优先级 new(带参数列表)>new(无参数列表))
    Foo.getName(); //2 [4]Foo.getName()就是单纯的表示 输出函数 Foo的静态方法,所以直接输出2
    getName(); //4 [6] getName()因为提前声明的原因, 声明后被  var getNmae() = xxx 所覆盖  所以这里的输出 变成 4
    Foo().getName(); //1 先执行Foo(),里面的return this 是window,即Foo().getName()变成window.getName 在Foo里面的全局变量getName覆盖,所以输出为 1
    getName(); //1 同上 1
    new Foo.getName();//2 [4]new  18 Foo.getName() 19 ,先Foo.getName()在new,为2
    new Foo().getName(); //3 [5]new Foo() 19 Foo.getName() 19 优先级相同从左到右,先new Foo()在getName(),new Foo()创建实例,xxx.getName()原型上的方法 为3
    new new Foo().getName(); //3 [5]先new Foo().getName()  new(new Foo().getName)new Foo()创建实例,xxx.getName()原型上的方法 为3

var let const 有什么区别? (5星)

  • var 声明变量存在变量提升,let 和 const 不存在
  • var会与window相映射(会挂一个属性),而let不与window相映射
  • var 的作用范围是函数作用域,而let、const 的作用范围是块级作用域
  • 同一作用域下 var可以重复声明变量,而 let const 可以
  • 同一作用域下在 let 和 const 声明前使用会存在暂时性死区,即在let、const声明之前,变量不可以使用
  • const
    • const定义常量一旦声明必须赋值,声明后值(内存地址)不能再修改,const声明的常量指向的内存地址不能修改,但是可以改变内部的属性

箭头函数,以及与普通函数的区别,箭头函数可以当做构造函数 new吗?箭头函数的作用(5星)

箭头函数()=>{}:

  1. 如果形参只有一个,则小括号可以省略;
  2. 函数体如果只有一条语句,则花括号可以省略,函数的返回值为该条语句的执行结果;

与普通函数的区别

  1. 普通函数有函数提升,而箭头函数没有
  2. 箭头函数没有属于自己的thisarguments,箭头函数的this指向为父级(往外层作用域找):首先从它的父级作用域中找,如果父级作用域还是箭头函数,再往上找,直到找到this的指向。
  3. 箭头函数不能作为构造函数,也就是说,不可以使用new命令(没有this),否则会抛出一个错误。
  4. 没有yield 属性,不能用作生成器 Generator 函数

箭头函数不能new

  • 没有自己的this,不能调用call和apply
  • 没有prototype,new关键字内部需要把新对象的_proto_指向函数的prototype

箭头函数的作用:

1.语法简洁

2.可以隐式返回

//显示返回
const double3 = numbers.map(number => {
    
    
    return number * 2;  
})
//隐式返回
//去掉return和花括号括号,把返回内容移到一行,较为简洁;
const double3 = numbers.map(number => number * 2);

3.不绑定this

this实际上是继承自其父级作用域中的this,箭头函数本身的this是不存在的,这样就相当于箭头函数的this是在声明的时候就确定了,this的指向并不会随方法的调用而改变

Promise(什么是Promise?)

要会写Promise/all函数

Promise :ES6引入的异步编程的新解决方案,语法是一个构造函数 ,用来封装异步操作并可以获取其成功或失败的结果. Promise对象有三种状态:初始化pending 成功fulfilled 失败rejected

  • Promise 就是一个对象,用来表示并传递异步操作的最终结果
  • Promise {: PromiseResult} PromiseState:状态 PromiseResult:返回值
  • Promise 最主要的交互方式:将回调函数传入 then 方法来获得最终结果或出错原因
  • Promise 代码书写上的表现:以“链式调用”代替回调函数层层嵌套(回调地狱)p.then().then()

当我们在构造 Promise 的时候,构造函数内部的代码是立即执行的。Promise构造函数是主线程,而Promise.then是微任务. 调用resolve 这个promise对象的状态为成功,调用reject 这个promise对象的状态为失败

new Promise((resolve, reject) => {
    
    
  console.log('new Promise');
  //状态为成功
  resolve('success');
});
console.log('finifsh');

// 先打印new Promise, 再打印 finifsh

调用Promise对象的then方法,两个参数为函数,返回的是promise对象,对象状态由回调函数的执行结果决定 .

	//创建Promis对象
	const p = new Promise((resolve, reject) => {
    
    
        setTimeout(() => {
    
    
          resolve("成功");
        }, 1000);
      });
  
      //1.如果回调函数中的返回结果是 非promise对象,状态为成功,返回值为对象成功值fulfilled
      const result = p.then(
        (value) => {
    
    
          console.log(value); //成功
          return 1;
        },
        (reason) => {
    
    }
      );
      console.log(result); //Promise {<fulfilled>: 1}
-------------------------------------------------------------------------------------------
      //2.如果回调函数中的返回结果是promise对象,此promise对象决定上面promise对象p的状态
      const result = p.then(
        (value) => {
    
    
          console.log(value); //成功
          return new Promise((resolve, reject) => {
    
    
            resolve("ok"); 
            reject("err"); 
          });
        },
        (reason) => {
    
    }
      );
      console.log(result);// //Promise {<fulfilled>: 'ok}  Promise {<rejected>: 'err'} 
-------------------------------------------------------------------------------------------
      //3.抛出错误
      const result = p.then(
        (value) => {
    
    
          console.log(value); //成功
          throw "出错啦"; 
        },
        (reason) => {
    
    }
      );
      console.log(result); // Promise {<rejected>: '出错啦'} 

​ Promise 实现了链式调用,也就是说每次调用 then 之后返回的都是一个 Promise,并且是一个全新的 Promise,原因也是因为状态不可变。如果你在 then 中 使用了 return,那么 return 的值会被 Promise.resolve() 包装。 Promise.resolve()将对象转换为promise对象

Promise.resolve(1)
  .then((res) => {
    
    
    console.log(res); // => 1
    return 2; // 包装成 Promise.resolve(2)
  })
  .then((res) => {
    
    
    console.log(res); // => 2
  });

Promise 也很好地解决了回调地狱的问题.

回调地狱:`回调函数层层嵌套,代码可读性低、不方便后期维护

ajax(url)
  .then((res) => {
    
    
    console.log(res);
    return ajax(url1);
  })
  .then((res) => {
    
    
    console.log(res);
    return ajax(url2);
  })
  .then((res) => console.log(res));
//手写一个Promise
const promise = new Promise(function (resolve, reject) {
    
    
  if (true) {
    
    
    resolve(value)
  } else {
    
    
    reject(error)
  }
})
promise.then(res => {
    
    
  console.log(res)
}).catch(err => {
    
    
  console.log(err)
})

async和await(3星)

asyncawait两种语法结合可以让异步代码看起来像同步代码一样,简化异步函数的写法

async函数:

1.async函数的返回值为promise对象;

2.promise对象的结果由async函数执行的返回值决定

3.async 是Generator函数的语法糖,并对Generator函数进行了改进,是对 yield 的简单封装

    async function fn() {
    
    
      //1.如果返回结果是 非promise对象,状态为成功,返回值为对象成功值fulfilled
      return 123;
      //2.抛出错误
      throw "出错啦";
    }
    const result = fn();
    console.log(result); // Promise {<fulfilled>: 123} Promise {<rejected>: '出错啦'}
-----------------------------------------------------------------------------------------
    async function fn() {
    
    
      //3.如果返回的promise对象,那么返回的最终结果就是Promise对象
      return new Promise((reslove, reject) => {
    
    
        //resolve('成功');
        reject('失败');
      })
    }
    const result = fn();
    console.log(result); //Promise {<rejected>: '失败'}
    // then方法来获得最终结果或出错原因
    result.then(
      (value) => {
    
    
        console.log(value);
      },
      (reason) => {
    
    
        console.log(reason); //失败
      }
    )

await表达式:

1.await必须写在aysnc函数中;

2.await后面的表达式一般为Promise对象;

3.await返回的是Promise成功的值;

4.await的Promise失败了,就会抛出异常,无法处理promise返回的reject对象,需要通过try…catch捕获处理.

    //async函数 + await表达式:异步函数
    async function fn() {
    
    
      //await 返回的promise成功值
      let result = await new Promise((resolve, reject) => {
    
    
      resolve('成功')
    });
      console.log(result); //成功
    }
    fn();

一个await 的例子:

let a = 0;
let b = async () => {
    
    
  a = a + (await 10);
  console.log('2', a);
};
b();
a++;
console.log('1', a);

//先输出  ‘1’, 1
//在输出  ‘2’, 10
  • 首先函数 b 先执行,在执行到 await 10 之前变量 a 还是 0,因为 await 内部实现了 generator ,generator 会保留堆栈中东西,所以这时候 a = 0 被保存了下来
  • 因为 await 是异步操作,后来的表达式不返回 Promise 的话,就会包装成 Promise.reslove(返回值),然后会去执行函数外的同步代码
  • 同步代码 a++ 与打印 a 执行完毕后开始执行异步代码,将保存下来的值拿出来使用,这时候 a = 0 + 10

上述解释中提到了 await 内部实现了 generator,其实 await 就是 generator 加上 Promise 的语法糖,且内部实现了自动执行 generator

Generator是怎么样使用的以及各个阶段的变化如何?(3星)

答:

  • 首先生成器是一个函数,用来返回迭代器的
  • 调用生成器后不会立即执行,而是通过返回的迭代器来控制这个生成器的一步一步执行的
  • 通过调用迭代器的next方法来请求一个一个的值,返回的对象有两个属性,一个是value,也就是值;另一个是done,是个布尔类型,done为true说明生成器函数执行完毕,没有可返回的值了,
  • donetrue后继续调用迭代器的next方法,返回值的valueundefined

状态变化:

  • 每当执行到yield属性的时候,都会返回一个对象
  • 这时候生成器处于一个非阻塞的挂起状态
  • 调用迭代器的next方法的时候,生成器又从挂起状态改为执行状态,继续上一次的执行位置执行
  • 直到遇到下一次yield依次循环
  • 直到代码没有yield了,就会返回一个结果对象donetruevalueundefined

异步实现方式及各自方式的优缺点(4星)

方式一:回调函数(callback):将一个函数当做参数传到另一个函数里,当那个函数执行完后,再执行传进去的这个函数,这个过程就叫做回调

回调地狱:回调函数层层嵌套,层层依赖代码可读性低、不方便后期维护

优点:解决了同步的问题(只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。)

缺点:回调地狱,每个任务只能指定一个回调函数,不能 return.

方式二: Promise

Promise就是为了解决callback的问题而产生的。

Promise 实现了链式调用,也就是说每次 then 后返回的都是一个全新 Promise,如果我们在 then 中 return ,return 的结果会被 Promise.resolve() 包装

优点:解决了回调地狱的问题

缺点:无法取消 Promise ,内部抛出的错误需要通过回调函数来捕获

方式三:生成器Gnenrator

Generator是解决异步编程的一种方案,解决回调地狱

方式四:async/await

优点:代码清晰,不用像 Promise 写一大堆 then 链,处理了回调地狱的问题

缺点:await 将异步代码改造成同步代码,如果多个异步操作没有依赖性而使用 await 会导致性能上的降低。

解决异步回调地狱的方式(3星)

Promise、generator、asynac/await

forEach、for in、for of的区别(5星)

forEach遍历数组,但不能使用break、continue和return语句

for...in是用来循环带有字符串key的对象的方法。实际是为循环”enumerable“(可枚举)对象而设计的

for...of数组对象都可以遍历,遍历对象需要通过和Object.keys()

for in循环出的是key,for of循环出的是value

可枚举属性是指那些内部 “可枚举” 标志设置为 true 的属性。

对于通过直接的赋值和属性初始化的属性,该标识值默认为即为 true。

Js基本数据类型自带的原型属性不可枚举,通过Object.defineProperty0方法指定enumeralbe为false的属性不可枚举。

Set、Map、WeakSet 和 WeakMap 的区别?(3星)

Set集合:类似于数组,但成员的值都是唯一.数组去重

Map集合:类似于对象,也是键值对的集合,但’键’不限于字符串,各种类型的值(包括对象)也可以当做键.

应用场景Set用于数据重组,Map用于数据储存

Set

  1. 成员不能重复
  2. 只有键值没有键名,类似数组
  3. 可以遍历,方法有add, delete,has

Map:

  1. 本质上是健值对的集合,类似集合
  2. 可以遍历,可以跟各种数据格式转换

WeakSet 结构与 Set 类似,也是不重复的值的集合。但与 Set 有几个区别:

  • WeakSet 的成员只能是对象,而不能是其他类型的值
  • WeakSet 中的对象都是弱引用
    • 如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存
    • WeakSet 适合临时存放一组对象,以及存放跟对象绑定的信息。只要这些对象在外部消失,它在 WeakSet 里面的引用就会自动消失。

WeakMap适用于你要往对象上添加数据,又不想干扰垃圾回收机制

一个典型应用场景是,在网页的 DOM 元素上添加数据,就可以使用 WeakMap 结构。当该 DOM 元素被清除,其所对应的 WeakMap 记录就会自动被移除。

解构赋值(3星)

ES6中允许从数组中提取值,按照对应位置,对变量赋值。对象也可以实现解构
剩余参数语法允许我们将一个不定数量的参数表示为一个数组。

//...args ...表示接受剩余的参数
function sum(first, ...args) {
    
    
  console.log(first);//10
  console.log(args);//[20,30]
}
sum(10, 20, 30);

数组解构

let [a, b, c] = [1, 2, 3]   //a=1, b=2, c=3
let [d, [e], f] = [1, [2], 3]    //嵌套数组解构 d=1, e=2, f=3
let [g, ...h] = [1, 2, 3]   //数组拆分 g=1, h=[2, 3]
let [i,,j] = [1, 2, 3]   //不连续解构 i=1, j=3
let [k,l] = [1, 2, 3]   //不完全解构 k=1, l=2

对象解构

let {
    
    a, b} = {
    
    a: 'aaaa', b: 'bbbb'}      //a='aaaa' b='bbbb'
let obj = {
    
    d: 'aaaa', e: {
    
    f: 'bbbb'}}
let {
    
    d, e:{
    
    f}} = obj    //嵌套解构 d='aaaa' f='bbbb'
let g;
(g = {
    
    g: 'aaaa'})   //以声明变量解构 g='aaaa'
let [h, i, j, k] = 'nice'    //字符串解构 h='n' i='i' j='c' k='e'

事件代理/事件委托(3星)

指不在事件的触发目标上设置监听,而是在其父元素设置,通过冒泡机制,父元素可以监听到子元素上事件的触发。

为什么要使用模块化?

都有哪几种方式可以实现模块化,各有什么特点?(3星)

  • 模块化是指将一个大的程序文件,拆分成许多小的文件,然后将小文件组合起来.

    一个模块是实现一个特定功能的一组方法

    优点:1. 防止命名冲突;2. 代码复用;3. 高维护性;

    实现模块化方式

    • AMD
    • CMD
    • CommonJS模块
    • ES6 模块

exportsmodule.exports有什么区别?(3星)

  • 导出方式不一样
    • exports.xxx='xxx'
    • module.export = {}
  • exportsmodule.exports的引用,两个指向的是用一个地址,而require能看到的只有module.exports

模块加载方案比较:CommonJS模块 与 ES6模块(3星)

CommonJS模块,它通过 require 来引入模块,通过 module.exports 定义模块的输出接口。这种模块加载方案是服务器端的解决方案,它是以同步的方式来引入模块的,因为在服务端文件都存储在本地磁盘,所以读取非常快,所以以同步的方式加载没有问题。

ES6模块使用 import 和 export 的形式来导入导出模块。

区别:

  • CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。CommonJS 模块输出的是值的拷贝,模块内部的变化就影响不到这个值。ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。

  • CommonJS 模块是运行时加载,ES6 模块是编译时输出接口 对外接口只是一种静态定义,在代码静态解析阶段就会生成。CommonJS 模块就是对象,即在输入时是先加载整个模块,生成一个对象,然后再从这个对象上面读取方法,这种加载称为“运行时加载”。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。

扩展运算符

扩展运算符可以将数组或者对象转为用逗号分隔的参数序列。

let ary = [1, 2, 3];
...ary //1,2,3
console.log(...ary);//1 2 3

扩展运算符可以应用于合并数组。

let ary1 = [1, 2, 3];
let ary2 = [4, 5, 6];
ary1.push(...ary2);
console.log(ary1);//[ 1, 2, 3, 4, 5, 6 ]

跨域

同源策略

如果两个页面(接口)的协议、域名、端口号都相同,我们认为它们为同源
同源策略就是浏览器的一个安全限制,它阻止了不同【域】之间进行的数据交互

跨域的方式都有哪些?他们的特点是什么? (5星)

跨域是指一个域下的文档或脚本试图去请求另一个域下的资源
同源策略是 浏览器 为了安全而作出的限制策略(所以服务端不涉及到跨域),同源政策是浏览器给予Ajax技术的限制,无法向非同源地址发送Ajax 请求,服务器端是不存在同源政策限制。 浏览器请求必须遵循同源策略,即同域名、同端口、同协议

当协议、子域名、主域名、端口号中任意一个不相同时,都算作不同域。跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。

JSONP

  • JSONP原理:就是通过script标签获取后端接口发送过来的数据通过js实现,但是jsonp只能解决get请求,这也是它的一个缺点。而且也不安全 json劫持:攻击者可以构造恶意的JSONP调用页面,诱导被攻击者访问来截取用户敏感信息

  • JSONP只能get请求

  • 源码:

        function addScriptTag(src) {
          
          
          var script = document.createElement("script")
          script.setAttribute('type','text/javascript')
          script.src = src
          document.appendChild(script)
        }
    
        // 回调函数
        function endFn(res) {
          
          
          console.log(res.message);
        }
        // 前后端商量好,后端如果传数据的话,返回`endFn({messag:'hello'})`
    

CORS

CORS(跨域资源共享)原理:它允许浏览器向跨源服务器,发出请求,从而克服了AJAX只能同源使用的限制。实现原理:在服务器返回响应的时候给响应头设置一个允许跨域的header,把Access-Control-Allow-Origin指定为可获取数据的域名
CORS请求分为两类:简单请求 非简单请求。
简单请求,浏览器在请求头中增加一个Origin字段直接发出CORS请求
非简单请求,会在正式通信之前,增加一次HTTP查询请求,浏览器先询问服务器,只有得到服务器的肯定答复时,浏览器才会发出正式请求,否则就报错。

浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。
nginx反向代理跨域
把 客户端和nginx反向代理服务器放在同一端口上,这样就不会存在同源限制,而 nginx模拟一个虚拟服务器,服务器与服务器之间是不存在跨域的,就能把客户端http请求转发到另一个或者一些服务器上,从而轻松实现跨域访问

  • 发送数据时 ,客户端->nginx->服务端
  • 返回数据时,服务端->nginx->客户端
    正向代理 是一个位于客户端和目标服务器之间的代理服务器,客户端为了从目标服务器取得内容,向代理服务器发送一个请求,然后代理服务器转交请求给目标服务器并将获得的内容返回给客户端
    对于客户端来说,反向代理就好像目标服务器。客户端向反向代理发送请求,接着反向代理判断请求走向何处,并将请求内容转交给客户端,使得这些内容就好似他自己一样,客户端并不会感知到反向代理后面的服务,只需要把反向代理服务器当成真正的服务器就好了。

我们常说的代理也就是指正向代理,正向代理的过程,它隐藏了真实的请求客户端,服务端不知道真实的客户端是谁,客户端请求的服务都被代理服务器代替来请求。
反向代理隐藏了真实的服务端,当我们请求一个网站的时候,背后可能有成千上万台服务器为我们服务,但具体是哪一台,我们不知道,也不需要知道,我们只需要知道反向代理服务器是谁就好了,反向代理服务器会帮我们把请求转发到真实的服务器那里去。反向代理器一般用来实现负载平衡(尽量别说)。

DOM

DOM绑定事件的几种方式及区别(3星)

  • element.onclick,元素+事件类型

对同一个元素绑定多次同类事件,写在后面的会覆盖前边的事件

  • addEventListener方法
    btn.addEventListener(type, handle, true)三个参数分别为事件类型,事件处理程序,执行的阶段;
    第三个参数有两个值,true和false,false默认值,事件冒泡阶段执行,true事件捕获阶段执行

对同一个元素绑定多次同类事件,不会发生覆盖,会顺序执行所绑定的事件

DOM事件流(5星)

在这里插入图片描述

getElementById querySelector querySelectorAll获取元素

两种事件流都会触发 DOM 的所有对象,从 window 对象开始,也在 window 对象结束。

DOM 标准规定事件流包括三个阶段:

  • 事件捕获阶段
  • 处于目标阶段
  • 事件冒泡阶段

捕获和冒泡阶段事件流都会触发 DOM 的所有对象,从 window 对象开始,也在 window 对象结束。

事件捕获和事件冒泡有什么区别?(5星)

  • 事件捕获

    • 从上至下(祖宗到儿子)执行
    • 在addEventListener中的第三属性设置为true
  • 事件冒泡 事件代理/事件委托

    • 从下至上(儿子至祖宗)执行
    • 在addEventListener中的第三属性设置为false(默认)

杂项

this 的指向有哪几种情况?(5星)

this 代表函数调用相关联的对象,通常也称之为执行上下文。

  1. 作为函数直接调用,非严格模式下,this 指向 window,严格模式下,this 指向 undefined;
  2. 作为某对象的方法调用,this 通常指向调用的对象。
  3. apply、call、bind 可以绑定 this 的指向。
  4. 在构造函数中,this 指向新创建的对象
  5. 箭头函数没有单独的 this 值,this 在箭头函数创建时确定,箭头函数的this指向为父级:首先从它的父级作用域中找,如果父级作用域还是箭头函数,再往上找,直到找到this的指向。

什么是防抖?什么是节流?手写一个(5星)

防抖节流都是控制事件触发频率的方法

  • 防抖: 某事件n秒后执行,若在n秒内被重复触发,则重新计时,
    文本编辑器实时保存,当无任何更改操作一秒后进行保存
    频繁操作点赞和取消点赞,因此需要获取最后一次操作结果并发送给服务器
    登录、发短信等按钮避免用户点击太快,以致于发送了多次请求,需要防抖

  • 节流:某事件n秒内只运行一次,若在n秒内重复触发,只有一次生效
    鼠标不断点击触发,单位时间内只触发一次(单位时间内点击很多次,但只有一次生效)
    搜索关键字的联想:每个一段时间触发,也可以做成防抖单位时间内最后一次执行,仅在最后触发一次

  //防抖函数 重在清零 clearTimeout(timer)
  //定时器期间,有新操作时,清空旧定时器,重设新定时器
function debounce(fn, time)  {
    
    
//先声明一个计时器
	let timer
	return function() {
    
    
	//如果计时器存在则清空
		if(timer) clearTimeout(timer)
		//否则重设新定时器
		timer = setTimeout(() => {
    
    
			fn.call(this)
		}, time)
	}
}



  // 节流  重在开关锁 timer=null 利用定时器来保证间隔时间内事件的触发次数
//传入两个值,一个是要执行的方法,一个是执行周期
function throttle(fn,time) {
    
    
	//先声明一个定时器
	let timer
	//返回一个方法(闭包)
	return function() {
    
    
		//如果定时器存在则不执行
		if(timer) return
		//如果定时器不存在则执行
		//设置一个一定时间自身清理的计时器
		timer = setTimeout(() => {
    
    
			//此this到时候会变成调用这个方法的元素
		    fn.call(this)
			timer = null
		},time)
	}
}

函数柯里化原理(5星)

//函数柯里化,就是可以将一个接受多个参数的函数分解成多个接收单个参数的函数的技术,直到接收的参数满足了原来所需的数量后,才执行原函数
//简单为用闭包把参数保存起来,当参数的数量足够执行函数了,就开始执行函数
//降低代码的重复,提高代码的适用性
const curry = (fn, ...args) => fn.length <= args.length // 判断参数的数量足够执行函数
  	? fn(...args) // 参数足够执行函数
    : curry.bind(null, fn, ...args) // 参数不够时返回传入已有参数,使用bind结合闭包思想返回一个临时函数
        //fn(this,1,2) 改变fn中的this,fn并不执行 等待事件触发
// 检验
function sum (a, b, c) {
    
     return a + b + c }
let currySum = curry(sum)

console.log( currySum(1, 2, 3) ) // 6//以 currySum(1, 2, 3)为例,可以用一个变量保存curry(1, 2),然后每次只传入第三个参数,相当于前两个参数给定了

js常见的设计模式(5星)

设计模式是一套被反复使用,多数人知晓的,经过分类的,代码设计经验的总结。

  • 单例模式、工厂模式、代理模式
    单例模式
    概念:同一个构造函数,生成唯一的实例,防止重复的实例化
    优点:节省内存空间,提高程序执行效率

1.只有一个实例。2.可以全局访问。3.节省内存

var Single = (function () {
    
    
      var instance = null
      function Single(name) {
    
    
        this.name = name
      }
      return function (name) {
    
    
        if (!instance) {
    
    
          instance = new Single()
        }
        return instance
      }
    })()

    var oA = new Single('hi')
    var oB = new Single('hello')
    console.log(oA);
    console.log(oB);
    console.log(oB === oA);

工厂模式

代替new创建一个对象,且这个对象像工厂制作一样,批量制作属性相同的实例对象(指向不同)

 function Animal(o) {
    
    
      var instance = new Object()
      instance.name = o.name
      instance.age = o.age
      instance.getAnimal = function () {
    
    
        return "name:" + instance.name + " age:" + instance.age
      }
      return instance
    }

    var cat = Animal({
    
    name:"cat", age:3})
    console.log(cat);

代理模式
在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在两个对象之间起到中介的作用。

JS性能优化的方式(5星)

  • 垃圾回收
  • 闭包中的对象清除
  • 防抖节流
  • 分批加载(setInterval,加载10000个节点)
  • 事件委托
  • script标签中的defer和async
  • CDN

图片的预加载和懒加载

  • 预加载:提前加载图片,当用户需要查看时可直接从本地缓存中渲染
  • 懒加载:延时加载,即当对象需要用到的时候再去加载
    懒加载的主要目的是作为服务器前端的优化,减少请求数
懒加载原理通过img中的src属性,根据可视化区域对数据进行切割

两种技术的本质:两者的行为是相反的,一个是提前加载,一个是迟缓甚至不加载。预加载则会增加服务器前端压力,懒加载对服务器有一定的缓解压力作用。

cookie、session、token、jwt的区别

回答:
Cookie 由于HTTP协议是无状态的,而服务器端的业务必须是要有状态的。Cookie的产生是为了存储web中的状态信息,以方便服务器端使用。 cookie 存储在客户端,cookie 是不可跨域的

session 是基于 cookie 实现的 是另一种记录服务器和客户端会话状态的机制

token是 访问资源接口(API)时所需要的资源凭证
Token 和 cookie、session的区别

  • cookie 在客户端中存放,容易伪造,不如 session 安全
  • session 会消耗大量服务器资源,cookie 在每次 HTTP 请求中都会带上,影响网络性能
  • 由于 session 存储于服务器端,当用户量很大时会有一定的扩展性问题。token存储在客户端,不存在这个问题
  • Cookie是不允许跨域访问的, token可跨域
  • 请求中发送token能够防止CSRF(跨站请求伪造),cookie不可

认证(鉴权)是指验证用户是否拥有访问系统的权利,就是验证当前用户的身份,证明“你是你自己”,如用户名密码登录,当你的信息和数据库匹配时就会登录成功

授权是用户授予第三方应用访问该用户某些资源的权限

凭证是实现认证和授权的前提,需要一种媒介(证书)来标记访问者的身份

JWT

  • JSON Web Token(简称 JWT)是目前最流行的跨域认证解决方案。是一种认证授权机制

  • JWT 的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源。比如用在用户登录上。

  • JWT由三部分组成:第一部分我们称它为头部(header),第二部分我们称其为载荷(payload, 类似于飞机上承载的物品),第三部分是签证(signature).

    jwt 有三部分组成:A.B.C

    A:Header,{“type”:“JWT”,“alg”:“HS256”} 固定

    B:playload,存放信息,比如,用户id,过期时间等等,可以被解密,不能存放敏感信息

    C: 签证,A和B加上秘钥 加密而成,只要秘钥不丢失,可以认为是安全的。

    jwt 验证,主要就是验证C部分 是否合法

    使用: 在HTTP 请求头信息的 Authorization 字段里,使用 Bearer 模式添加 JWT,授权携带token信息

    • 服务端的保护路由将会检查请求头 Authorization 中的 JWT 信息,如果合法,则允许用户的行为
    • 因为 JWT 是自包含的(内部包含了一些会话信息),因此减少了需要查询数据库的需要
    • 因为 JWT 并不使用 Cookie 的,所以你可以使用任何域名提供你的 API 服务而不需要担心跨域资源共享问题(CORS)
    • 因为用户的状态不再存储在服务端的内存中,所以这是一种无状态的认证机制

cookie、session的区别

  • cookie 储存在在客户端,session 储存在于服务端。 session 是基于 cookie 实现的,session 存储在服务器端
  • cookie 在客户端中存放,容易伪造,不如 session 安全
  • session 会消耗大量服务器资源,cookie 在每次 HTTP 请求中都会带上,影响网络性能

Token 和 JWT 的区别

相同:

  • 都是访问资源的令牌
  • 都可以记录用户的信息
  • 都是使服务端无状态化
  • 都是只有验证成功后,客户端才能访问服务端上受保护的资源

区别:

  • Token:服务端验证客户端发送过来的 Token 时,还需要查询数据库获取用户信息,然后验证 Token 是否有效。
  • JWT:将 Token 和 Payload 加密后存储于客户端,服务端只需要使用密钥解密进行校验(校验也是 JWT 自己实现的)即可,不需要查询或者减少查询数据库,因为 JWT 自包含了用户信息和加密的数据。

cookie,sessionStorage 和 localStorage 的区别?

  • 在同一浏览器下生命周期不同

Cookie生命周期: 默认是关闭浏览器后失效, 但是也可以设置过期时间

SessionStorage生命周期: 仅在当前会话(窗口)下有效,关闭窗口或浏览器后被清除, 不能设置过期时间

LocalStorage生命周期: 除非被清除,否则永久保存

  • 容量不同

Cookie容量限制: 大小(4KB左右)和个数(20~50)

SessionStorage和LocalStorage容量限制: 大小(5M左右)

  • 网络请求不同

Cookie网络请求: 每次都会携带在HTTP请求头中,如果使用cookie保存过多数据会带来性能问题

SessionStorage和LocalStorage网络请求: 仅在浏览器中保存,不参与和服务器的通信

ajax

什么是ajax?创建过程

ajax是一种异步通信的方法,从服务端获取数据,达到局部刷新页面的效果。Ajax 相当于客户端发送请求与接收响应的代理人,以实现在不影响用户浏览页面的情况下,局部更新页面数据,从而提高用户体验。
过程:
创建XMLHttpRequest对象;
调用open方法传入三个参数 请求方式(GET/POST)、url、同步异步(true/false);
监听onreadystatechange事件,当readystate等于4时返回responseText;
调用send方法传递参数。

同步和异步的区别

同步:
浏览器访问服务器请求,用户看得到页面刷新,重新发请求,等请求完,页面刷新,新内容出现,用户看到新内容,进行下一步操作

异步:
浏览器访问服务器请求,用户正常操作,浏览器后端进行请求。等请求完,页面不刷新,新内容也会出现,用户看到新内容

ajax和axios的区别(5星)

ajax技术实现了网页的局部数据刷新,是客户端发送请求与接收响应的代理人,axios是通过promise实现对ajax技术的一种封装,安装axios依赖用于发起ajax请求

ajax:本身是针对MVC的编程,不符合现在前端MVVM的浪潮
axios:从 node.js 创建 http 请求 支持 Promise API 客户端支持防止CSRF 提供了一些并发请求的接口

ajax的优点和缺点

ajax的优点

1、无刷新更新数据(在不刷新整个页面的情况下维持与服务器通信)

2、异步与服务器通信(使用异步的方式与服务器通信,不打断用户的操作)

3、前端和后端负载均衡(将一些后端的工作交给前端,减少服务器与宽度的负担)

4、界面和应用相分离(ajax将界面和应用分离也就是数据与呈现相分离)

ajax的缺点

1、ajax不支持浏览器back按钮

2、安全问题 Aajax暴露了与服务器交互的细节

3、对搜索引擎的支持比较弱

4、破坏了Back与History后退按钮的正常行为等浏览器机制

js内置对象 String Array Math Date常见方法

String 字符串常见方法:

 str.length:字符串的长度
 str.charAt(索引),返回值是指定索引位置的字符串,超出索引,结果是空字符串
 str.concat(字符串1,字符串2,...);返回的是拼接之后的新的字符串
 str.indexOf(要找的字符串,从某个位置开始的索引);返回的是这个字符串的索引值,没找到则返回-1
 str.lastIndexOf(要找的字符串);从后向前找,但是索引仍然是从左向右的方式,找不到则返回-1
 str.replace(原来的字符串,新的字符串):用来替换字符串的
 str.slice(开始的索引,结束的索引); 提取字符串的片断,并在新的字符串中返回被提取的部分,从索引5的位置开始提取,到索引为10的前一个结束,没有10,并返回这个提取后的字符串
 str.split()把字符串分割为字符串数组
 "hello".split("")	//可返回 ["h", "e", "l", "l", "o"]
 str.substr(开始的位置,个数);返回的是截取后的新的字符串
 str.substring(开始的索引,结束的索引),返回截取后的字符串,不包含结束的索引的字符串
 str.toLocaleLowerCase();转小写
 str.toLowerCase();转小写
 str.toLocaleUpperCase()转大写
 str.toUpperCase();转大写
 str.trim();干掉字符串两端的空格
 str.fromCharCode(数字值,可以是多个参数),返回的是ASCII码对应的值

Array数组常见方法:

Array.isArray/instanceof/typeof/Object.prototype.toString.call:判断是否为数组    
map(函数): 遍历数组,返回回调返回值组成的新数组
forEach(函数): 遍历数组相当于for循环,无法break,可以用try/catch中throw new Error来停止
filter(函数): 过滤,返回的是数组中符合条件的元素,组成了一个新的数组
some(函数): 返回布尔类型,有一项符合条件返回true,则整体为true
every(函数): 返回布尔类型,有一项返回false,则整体为false
join(字符串): 通过指定连接符生成字符串
push / pop: 末尾推入和弹出,改变原数组, 返回推入之后新数组的长度和弹出的这个值
unshift / shift: 头部推入和弹出,改变原数组,返回推入之后新数组的长度和弹出的这个值
sort/ reverse: 排序(可能不稳定,请写MDN中的那个固定的代码)与反转,改变原数组
concat: 连接数组,不影响原数组, 浅拷贝
slice(start开始的索引, end结束的索引): 返回截断后的新数组,不改变原数组
splice(start开始的位置, number删除的个数, 替换的值): 一般用于删除数组中的元素,返回删除元素组成的数组,改变原数组
indexOf / lastIndexOf(要找的值value, 从某个位置开始的索引fromIndex): 从前往后/从后往前查找数组项,返回的是索引,没有则是-1
reduce(function(prev,cur,index,arr){
    
    ...}, init);
reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。
prev 必需。累计器累计回调的返回值; 表示上一次调用回调时的返回值,或者初始值 init;
cur 必需。表示当前正在处理的数组元素;
index 可选。表示当前正在处理的数组元素的索引,若提供 init 值,则起始索引为- 0,否则起始索引为1;
arr 可选。表示原数组;
init 可选。表示初始值。

Math常见方法:

Math.ceil(x)---向上取整 
Math.floor(x)---向下取整 
Math.Pi----圆周率的值 Math.Max(num1,num2,...)---一组数字中的最大值 
Math.Min(num1,num2,...)---一组数字中的最小值  
Math.abs(x)----绝对值 
Math.random()---随机数字(范围0~1)  
Math.sqrt(x)----开平方 
Math.pow(x,y)----一个数字的多少次幂   x^y

Date常见方法:

 var dt=new Date(); console.log(dt);   当前的时间---当前的服务器 
 var dt=new Date("2019-01-11");  //传入时间   
 dt.getFullYear();//年  
 dt.getMonth();//月---从0开始   
 dt.getDate();//日    
 dt.getHours();//小时  
 dt.getMinutes();//分钟   
 dt.getSeconds();//秒   
 dt.getDay();//星期---从  0  星期日  开始  
 dt.toDateString();//日期    
 dt.toLocaleDateString();//日期  
 dt.toTimeString();//时间   
 dt.toLocaleTimeString();//时间  
 dt.valueOf();//毫秒

杂项

在js中,字符串和数字相加,会把数字转成字符串再相加,‘5’+3=“53”
字符串和数字相减,会把字符串转成数字再相减,‘5’-3=2
常见的数据结构有数组,队列,堆,栈,树,图,散列表,链表等

讲一下token实现单点登陆的过程,思路怎么样(先将token存储在localstorage中,在利用跨域解决子域名共享token)

vuex作用,vuex和localstorage区别,本质区别,驱动不一样( 一个是内存存储,一个是浏览器永久存储, vuex应用于组件之间的共享数据,local更多用于页面之间传递

猜你喜欢

转载自blog.csdn.net/Better_Xing/article/details/115392086