js面试题整理

1.js有几种数据类型,其中基本数据类型有哪些?

五种基本类型: Undefined、Null、Boolean、Number和String。
一种引用数据类型:object
在引用数据类型 object 中包括function/array/object
ES6新增:Symbol,主要用于创建一个独一无二的标识。
ES10新增:Bigint,解决js中精度问题

2.undefined 和 null 区别?

null 表示一个对象被定义了, 值为"空值",使用typeof运算得到 “object”;

undefined 表示不存在这个值,当一个声明了一个变量未初始化时,得到的就是undefined。没有返回值的函数返回为undefined,没有实参的形参也是undefined。

null 和 undefined 都表示“值的空缺”,可以认为undefined是表示系统级的、出乎意料的或类似错误的值的空缺,而null是表示程序级的、正常的或在意料之中的值的空缺。

3.数组去重?

1、简单方法

var arr = ['abc','abcd','sss','2','d','t','2','ss','f','22','d'];
//定义一个新的数组
var s = [];
//遍历数组
for(var i = 0;i<arr.length;i++){
    if(s.indexOf(arr[i]) == -1){  //判断在s数组中是否存在,不存在则push到s数组中
        s.push(arr[i]);
    }
}
console.log(s);
//输出结果:["abc", "abcd", "sss", "2", "d", "t", "ss", "f", "22"]

2、Set方法

let newArr1 = new Set(arr)
console.log([...newArr1]);

3、基于对象去重

let arr3 = [1, 2, 1, 1, 1, 2, 3, 3, 3, 2]
    let obj = {}
    for (let i = 0; i < arr3.length; i++) {

      let item = arr3[i]
      if (obj[item]) {
        //把数组最后一个元素取出并删除
        arr3[i] = arr3[arr3.length - 1]
        arr3.length--;
        i--;
        continue;
      }
      obj[item] = item

    }
    console.log(arr3);
    console.log(obj);

2021.07.08

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

区别

字面量:

  • 面量创建对象更简单,方便阅读
  • 不需要作用域解析,速度更快

new:

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

手写new

function myNew(fn,...arg){
	//在内存中创建一个新对象
	let obj = {};
	//使新对象的__proto__指向原函数的原型对象
	obj.__proto__ = fn.prototype;
	//改变this指向(指向新的obj)并执行该函数,执行结果保存起来作为result
	let result = fn.call(obj,args)
	//判断执行函数的结果是不是null或Undefined,如果是则返回之前的新对象,如果不是则返回result
	return result instanceof Object ? result : obj
}

5.什么是原型?什么是原型链?如何理解?

原型
一个对象,我们也称为prototype为原型对象,作用是共享方法。
原型分为隐式原型和显式原型,每个对象都有一个隐式原型,它指向自己的构造函数的显式原型。
构造函数通过原型分配的函数是所有对象共享的,javascript规定,每个构造函数都有一个prototype属性,指向另一个对象。这个prototype就是一个对象,我们可以把不变的方法直接定义在prototype对象身上,这样所有的对象实例都可以共享这些方法。

原型链

  • 多个__proto__组成的集合成为原型链
  • 实例对象的__proto__指向构造函数的原型对象
  • 构造函数的原型对象的__proto__指向Object的原型对象
  • Object的原型对象的__proto__指向null
    在这里插入图片描述

6.JavaScript的成员查找机制?

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

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

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

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

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

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

  1. 字面量和new创建出来的对象会继承Object的方法和属性,他们的隐式原型会指向Object的显式原型
  2. Object.create(null)创建出来的对象原型为null,作为原型链的顶端,自然也没有继承Object的方法和属性
  3. new关键字创建的对象会保留原构造函数的属性,而用Object.create()创建的对象不会。

8.JS 中的常用的继承方式有哪些?以及各个继承方式的优缺点 ?

原型链继承、构造函数继承、组合继承、
原型式继承、寄生式继承、寄生组合继承、ES6的extend

原型链继承

  • 优点:利用原型让一个引用类型继承另一个引用类型的属性和方法
  • 缺点:1、无法传参 2、包含引用类型的原型属性会被所有实例属性共享,容易造成属性的修改混乱
    在这里插入图片描述

构造函数继承

在子类型的构造函数中调用超类型构造函数

  • 优点:可以在子类型构造函数中父类构造函数添加参数
  • 缺点:无法继承protype上的属性,无法复用。
function SuperType(){
    this.colors = ["red", "blue", "green"];
}

function SubType(){
    //继承了 SuperType

    SuperType.call(this);
}

var instance1 = new SubType();
instance1.colors.push("black");
console.log(instance1.colors);  //"red,blue,green,black"

var instance2 = new SubType();
console.log(instance2.colors);  //"red,blue,green"

组合继承

组合继承指的是将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。

方法:在子函数中运行父函数,但是要利用call把this改变一下,再在子函数的prototype里面new Father() ,使Father的原型中的方法也得到继承,最后改变Son的原型中的constructor

  • 优点:组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点。可以继承父类的属性,可以传参,父类的实例方法定义在父类的原型对象上,可以复用,不共享父类引用属性。
  • 缺点:调用了两次父类的构造函数,(第一次是在创建子类原型的时候,第二次是在子类构造函数内部)。导致子的原型对象中增添了不必要的父类的实例对象中的所有属性。
function SuperType(name){
    this.name = name
    this.colors = ["red", "blue", "green"];
}

SuperType.prototype.sayName = function(){
    console.log(this.name);
}

function SubType(name, age){
    
    //继承属性
    SuperType.call(this,name);"二次调用"

    this.age = age;
}

//继承方法
SubType.prototype = new SuperType();// "一次调用"
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
    console.log(this.age);
}

var instance1 = new SubType("james",9);
instance1.colors.push("black");
console.log(instance1.colors);  //"red,blue,green,black"
instance1.sayName(); // "james"
instance1.sayAge(); // 9

var instance2 = new SubType("kobe",10);
console.log(instance2.colors);  //"red,blue,green"
instance2.sayName(); // "kobe"
instance2.sayAge(); // 10

原型式继承

简单来说这个函数的作用就是,传入一个对象,返回一个原型对象为该对象的新对象。

function object(o){
    function F(){};
    F.prototype = o;
    return new F();
}
  • 优点:可以实现基于一个对象的简单继承,不必创建构造函数
  • 缺点:与原型链中提到的缺点相同,一个是传参的问题,一个是属性共享的问题,无法复用(新实例属性都是后面添加的)。

寄生式继承

创建一个仅用于封装继承过程的函数,该函数在内部以某种方式增强对象,最后返回这个对象。

  • 优点:在主要考虑对象而不是自定义类型和构造函数的情况下,实现简单的继承。
  • 缺点:使用该继承方式,在为对象添加函数的时候,没有办法做到函数的复用(没用到原型)。
function createAnother(original){
    
    var clone = object(original); //通过调用函数创建一个新对象
    
    clone.sayHi = function(){  // 某种方式增强这个对象
        console.log("hi");
    }

    return clone;  // 返回这个对象
}

var person = {
    name: "james"
}

var anotherPerson = createAnother(person);

anotherPerson.sayHi(); // "hi"

寄生式组合继承

解决组合继承的缺点,在继承原型时,我们继承的不是父类的实例对象,而是原型对象是父类原型对象的一个实例对象。

优点:效率高,避免了在 SubType.prototype 上创建不必要的属性。与此同时还能保持原型链不变,开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。

function inheritPrototype(subType, superType){

    var prototype = object(superType.prototype); // 创建原型对象是超类原型对象的一个实例对象
  
    prototype.constructor = subType; // 弥补因为重写原型而失去的默认的 constructor 属性。
 
    subType.prototype = prototype; // 实现原型继承
}

extend继承
ES6的extend(寄生组合继承的语法糖)


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

8.this指向问题

this 指向最后一次调用这个方法的对象
在这里插入图片描述
为什说this指向,谁调用就指向谁,是不严谨的说法?

  1. 如果 一个函数中有this,但是它没有被上一级对象调用。那么this就指向的是window。但是在严格模式下不同。
  2. 如果 一个函数中有this,当它被上一级对象所调用,那么this指向就指向你的上一级对象。
  3. 如果 一个函数中有this,这个函数中有多个对象,尽管这个函数是被最外层的对象所调用,this指向的也是它的上一级对象。

9.改变this指向(call、apply、bind )

相同点:

都可以改变函数内部的this指向。

不同点:

  1. call和apply会调用函数,并且改变函数内部指向。
  2. call和apply传递参数类型不一样,call传递参数形式aru1,aru2形式…apply必须传递数组
  3. bind 不会调用函数,也可以改变函数内部this指向

主要应用场景:

  1. call经常做继承
  2. apply 经常和数组有关,比如实现数组中的最大值,最小值等
  3. bind 不调用函数,但是还是想改变this指向,比如改变定时器内部的this指向。
//手写call
Function.prototype.myCall = function (context) {
      // 先判断调用myCall是不是一个函数
      // 这里的this就是调用myCall的
      if (typeof this !== 'function') {
        throw new TypeError("Not a Function")
      }
      // 不传参数默认为window
      context = context || window
      // 保存this
      context.fn = this
      // 保存参数
      let args = Array.from(arguments).slice(1)   //Array.from 把伪数组对象转为数组
      // 调用函数
      let result = context.fn(...args)
      delete context.fn
      return result
    }

//手写apply
Function.prototype.myApply = function (context) {
      // 判断this是不是函数
      if (typeof this !== "function") {
        throw new TypeError("Not a Function")
      }
      let result
      // 默认是window
      context = context || window
      // 保存this
      context.fn = this
      // 是否传参
      if (arguments[1]) {
        result = context.fn(...arguments[1])
      } else {
        result = context.fn()
      }
      delete context.fn
      retun result
    }

//手写bind
Function.prototype.myBind = function(context){
      // 判断是否是一个函数
      if(typeof this !== "function") {
        throw new TypeError("Not a Function")
      }
      // 保存调用bind的函数
      const _this = this 
      // 保存参数
      const args = Array.prototype.slice.call(arguments,1)
      // 返回一个函数
      return function F () {
        // 判断是不是new出来的
        if(this instanceof F) {
          // 如果是new出来的
          // 返回一个空对象,且使创建出来的实例的__proto__指向_this的prototype,且完成函数柯里化
          return new _this(...args,...arguments)
        }else{
          // 如果不是new出来的改变this指向,且完成函数柯里化
          return _this.apply(context,args.concat(...arguments))
        }
      } 
    }

10.什么是作用域,什么是作用域链?

  • 规定变量和函数的可使用范围称为作用域
  • 查找变量或者函数时,需要从局部作用域到全局作用域依次查找,这些作用域的集合称作用域链。

11.什么是执行栈,什么是执行上下文?

执行栈

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

执行上下文(执行环境)

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

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

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

12.变量提升?

我们JS引擎在运行JS的时候,分为两步,第一步是预解析,一步是代码执行,预解析分为变量预解析(变量提升)与函数预解析(函数提升)。函数提升优先级高于变量提升

  • 变量提升就是把所有的变量声明提升到当前作用域的最前边,变量赋值不会提升。
  • 函数提升是函数的声明会被提升到当前作用域的最前上边,但是不会调用函数。

13.闭包

什么是闭包?
闭包是指有权访问另一个函数作用域中变量的函数
简单理解就是,一个作用域可以访问到另一个函数内部的局部变量。

  • 作用:延伸变量的作用范围,其主要目的是保证一个函数内部的变量既可以得到重用,又不被污染(不会被随意篡改)
  • 缺点:会造成内存泄漏(有一块内存空间被长期占用,而不被释放)
  • 应用:防抖和节流、封装私有变量、for循环中的保留i的操作、函数柯里化

14.事件循环(Event Loop)

当JS解析执行时,会被引擎分为两类任务,同步任务(synchronous)异步任务(asynchronous)

  • 对于同步任务来说,会被推到执行栈按顺序去执行这些任务。
  • 对于异步任务来说,当其可以被执行时,会被放到一个任务队列(task queue)里等待JS引擎去执行。

当执行栈中的所有同步任务完成后,JS引擎才会去任务队列里查看是否有任务存在,并将任务放到执行栈中去执行,执行完了又会去任务队列里查看是否有已经可以执行的任务。这种循环检查的机制,就叫做事件循环(Event Loop)。

15.宏任务和微任务

对于任务队列,其实是有更细的分类。其被分为 微任务(microtask)队列 == & == 宏任务(macrotask)队列

微任务: Promise的then、Mutation Observer、process.nextTick()等,会被放在微任务(microtask)队列。
宏任务: setTimeout、setInterval、ajax等,会被放在宏任务(macrotask)队列。

Event Loop的执行顺序是:

  • 首先执行执行栈里的任务。(new Promise 构造函数是同步执行的)
  • 执行栈清空后,检查微任务(microtask)队列,将可执行的微任务全部执行。
  • 取宏任务(macrotask)队列中的第一项执行。
  • 回到第二步,循环执行

16.内存泄露、垃圾回收机制

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

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

哪些操作会造成内存泄漏?

  • 未使用 var 声明的全局变量
  • 作用域未释放(闭包)
  • 定时器未清除
  • 事件监听为空白

如何优化内存泄漏?

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

垃圾回收机制都有哪些策略

  • 标记清除法:垃圾回收器会在运行的时候给内存中的所有变量加上标记,然后去掉执行环境中的变量以及被执行环境中变量所引用的变量(闭包),在这些完成之后仍存在标记的就是要删除的变量了
  • 引用计数法:跟踪记录每个值被引用的次数。当声明一个变量并给该变量赋值一个引用类型的值时候,该值的计数+1,当该值赋值给另一个变量的时候,该计数+1,当该值被其他值取代的时候,该计数-1,当计数变为0的时候,说明无法访问该值了,垃圾回收机制清除该对象

17.深拷贝和浅拷贝

浅拷贝是拷贝一层,深层次的对象级别的就拷贝引用;
深拷贝是拷贝多层,每一级别的数据都会拷贝出来。
具体来说,浅拷贝的时候如果数据是基本数据类型,那么就如同直接赋值那种,会拷贝其本身,如果除了基本数据类型之外还有一层对象,那么对于浅拷贝而言就只能拷贝其引用,对象的改变会反应到拷贝对象上;但是深拷贝就会拷贝多层,即使是嵌套了对象,也会都拷贝出来。
浅拷贝实现:

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

深拷贝实现:
1.JSON.stringify,JSON.parse;但是遇到正则会变为空对象,函数为空,日期会变为字符串
在这里插入图片描述

18.var let const 有什么区别

在这里插入图片描述
2021.7.16

19.JS如何实现异步编程(5种)?

  • 回调函数(callback)
    优点:解决了同步的问题(只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。)
    缺点:回调地狱,每个任务只能指定一个回调函数,不能 return.

  • 事件监听。这种思路是说异步任务的执行不取决于代码的顺序,而取决于某个事件是否发生。比如一个我们注册一个按钮的点击事件或者注册一个自定义事件,然后通过点击或者trigger的方式触发这个事件。

  • Promise

  • Generator

  • 生成器 async/await,是ES7提供的一种解决方案。

20.Promise?

ES6引入的异步编程的新解决方案,语法是一个构造函数 ,用来封装异步操作并可以获取其成功或失败的结果. Promise对象有三种状态:初始化pending 成功fulfilled 失败rejected
需要注意:一旦从进行状态变成为其他状态就永远不能更改状态了。

特点

  • 将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。流程更加清晰,代码更加优雅。

  • Promise对象提供统一的接口,使得控制异步操作更加容易。

缺点

  • 无法取消Promise,一旦新建它就会立即执行,无法中途取消。
  • 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。
  • 当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

21.Generator是怎么样使用的以及各个阶段的变化如何?

  1. 首先生成器是一个函数,异步操作需要暂停的地方,都用 yield 语句注明。
  2. 调用生成器后不会立即执行,而是通过返回的迭代器来控制这个生成器的一步一步执行的
  3. 通过调用迭代器的next方法来请求一个一个的值,返回的对象有两个属性,一个是value,也就是值;另一个是done,是个布尔类型,done为true说明生成器函数执行完毕,没有可返回的值了
  4. done为true后继续调用迭代器的next方法,返回值的value为undefined

状态变化

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

22.箭头函数

箭头函数()=>{}:
1.如果形参只有一个,则小括号可以省略;
2.函数体如果只有一条语句,则花括号可以省略,函数的返回值为该条语句的执行结果。

与普通函数的区别

  1. 普通函数有函数提升,而箭头函数没有
  2. 箭头函数没有属于自己的this,this等同于上一层非箭头函数的this值或全局对象(window或undefined)(严格模式this是undefined)
  3. 箭头函数不能作为构造函数,也就是说,不可以使用new命令(没有this),否则会抛出一个错误。
  4. 没有yield 属性,不能用作生成器 Generator 函数

箭头函数不能new

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

箭头函数的作用:

  1. 语法简洁
  2. 可以隐式返回(无return)
  3. 不绑定this,在声明的时候就确定了,this的指向并不会随方法的调用而改变

23.forEach、for in 、 for of三者的区别

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

for…in是用来循环带有字符串key的对象的方法。实际是为循环”enumerable“(可枚举)对象而设计的。
Js基本数据类型自带的原型属性不可枚举,通过Object.defineProperty0方法指定enumeralbe为false的属性不可枚举。

let obj = {a: '1', b: '2', c: '3', d: '4'}
for (let o in obj) {
    console.log(o)    //遍历的实际上是对象的属性名称 a,b,c,d
    console.log(obj[o])  //这个才是属性对应的值1,2,3,4
}

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

for…of数组对象都可以遍历,它是ES6中新增加的语法

一个数据结构只有部署了 Symbol.iterator 属性, 才具有 iterator接口可以使用 for of循环。

哪些数据结构部署了 Symbol.iteratoer属性了呢?
数组 Array
Map
Set
String
arguments对象
Nodelist对象, 就是获取的dom列表集合

for of 遍历对象需要通过和Object.keys()

let obj = {a: '1', b: '2', c: '3', d: '4'}
for (let o of Object.values(obj)) {
    console.log(o) // 1,2,3,4
}

24.模块化

什么是模块?

  • 将一个复杂的程序依据一定的规则(规范)封装成几个块(文件), 并进行组合在一起
  • 块的内部数据与实现是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通信

模块化的好处

  • 避免命名冲突(减少命名空间污染)
  • 更好的分离, 按需加载
  • 更高复用性
  • 高可维护性

实现模块化的方式

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

CommonJS规范主要用于服务端编程,加载模块是同步的,这并不适合在浏览器环境,因为同步意味着阻塞加载,浏览器资源是异步加载的,因此有了AMD CMD解决方案。

AMD规范在浏览器环境中异步加载模块,而且可以并行加载多个模块。不过,AMD规范开发成本高,代码的阅读和书写比较困难,模块定义方式的语义不顺畅。

CMD规范与AMD规范很相似,都用于浏览器编程,依赖就近,延迟执行,可以很容易在Node.js中运行。不过,依赖SPM 打包,模块的加载逻辑偏重

ES6 模块在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。

CommonJS模块 与 ES6模块

CommonJS模块:它通过 require 来引入模块,通过 module.exports 定义模块的输出接口。这种模块加载方案是服务器端的解决方案,它是以同步的方式来引入模块的,因为在服务端文件都存储在本地磁盘,所以读取非常快,所以以同步的方式加载没有问题。但如果是在浏览器端,由于模块的加载是使用网络请求,因此使用异步加载的方式更加合适。

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

区别

  • CommonJS 模块输出的是值的拷贝,模块内部的变化就影响不到这个值。ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。
  • CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。**CommonJS 模块就是对象,即在输入时是先加载整个模块,生成一个对象,然后再从这个对象上面读取方法,这种加载称为“运行时加载”。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。

25.跨域的方式都有哪些?他们的特点是什么 ?

出于浏览器的同源策略限制。同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。同源策略会阻止一个域的javascript脚本和另外一个域的内容进行交互。所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号(port)

  1. JSONP
    JSONP 是服务器与客户端跨源通信的常用方法。最大特点就是简单适用,兼容性好(兼容低版本IE),缺点是只支持get请求,不支持post请求。
    核心思想:网页通过添加一个script元素,向服务器请求 JSON 数据,服务器收到请求后,将数据放在一个指定名字的回调函数的参数位置传回来。
  2. 设置document.domain解决无法读取非同源网页的 Cookie问题
    因为浏览器是通过document.domain属性来检查两个页面是否同源,因此只要两个页面通过设置相同的document.domain,两个页面就可以共享Cookie(此方案仅限主域相同,子域不同的跨域应用场景。)
  3. CORS
    CORS 是跨域资源分享(Cross-Origin Resource Sharing)的缩写。它是 W3C 标准,属于跨源 AJAX 请求的根本解决方法。
    1、 普通跨域请求:只需服务器端设置Access-Control-Allow-Origin
    2、带cookie跨域请求:前后端都需要进行设置,前端设置根据xhr.withCredentials字段判断是否带有cookie,后端Java还可以使用springMVC的@CrossOrigin
  4. nginx代理跨域
    ginx模拟一个虚拟服务器,因为服务器与服务器之间是不存在跨域的。
    发送数据时 ,客户端->nginx->服务端;返回数据时,服务端->nginx->客户端。

26.DOM事件流

在这里插入图片描述
DOM 标准采用捕获+冒泡。两种事件流都会触发 DOM 的所有对象,从 window 对象开始,也在 window 对象结束。

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

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

27.防抖和节流

防抖:n秒后在执行该事件,若在n秒内被重复触发,则重新计时
节流: n秒内只运行一次,若在n秒内重复触发,只有一次生效

<button id="debounce">点我防抖!</button>

$('#debounce').on('click', debounce());

function debounce() {
    let timer;
    // 闭包
    return function () {
        clearTimeout(timer);
        timer = setTimeout(() => {
            // 需要防抖的操作...
            console.log("防抖成功!");
        }, 500);
    }
}

------------------------------------------------------------------
<button id="throttle">点我节流!</button>

function throttle(fn, delay) {
    let flag = true,
        timer = null
    return function(...args) {
        let context = this
        if(!flag) return
        
        flag = false
        clearTimeout(timer)
        timer = setTimeout(function() {
            fn.apply(context,args)
            flag = true
        },delay)
    }
}


28.柯里化

柯里化(Currying),又称部分求值(Partial Evaluation),可以理解为提前接收部分参数,延迟执行,不立即输出结果,而是返回一个接受剩余参数的函数。因为这样的特性,也被称为部分计算函数。柯里化,是一个逐步接收参数的过程。

柯里化有3个常见作用:

  • 参数复用
  • 提前返回
  • 延迟计算/运行
  function add() {
	//将传入的不定参数变为数组     
      var args = Array.prototype.slice.call(arguments)

      var adder = function () {
        args.push(...arguments)
        return adder
      }
	//toString隐式转换     
      adder.toString = function () {
        return args.reduce((prev, curr) => {
          return prev + curr
        }, 0)
      }

      return adder
    }

    let a = add(1, 2, 3)
    let b = add(1)(2)(3)
    console.log(a)
    console.log(b)
    console.log(add(1, 2)(3));
    console.log(Function.toString)

29.js常见的设计模式

  • 单例模式
  • 工厂模式
  • 构造函数模式
  • 发布订阅者模式
  • 迭代器模式
  • 代理模式

30.判断数据类型的方法

typeof:无法判断null,是obj类型,而且只能判断出是引用类型

instanceof

constructor:null和undefined是无效的对象,因此是不会有constructor存在的,这两种类型的数据需要通过typeof来判断。

Object.prototype.toString :Object.prototype.toString.call(undefined) ;

31.数组扁平化

递归

 let arr = [1, [2, [3, 4]]];
 function flattern(arr) {
        let result = [];
        for(let i = 0; i < arr.length; i++) {
            if(Array.isArray(arr[i])) {
                flattern(arr[i])
            } else {
                result.push(arr[i])
            }
        }
        return result;
    }
console.log(flattern(arr));

toString

let arr = [1, [2, [3, 4]]];
function flatten(arr) {
    return arr.toString().split(',').map(function(item){
        return +item //+可以快速获得Number类型
    })
}
console.log(flatten(arr))

reduce

let arr = [1, [2, [3, 4]]];
 
function flatten(arr) {
    return arr.reduce(function(prev, next){
        return prev.concat(Array.isArray(next) ? flatten(next) : next)
    }, [])
}
 
console.log(flatten(arr))

es6 展开运算符

let arr = [1, [2, [3, 4]]];
 
function flatten(arr) {
 
    while (arr.some(item => Array.isArray(item))) {
        arr = [].concat(...arr);
    }  //ES6新方法
 
    return arr;
}
 
console.log(flatten(arr))
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值