前端JS面试题总结

目录

什么是JS

DOM

BOM

JS的数据类型

基本数据类型与引用数据类型的区别

数据类型检测

数据类型的转换

深拷贝与浅拷贝

数组去重

数组排序

数组的扁平化

构造函数的原型,原型链,

js的继承

constructor,__proto__,prototype的三角关系

作用域

自由变量与作用域链

变量的提升(预解析)

闭包

this指向

修改this指向

垃圾回收机制与内存泄漏

宏任务与微任务

异步与单线程

for···in和for···of的区别

null和undefined区别

设计模式

js的事件

DOM事件的三种级别

DOM的事件类型

DOM事件的事件流(事件传播)

自定义事件

解决跨域

let、var、const区别

es6解构赋值

箭头函数与普通函数的区别

promise使用及实现

async/await

Es6中新的数据类型symbol

当url输入到页面发生了什么

三次握手和四次挥手

http和https的区别

使用基于token的登录流程

Get和post

web安全及防护

html和XML

浏览器的回流与重绘

MVC与MVVM

常用状态码

面向对象与面向过程编程

找出数组中的重复数据

es6的新方法

前端性能优化


什么是JS

ECMAScript

JS的基本语法
DOM文档对象模型,操作页面元素,顶级对象  document
BOM浏览器对象模型,顶级对象  window

DOM

DOM是网页中用来表示文档中对象的标准模型,他是由节点和对象组成的结构集合。在浏览器解析HTML标签时,会构建一个DOM树结构,把html结构化成js可以识别的树模型

DOM元素的获取

document.getElementById("id名")

通过ID来获取元素,因为id的唯一性,获取到的是单独的一个数据

document.getElementsByTagName("标签名")

通过标签名来获取元素,获取到的是一个数组,需要单个数据需要加 [ 0 ]

document.getElementsByClassName("类名")

通过类型获取元素,获取到的是一个数组

 document.body

获取body

document. documentElement

获取html

document.querySelector("选择器")

h5新增方法,获取单个元素

document.querySelectorAll("选择器") 

h5新增方法,获取多个元素

DOM元素的操作

document.createElement("要创建的标签名")

创建标签

父元素.append(要追加的子元素)

在父元素的末尾追加元素   

父元素.insertBefore(子元素父元素.children[i])

在父元素的第几个下标中插入元素

元素.remove( )

删除元素自身 

元素.parentNode.remove( )

删除父元素节点

元素.parentNode

获取的是元素的父节点

父元素.children

获取父元素的子元素节点

元素.previousElementSibling

找当前元素的上一个兄弟元素节点

元素.nextElementSibling

找当前元素的下一个兄弟元素节点

元素.classList.add("要添加的类名")

添加类名

BOM

BOM(Browser Object Model)即浏览器对象模型,与浏览器窗口进行交互的对象,其核心对象是 window。

BOM的五大组成部分

浏览器:document     位置 :location    导航:navigation   屏幕:screen   历史:history

BOM的一些api

元素.focus()

让元素自动聚焦  

setInterval ( function ( ) {   函数体代码  } ,相隔时间)

定时器,根据时间间隔自动执行,异步事件宏任务

setTimeout( function ( ) {   函数体代码  } ,相隔时间)

延时器,延时多长时间后在执行,异步事件宏任务

clearInterval(序列号)

关闭定时器

clearTimeout(序列号)

关闭延迟器

location.href

获取当前页面的url地址

location.reload()

重新加载当前页面

location.assign("要跳转的url")

重新跳转页面        

navigation.userAgent

判断当前是移动端还是pc端

( function( ) {  函数体代码 })  ( )  

自执行函数,可以在()里传参

window.οnscrοll=function(){}        

给浏览器绑定滚动事件

document.body.scrollTop || document.documentElement.scrollTop

获取滚动高度(注意处理兼容性):同时获取body和html

window.scroll({

           top:0,

           behavior:'smooth'

       })

可以匀速滚动到页面头部的代码

BOM的盒模型

元素.offsetParent

获取的是带有定位的最近的父元素

元素.offsetLeft

相对于父元素左边的距离

元素.offsetWidth

获取元素本身所占据的宽度,包括内容、边框和padding

元素.offsetHeight

获取元素本身所占据的高度,包括内容、边框和padding

元素.clientTop

获取的是元素上边框的大小

元素.clientLeft

获取的是元素左边框的大小

元素.clientWidth

获取的元素包含padding、内容的宽度,不含边框

元素.clientHeight

获取的元素包含padding、内容的高度,不含边框

JS的数据类型

js分为简单数据类型(值类型)分为以下几种

Number、String、Boolean、Undefined、Null、Symbol(es6新增独一无二的值)

js的复杂数据类型(引用数据类型)只有一种

Object  其中包含了Object、Array、 function、Date、RegExp

点击查看数组,对象,字符串的简单方法

基本数据类型与引用数据类型的区别

基本类型值:指的是保存在内存中的简单数据段;number string 布尔,栈内存中是从上到下排列

引用类型值:指的是那些保存在内存中的对象,意思是,变量中保存的实际上只是一个指针,这个指针指向内存堆中实际的值,数组 对象,堆内存是从下到上排列

数据类型检测

数据类型判断大概有四种typeof、instanceof、constructor、Object.prototype.toString.call()

1.typeof:  

使用方法:  typeof  'hello'    返回值为string

使用typeof检测基本数据类型,分别返回对应的数据类型小写字符。

null是特殊的,检测会返回object

检测构造函数创建的变量(New Date()),都是object 

检测引用数据类型,返回值都是object ,function返回值还是function

2 . instanceof    

使用方法:   [ ] instanceof  Array   返回值是true或false
instanceof运算符需要指定一个构造函数,或者说指定一个特定的类型,它用来判断这个变量的原型是否继承于某个类

用与检测引用数据类型,无法检测基本数据类型,不然返回值都是false

3.constructor

使用方法:  vue a =15;  a.constructor==Number  返回值为true

constructor是prototype对象上的属性,指向构造函数。根据实例对象寻找属性的顺序,若实例对象上没有实例属性或方法时,就去原型链上寻找,因此,实例对象也是能使用constructor属性的

适用于除null与undefined的所有数据类型,null与undefined没有原型,所以无法检测,会报错

4 . 使用Object.prototype.toString.call()检测对象类型

使用方法:Object.prototype.toString.call(123)   返回值为"[object Number]"

可以通过toString() 来获取每个对象的类型。为了每个对象都能通过 Object.prototype.toString() 来检测,需要以 Function.prototype.call() 或者 Function.prototype.apply() 的形式来调用,传递要检查的对象作为第一个参数,称为thisArg

可以检测任意数据类型,包括null与undefined

封装一个获取变量准确类型的函数

function gettype(obj) {
  var type = typeof obj;

  if (type !== 'object') {
    return type;
  }
  //如果不是object类型的数据,直接用typeof就能判断出来

  //如果是object类型数据,准确判断类型必须使用Object.prototype.toString.call(obj)的方式才能判断
  return Object.prototype.toString.call(obj).replace(/^\[object (\S+)\]$/, '$1');
}

数据类型的转换

数组转字符串 

 数组名.join(‘分隔符’)

数组转对象  

    let arr =[a,b,c,d]   
    var obj ={ ...arr }

    //obj的值为 {0:a, 1:b, 2:c, 3:d}

对象转数组  

 Array.from() 将伪数组或可遍历对象转换为真正的数组

字符串转数组  

字符串名.split(‘分隔符’)

转数值

parseInt( ) 不保留小数

parseFloat( ) 保留小数

Number( ) 其中有一个数据不能转换就是NaN

转换字符串 

 toString( )    string( )    给字符拼接一个‘ ’

转换布尔值

 Boolean( )   其中0 ‘’undefiend null NaN转换后是false


 

深拷贝与浅拷贝

1.浅拷贝: 将原对象或原数组的引用(指针)直接赋给新对象/数组,新对象/数组只是原对象的一个引用(指针)

2.深拷贝: 创建一个新的对象和数组,将原对象的各项属性的“值”(数组的所有元素)拷贝过来,是“值”而不是“引用”

数组的浅拷贝

1 直接遍历

2 slice()

slice() 方法返回一个从已有的数组中截取一部分元素片段组成的新数组(不改变原来的数组!)

var array = [1, 2, 3, 4];
var copyArray = array.slice();
copyArray[0] = 100;
console.log(array); // [1, 2, 3, 4]
console.log(copyArray); // [100, 2, 3, 4]

3 concat()

concat() 方法用于连接两个或多个数组。( 该方法不会改变现有的数组,而仅仅会返回被连接数组的一个副本。)

var array = [1, 2, 3, 4];
var copyArray = array.concat();
copyArray[0] = 100;
console.log(array); // [1, 2, 3, 4]
console.log(copyArray); // [100, 2, 3, 4]

对象的浅拷贝

1 直接遍历

2 ES6的Object.assign

Object.assign:用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target),并返回合并后的target

var obj = {
  name: '张三',
  job: '学生'
}
var copyObj = Object.assign({}, obj);
copyObj.name = '李四';
console.log(obj);   // {name: "张三", job: "学生"}
console.log(copyObj);  // {name: "李四", job: "学生"}
 

3 ES6扩展运算符

扩展运算符(…)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中,不适用于多层数据嵌套

深拷贝

1 先转换成字符串,在转换成(数组/对象) JSON.parse(JSON.stringify(XXXX))

2 手写递归拷贝

function deepClone(obj = {}) {
    if (typeof obj !== 'object' || obj == null) {
        // obj 是 null ,或者不是对象和数组,直接返回
        return obj
    }

    // 初始化返回结果
    let result
    if (obj instanceof Array) {
        result = []
    } else {
        result = {}
    }

    for (let key in obj) {
        // 保证 key 不是原型的属性
        if (obj.hasOwnProperty(key)) {
            // 递归调用!!!
            result[key] = deepClone(obj[key])
        }
    }

    // 返回结果
    return result
}

数组去重

1 es6的set方法

es6的set数据格式会自动去除数组中重复的数据,只应用与简单数组,对于过于复杂的数组不能实现

var arr  = [1,1,'true','true',true,true,15,15,false,false, undefined,
undefined, null,null, NaN,NaN,'NaN', 0, 0, 'a', 'a',{},{}];
 
function arr_unique1(arr){
return  [...new Set(arr)];
//或者
//return  Array.from(new Set(arr));
}
arr_unique1(arr); // (13)[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}, {…}]

2 map数据结构去重

创建一个空Map数据结构,遍历需要去重的数组,把数组的每一个元素作为key存到Map中。由于Map中不会出现相同的key值,所以最终得到的就是去重后的结果

function arr_unique2(arr) {
  let map = new Map();
  let array = new Array();  // 数组用于返回结果
  for (let i = 0; i < arr.length; i++) {
    if(map .has(arr[i])) {  // 如果有该key值
      map .set(arr[i], true);
    } else {
      map .set(arr[i], false);   // 如果没有该key值
      array .push(arr[i]);
    }
  }
  return array ;
}

 console.log(arr_unique2(arr)); //(13) [1, "a", "true", true, 15, false, 1, {…}, null, NaN, NaN, "NaN", 0, "a", {…}, undefined]

3 利用递归去重

function arr_unique3(arr) {
     var array= arr;
     var len = array.length;
     array.sort(function(a,b){   //排序后更加方便去重
     return a - b;
    })
    
 function loop(index){
        if(index >= 1){
            if(array[index] === array[index-1]){
                array.splice(index,1);
            }
            loop(index - 1);    //递归loop,然后数组去重
        }
    }
    loop(len-1);
    return array;
}
 
console.log(arr_unique3(arr)); //(14) [1, "a", "true", true, 15, false, 1, {…}, null, NaN, NaN, "NaN", 0, "a", {…}, undefined]

4 forEach + indexOf  或  forEach + includes  或 filter+indexOf 或 filter+includes

通过forEach循环或者filter查询所有数据,利用indexOf或includes判断每一项是否与当前要添加的数据相同,如果相同,则终止,不相同就添加

注意:使用indexOf无法判别NaN,因为indexOf用于判断当前元素在数组的下标,有就返回当前下标,没有就返回-1,而它用来判别NaN都是-1

数组排序

1 冒泡排序法

将数组中的相邻两个元素进行比较,将比较大(较小)的数通过两两比较移动到数组末尾(开始),执行一遍内层循环,确定一个最大(最小)的数,外层循环从数组末尾(开始)遍历到开始(末尾)

function MaoPaoSort(arr){
        for(var i = 0;i<arr.length-1;i++) {
            for(var j = 0;j<arr.length-i-1;j++){
                if(arr[j]>arr[j+1]){
                    //把大的数字放到后面
                    var str = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = str;
                }
            }
        }
    }
    var arr = [3,5,1,2,7,8,4,5,3,4];
    //console.log(arr);[3,5,1,2,7,8,4,5,3,4];
    MaoPaoSort(arr);
    //console.log(arr);[1, 2, 3, 3, 4, 4, 5, 5, 7, 8]

2 插入排序法(插队排序)

将要排序的数组分成两部分,每次从后面的部分取出索引最小的元素插入到前一部分的适当位置

  • 从第一个元素开始,该元素可以认为已经被排序;
  • 取出下一个元素,在已经排序的元素序列中从后向前扫描;
  • 如果该元素(已排序)大于新元素,将该元素移到下一位置;
  • 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
  • 将新元素插入到该位置后;
  • 重复步骤2~5。
function InsertSort(arr) {
  let len = arr.length;
  let preIndex, current;
  for (let i = 1; i < len; i++) {
    preIndex = i - 1;
    current = arr[i];
    while (preIndex >= 0 && current < arr[preIndex]) {
      arr[preIndex + 1] = arr[preIndex];
      preIndex--;
    }
    arr[preIndex + 1] = current;
  }
  return arr;
}
 
 
var arr = [3,5,7,1,4,56,12,78,25,0,9,8,42,37];
InsertSort(arr);

3 快速排序法

将一个数组的排序问题看成是两个小数组的排序问题,以一个数为基准(中间的数),比基准小的放到左边,比基准大的放到右边,而每个小的数组又可以继续看成更小的两个数组,一直递归下去,直到数组长度大小最大为2

function quickSort(arr){
   //如果数组长度小于1,没必要排序,直接返回
   if(arr.length<=1) return arr;
   //pivot 基准索引,长度的一半
   let pivotIndex = Math.floor(arr.length/2);//奇数项向下取整
   //找到基准,把基准项从原数组删除
   let pivot = arr.splice(pivotIndex,1)[0];
   //定义左右数组
   let left = [];
   let right = [];
   //把比基准小的放left,大的放right
   arr.forEach(element => {
       if(element<pivot){
           left.push(element)
       }else{
           right.push(element)
       }
   });
   return quickSort(left).concat([pivot],quickSort(right))
}
     
     
var arr=[4,56,3,67,44,5,66];
console.log(quickSort(arr));//[3, 4, 5, 44, 56, 66, 67]

4 sort排序

数组的扁平化

数组的扁平化就是将一个多维数据转换为一个一维数组

实现基本方式

1、对数组的每一项进行遍历。

2、判断该项是否是数组。

3、如果该项不是数组则将其直接放进新数组。

4、是数组则回到1,继续迭代。

5、当数组遍历完成,返回这个新数组。

点击查看扁平化的具体方法

构造函数的原型,原型链,

prototyoe:   每个构造函数里都有一个prototype属性,称为显示原型

__proto__:  每个实例化对象都有一个__proto__属性,称为隐式原型

constructor:每个prototype原型都有一个constructor属性,我们称它为构造函数

原型链:每一个实例对象有一个__proto__属性,指向构造函数的原型对象,构造函的原型对象也是一个对象,也有__proto__属性,这样一层一层往上找,直到找到Object.prototype为止,就形成了原型链,Object.prototype的__proto__值为null

js的继承

1 原型链继承  

// 定义子类构造函数
function Woman(){ 
}
// 将父类的实例化对象赋予子类的原型
Woman.prototype= new People();
// 手动给子类添加构造器
Woman.prototype.constructor = Woman
// 子类就可以继承和调用父类的属性和方法
Woman.prototype.name = 'haixia';

let womanObj = new Woman();

实现:把父构造函数的实例化对象,赋予子构造函数的原型

优点:简单易于实现,子类可以访问父类实例与原型上的方法

缺点:子类不可以向父类传参,子类将没有constructor构造器,需手动添加

2 构造函数继承(对象冒充式继承)

function Woman(name){
 //修改父类的this指向,使其指向子类构造函数
  People.call(this,name);  
  this.name = name || 'kitty'
}
let womanObj = new Woman();

实现:在子构造函数内部,改变父构造函数的this指向,使其指向子类构造函数,而子类的this指向它的实例化对象,这样就可以子类在实例化对象里,拿到两个构造函数的数据

优点:子类可以向父类传参

缺点:子类无法获取父类原型的属性与方法

3 实例继承(原型式继承)

function Wonman(name){
//使用一个对象来接收父类实例化对象
  let instance = new People();
  instance.name = name || 'wangxiaoxia';
// 返出这个对象
  return instance;
}
let wonmanObj = new Wonman();

实现:在子构造函数内部,用一个对象接收父构造函数的实例化对象,然后定义该对象的属性,并返出该对象

优点:即可以向父类传值,又可以继承父类的原型

缺点:构造器还是指向父类,不能多次继承

4 组合式继承

function Woman(name,age){
// 改变父构造函数的this指向
  People.call(this,name,age)
}
// 将父构造函数的实例化对象赋予子构造函数的原型
Woman.prototype = new People();
// 手动为子构造函数添加构造器
Woman.prototype.constructor = Woman;

let wonmanObj = new Woman(ren,27);
wonmanObj.eat(); 

实现:同时使用原型链继承与构造函数继承,即在子构造函数内改变父构造函数的this指向,也将父构造函数的实例化对象,赋予构造函数的原型

优点:即可以向父类传值,又可以继承父类的原型,也不会有构造器的指向问题

缺点:由于调用了两次父类,所以产生了两份实例,子构造函数没有constructor构造器,需手动添加

5 寄生组合继承

function Woman(name,age){
  //修改父类的this指向
  People.call(this,name,age)
}
//创建自执行函数
(function(){
  // 创建空类
  let Super = function(){};
  // 将父构造函数的原型赋予空类的构造函数的原型 
  Super.prototype = People.prototype;
  //将空类的实例化对象,赋予子构造函数的原型
  Woman.prototype = new Super();
})();

//给子构造函数添加构造器
Woman.prototype.constructor = Woman;

let womanObj = new Woman();

实现: 

1 在子构造函数内改变父构造函数的this指向

2 创建一个自执行函数,在内部创建一个空的类,即空的函数

3 将父构造函数的原型赋予空类的构造函数的原型 

4 将空类的实例化对象,赋予子构造函数的原型

5 自执行函数执行完毕之后,手动为子构造函数添加constructor构造器

优点:一个近乎完美的继承

6 class类的继承

Es6提出class类的概念,做为对象的模板,可以通过class来定义一个类,类的数据类型就是函数,类本身就指向构造函数

使用方法是:class 子类 extends 父类

 子类必须在constructor方法中调用super,否则会报错,它用来新建父类的this指向,super和constructor里要接收父级的形参

       class Game {
               constructor(name, type) {
                this.name = name
                this.type = type      }
       }

        class Role extends Game {
            constructor(roleName, name, type) {
            super(name, type)
            this.roleName = roleName  } 
       }

        let game = new Role('王者荣耀', '手游')

constructor,__proto__,prototype的三角关系

假设有一个构造函数Fn  一个Fn的实例化对象 obj

构造函数Fn的prototype属性指向了构造函数的原型对象Fn.prototype

实例对象obj的__proto__属性指向了构造函数的原型对象Fn.prototype

构造函数的原型对象的constructor属性又指回了构造函数Fn,

作用域

作用域就是一个变量可以使用的范围,主要分为全局作用域和函数作用域和块级作用域(es6新增)

全局作用域:Js中最外层的作用域

函数作用域:js通过函数创建的一个独立作用域,函数可以嵌套,所以作用域也可以嵌套

块级作用域:Es6中新增,由大括号包裹,比如:if(){}, for(){}  function等

自由变量与作用域链

自由变量:在当前作用域被使用,但是没有被定义的变量,被称作自由变量

作用域链:一个变量在当前作用域没有定义,但是被使用了,就会向上级作用域,一层一层依次查找,直至找到为止,如果全局作用域都没有找到这个变量就会报错这个自由变量查找的过程就是作用域链

注意:函数中的自由变量取决于函数定义的位置,与执行的位置无关

变量的提升(预解析)

js在运行之前,会把所有带有varfunction的变量与函数提升至当前作用域的最上方

var只提升变量,不提升赋值,而function提升整个函数体

注意:function的优先级比var要高

闭包

函数中套了一个函数,内层函数可以访问外层函数中的变量

闭包的分类:

1 函数做为参数被传递

2 函数做为返回值被返出

闭包的运用场景

1 封装对象的私有属性和方法,用于隐藏数据,做一个简单的缓存工具

2 做为回调函数使用

3 利用闭包实现函数的防抖,节流

   防抖:例如一个倒计时,被连续点击时,关闭上一个倒计时,重新开启一个新的倒计时

   节流:例如一个倒计时,被连续点击时,规定时间内,无论点击多少次,只触发一次

优点:闭包因为长期驻扎在内存中。可以重复使用变量,不会造成变量污染

缺点:闭包会使函数中的变量都被保存在内存中内存消耗很大滥用闭包会造成网页的性能问题可能会导致内存泄露

解决方法:在退出函数之前将不使用的变量全部删除

this指向

this指向什么值,是在执行的时候确定的,与定义的位置无关,箭头函数除外

常用的调用场景

普通函数指向当前调用它的对象,如果没有就是window
构造函数与class类指向当前的实例化对象
对象方法的调用调用该方法的对象
箭头函数

函数声明时所在的对象,与其他不同,其他都是调用时,箭头函数是声明时

定时器指向window   但是如果内部函数为箭头函数,则指向函数声明时所在的对象

修改this指向

call

多个参数

第一个参数就是要修改的this指向,其他参数是以散落的形式给调用该方法的函数传递参数

Game.call(this , '王者荣耀', '手游')

apply

两个参数

第一个参数就是要修改的this指向,第二个参数是以数组的形式给调用该方法的函数传递参数
Game.apply(this, ['王者荣耀', '手游'])

bind

一个参数,就是要修改的this指向

返回值是调用方法的函数本身 函数需要再次调用才会执行,传递参数可以在函数调用时传递
 Game.bind(this)('王者荣耀', '手游')

垃圾回收机制与内存泄漏

垃圾回收机制

浏览器的js具有自动垃圾回收机制(GC),执行环境会管理代码在执行中所使用的内存,垃圾回收器会定期寻找不再使用的变量,释放其内存,垃圾回收器会按照时间间隔周期性的执行

变量的死亡

全局作用域内的变量,会在关闭浏览器关闭页面时结束,被垃圾回收期回收

函数级与块级作用域内的变量,只有在函数执行的过程中存在,函数执行完毕,垃圾回收器回收释放

闭包内,因为内部函数使用外部函数变量的原因,它内部的变量,永远不会结束、

判别变量是否还有用的两种方式:

1 标记清除(常用)

垃圾回收器在运行的时候会给存储在内存中的所有变量都加上标记。当一个变量进入环境时,就将这个变量标记为“进入环境”,而当变量离开环境时,则将其标记为“离开环境”。垃圾收集器下次运行时,就会把所有标为离开环境的变量回收释放

function test(){
var a = 10 ;        
var b = 20 ;      
}
// 在调用该函数时,内部的变量会标记为进入环境
test();            
// 函数执行完毕之后 a、b又被标离开环境,被回收。

2 引用计数

跟踪记录每个值别引用的次数,当声明了一个变量,并将一个引用类型的值赋给该变量时,那这个值的引用次数就是1,而这个值再被赋值给另一个变量,那这个值的引用次数+1,相反,如果包含这个值的变量,获取了另外的值,那这个值的引用次数-1,当这个值的引用次数为0时,垃圾回收器下次运行时,就会把这个值回收释放

function test() {
    var a = {};    // a指向对象的引用次数为1
    var b = a;     // a指向对象的引用次数加1,为2
    var c = a;     // a指向对象的引用次数再加1,为3
    var b = {};    // a指向对象的引用次数减1,为2
}

内存泄漏

  1. 循环引用

一个DOM对象被一个Javascript对象引用,又引用同一个或其它的Javascript对象,这个DOM对象可能会引发内存泄露。将不会在脚本停止的时候被垃圾回收器回收。要想破坏循环引用,赋值为null即可

     2 闭包

在闭包中引入闭包外部的变量时,当闭包结束时此对象无法被垃圾回收

     3 DOM泄露

当原有的DOM被移除时,子结点引用没有被移除则无法回收

宏任务与微任务

js的机制是先执行同步事件,再执行异步事件

异步事件又分为了宏任务与微任务,微任务的执行时间要比宏任务早

宏任务:setTimeout setInterval Ajax DOM事件

微任务:promise   async/await

异步与单线程

异步和单线程是相辅相成的,js是一门单线程脚本语言,所以需要异步来辅助

异步与同步的区别

同步会阻塞代码的执行,而异步不会

JS的任务运行顺序

1 将所有的任务放进执行线

2 分出同步任务还是异步任务

3 将同步任务放入主线程,进行执行

4 将异步任务放入Event Table并注册函数,放入事件队列中

5 在同步任务完成后,再将任务队列的放入主线程进行执行

6 异步先执行微任务,再执行宏任务

注意事项

1 在异步事件中,遇到其他宏任务,将其放进下一个事件队列

2 swite异步代码同步执行,但是会把后面的代码,变成一个微任务

for···in和for···of的区别

从遍历数组角度来说,for···in遍历出来的是key(即下标),for···of遍历出来的是value(即数组的值);

从遍历字符串的角度来说,同数组一样。
从遍历对象的角度来说,for···in会遍历出来的为对象的key,但for···of会直接报错。

如果要使用for…of遍历普通对象,需要配合Object.keys()一起使用。

null和undefined区别

在 if 语句中 null 和 undefined 都会转为false两者用相等运算符比较也是相等

1.null表示没有对象,可能将来要赋值一个对象,即该处不应该有值

1 作为函数的参数,表示该函数的参数不是对象

2 作为对象原型链的终点

2 undefined表示缺少值,即此处应该有值,但没有定义

1 定义了形参,没有传实参,显示undefined

2 对象属性名不存在时,显示undefined

3 函数没有写返回值,即没有写return,拿到的是undefined

4 写了return,但没有赋值,拿到的是undefined

设计模式

一、单例模式

1. 定义

保证一个类仅有一个实例,并提供一个访问它的全局访问点

2. 核心

确保只有一个实例,并提供全局访问

3. 实现

在vue脚手架项目开发中,我们需要对axios进行请求拦截,响应拦截,多次调用封装好的axios实例也仅设置一次,封装后的axios就是要一个单例

二、发布者、订阅者模式

vue.js 则是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调


 

js的事件

DOM事件的三种级别

 DOM 0级事件

DOM 0级时间分两种,一是直接在标签内直接添加执行语句,二是定义执行函数

    <input type="text" id="test">
	<input type="button"	value="button"	onclick="alert(document.getElementById('test').value)">

	<script>
		document.getElementById('button').onclick=function(){								 
         alert(document.getElementById('test').value);
		}
	</script> 

DOM 2级事件

2级事件有3个参数,分别为 事件名称,执行函数,指定冒泡还是捕获,默认是false,冒泡。

element.addEventListener('click',function(){},false)

DOM 3级事件

同DOM2级一样,只不过添加了更多的事件类型,鼠标事件、键盘事件

element.addEventListener('keyup',function(){},false)

DOM的事件类型

事件类型分两种:事件捕获、事件冒泡。

事件捕获就是由外往内,从事件发生的顶点window开始,之后是document对象,逐级往下查找,一直到目标元素。

事件冒泡就是由内往外,从具体的目标节点元素触发,逐级向上传递,直到根节点window。

DOM事件的事件流(事件传播)

事件流就是,事件的传播过程。

DOM完整的事件流包括三个阶段:事件捕获阶段、目标阶段和事件冒泡阶段。

事件通过捕获到达目标元素,这个时候就是目标阶段。从目标节点元素将事件上传到根节点的过程就是第三个阶段,冒泡阶段

自定义事件

我们也可以通过 new Event()自定义事件

var eve = new Event('test'); //通过new Event 创建事件
	dom.addEventListener('test', function () { //注册事件
    console.log('test dispatch');
});
setTimeout(function () {
    dom.dispatchEvent(eve);  //触发事件
}, 1000);

解决跨域

什么是跨域

理解跨域的概念:协议、域名、端口都相同才同域,否则都是跨域

跨域就是指浏览器不允许当前页面的所在源,去请求另一个源的数据

JSONP原理        

出于安全考虑,服务器不允许 ajax 跨域获取数据,但是可以跨域获取文件内容,所以基于这一点,可以动态创建 script 标签,使用标签的 src 属性访问 js 文件的形式获取 js 脚本,并且这个 js 脚本中的内容是函数调用,该函数调用的参数是服务器返回的数据,为了获取这里的参数数据,需要事先在页面中定义回调函数,在回调函数中处理服务器返回的数据,这就是解决跨域问题的jsonp原理。

脚手架解决跨域

在脚手架中,创建 vue.congif.js 文件,里面的 devServer 是进行开发环境的服务器配置的,里面有proxy是用来进行代理的转发,在proxy里的 target 设置转发的网址

因为跨域的问题,axios是无法请求网址数据的,但是浏览器可以,所以,应用代理转发请求数据到我们设置的网址中,而axios请求该网址,相当于一个中间商进行了一次代理

let、var、const区别

Var是块级作用域,值可以更改,多次定义会被顶替,拥有变量提升特性,可以不赋值

Let 函数级作用域,值可以更改,不能多次定义,无法变量提升,可以不赋值

Const 函数级作用域,是一个常量,值不能为空,一经定义无法更改,无法变量提升,必须赋值

注意:const如果定义的是一个数组或者对象,它的值就是一个指针,数组或对象里面的值是可以更改的,并不影响这个指针


es6解构赋值

es6可以根据一定的规则,从数组或对象中提取值赋给变量,字符串也可以直接进行结构赋值,无论对象还是数组,两边的 = 数据类型和数据结构都必须保持一致

解构

数组的解构:数组的结构赋值是按照索引值一一对应的,如果多层嵌套,结构也必须保持一致,不想要的数据可以在解构时用隔开 空格表示

字符串的解构遵循数组的方式,因为它同样有下标和length

对象的解构:对象的解构按照key值来对应 {key}

//数组结构
var a, b, rest;
[a, b] = [10, 20];
console.log(a); // 10
console.log(b); // 20

//对象解构
({ a, b } = { a: 10, b: 20 });
console.log(a); // 10
console.log(b); // 20

// ...rest 解构数组
[a, b, ...rest] = [10, 20, 30, 40, 50];
console.log(a); // 10
console.log(b); // 20
console.log(rest); // [30, 40, 50]

// ...rest 解构对象(最新)
({a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40});
console.log(a); // 10
console.log(b); // 20
console.log(rest); // {c: 30, d: 40}

//解析一个从函数返回
function f() {
  return [1, 2];
}

var a, b; 
[a, b] = f(); 
console.log(a); // 1
console.log(b); // 2

解构的默认值

数组,对象,字符串都是一样的,默认值就是a = 值 ,默认值只有在没有数据,也就是undefined时生效

解构赋值的rest参数

rest会将剩余的所有数据赋值给rest,必须放在最后,...rest

箭头函数与普通函数的区别

普通函数会遇到this作用域问题,ES6新建箭头函数

箭头函数无法作为构造函数使用,无法实例化,

箭头函数this指向她所在的作用域,

箭头函数本身没有arguments参数

箭头函数不能通过call()、apply()、bind()方法直接修改它的this指向

箭头函数没有原型属性

promise使用及实现

promise是es6提出的一种异步编程的解决方案,接受两个函数做为参数,从语法上说,promise 是一个对象,从它可以获取异步操作的的最终状态(成功或失败),本质上是一个构造函数,

promise的状态

promise的状态不受外部影响,一经改变就代表执行结束,无法再进行更改        

pending 初始状态     fulfilled 成功状态    rejected 失败状态

promise的参数

resolve的作用是将promise的状态从进行中pending变为已完成fullfilled,并将成功信息返出

reject的作用是将promise的状态从进行中pending变为已失败rejected,并将失败信息返出

promise的状态一经改变就代表执行结束

promise一些api

.then()获取promise的结果

.catch()来获取失败时返出的错误信息

Promise.all( ) 接受多个promise的实例做为参数,参数必须是一个数组,promise都执行成功后,返回的是执行后的结果的一个数组,如果有一个失败了,返回的是第一个失败的返回值

Promise.race( ) 接受多个promise的实例做为参数,其中一个promise完成时执行,返回结果是第一个完成的promise的返回数据

名词约定

  • promise(首字母小写)对象指的是“Promise实例对象”

  • Promise首字母大写且单数形式,表示“Promise构造函数”

  • Promises首字母大写且复数形式,用于指代“Promises规范”

async/await

特点

  1. asayc的用法,它作为一个关键字放到函数前面,这样普通函数就变为了异步函数
  2. 异步async函数调用,跟普通函数的使用方式一样
  3. 异步async函数返回一个promise对象
  4. async函数配合await关键字使用,await异步代码同步执行,但是后面的代码将会被阻塞,变为一个异步微任务,

优点

1.方便级联调用:即调用依次发生的场景;

2.同步代码编写方式:更符合代码编写习惯;

3.多个参数传递: Promise的then函数只能传递一个参数,async/await没有这个限制,可以定义块级变量传递数据

4.同步代码和异步代码可以一起编写: async/await整个书写习惯都是同步的,不需要纠结同步和异步的区别,当然,异步过程需要包装成一个Promise对象放在await关键字后面;

5.sync/await是对Promise的优化:不过在写代码时,Promise本身的API出现得很少,很接近同步代码的写法;

Es6中新的数据类型symbol

Symbol 值通过Symbol函数生成。对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol

凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。

注意,Symbol函数前不能使用new命令,否则会报错。这是因为生成的 Symbol 是一个原始类型的值,不是对象,所以不能添加属性。基本上,它是一种类似于字符串的数据类型。

当url输入到页面发生了什么

浏览器的地址栏输入URL并按下回车,

查找当前的URL是否存在缓存,并比较缓存是否过期,

DNS解析URL对应的IP,

根据IP建立TCP连接(三次握手),

HTTP发起请求,服务器处理请求,返回数据,浏览器接收数据,

渲染页面,构建DOM树,

关闭TCP连接(四次挥手)。

三次握手和四次挥手

三次握手:

第一次:建立连接时,客户端发送一个SYN包到服务器请求链接

第二次:服务器收到SYN包,并确认,同时也发送一个SYN+ACK包到客户端

第三次:客户端收到服务器的SYN+ACK,向服务器发送最终的确认ACK包,发送完毕,客户端和服务端连接成功,完成三次握手

四次挥手:

第一次:浏览器发送完数据后,发送断开连接的FIN请求

第二次:服务器发送ACK包到客户端,确认浏览器的断开请求,并做好准备

第三次:服务器最终向客户端确认是否断开连接

第四次:客户端确认,断开服务器

http和https的区别

1、HTTP是超文本传输协议,信息是明文传输,HTTPS是具有安全性的SSL加密传输协议。

2、HTTPS协议需要ca申请证书,一般免费证书少,因而需要一定费用。

3、HTTP和HTTPS使用的是完全不同的连接方式,用的端口也不一样。前者是80,后者是443。

4、HTTP连接是无状态的,HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,安全性高于HTTP协议。

使用基于token的登录流程

  1. 客户端使用用户名跟密码请求登录
  2. 服务端收到请求,去验证用户名与密码
  3. 验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端
  4. 客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 里
  5. 客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
  6. 服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据

Get和post

Get是向服务器请求数据,post是向服务器提交数据

Get在数据传输时可见,post不可见

Get传输数据量小,post比较大

Get执行效率却比Post方法好

Get是form提交的默认方法。

get推荐使用在查询信息时的使用,post适用于登录,注册等带有信息的请求

web安全及防护

1.XSS攻击原理:

攻击者往Web页面里插入恶意 html标签或者javascript代码。用来窃取cookie中的用户信息

解决:对一些输入的字符进行过滤,尽量采用post表单提交的方式。

2.CSRF攻击(跨站请求伪造)

登录受信任的网站A,并在本地生成Cookie,在不登出A的情况下,携带cookie去访问危险网站B/

解决:通过验证码的方式解决

3.SQL注入攻击

就是通过吧SQL命令插入到Web表单递交或输入域名,最终达到欺骗服务器执行恶意的SQL命令。

解决:表单输入时通过正则表达式将一些特殊字符进行转换

html和XML

区别

html被称为超文本标记语言是一种描述性语言,是构成网页文档的主要语言,它是由很多的标签组成

xml 即可扩展标记语言,是Internet环境中跨平台的、依赖于内容的技术,是当前处理结构化文档信息的有力工具,满足了Web内容发布与交换的需要,适合作为各种存储与共享的通用平台。

共同点

 都可以通过DOM 来访问。都可以通过CSS来改变外观。

 html和xml 都是标记语言,都是基于文本编辑和修改的。   

xml不是要来取代html的,是对html的补充,用来与html协同工作的语言,基于上面这些优势,xml将来成为所有的数据处理和数据传输的常用工具非常可观。

浏览器的回流与重绘

回流:当页面元素的尺寸,样式,布局,dom增删发生改变时,浏览器会重新构造页面,这就是回流

重绘:当页面中的元素属性发生改变,例如颜色,背景色,而这些属性只影响元素的外观风格,不影响布局时,浏览器就会进行重绘,修改这些更改的属性

回流与重绘的区别

回流一定会引起重绘,但是重绘不一定要回流,只有在页面的布局和几何属性发生改变时,才会进行回流,例如:删除元素后引发元素位置的改变,属性导致大小,边框,内外边距改变等

如何避免重绘

1 集中改变样式,通过同样的类名进行改变

2 将频繁操作变为一次性操作,例如进行for循环添加dom结构或者修改数据

MVC与MVVM

mvc与mvvm都是一种框架模式

mvc

mvc模式,Model(数据模型)、View(视图层)、Controller(控制器)

mvc的通信是单项的,view层会从model层拿数据,因此这两个层之间,还是存在耦合的

mvvm

mvvm模式,Model(数据)、View(视图)、ViewModel(视图模型)

Model是数据模型,View是UI视图层,通过ViewModle,可以把Modle中的数据映射到View视图上,同时,在View层修改了一些数据,也会反应更新我们的Modle。简单理解就是双向数据绑定

vue就是用的mvvm框架模式,view 对应 templatevm 对应 new Vue({…})model 对应 data

常用状态码

200

请求成功

404

请求的资源没有找到

400

语义有误,无法被服务器理解

401

当前请求需要用户验证

403

服务器已经理解请求,但是拒绝执行它

500

内部服务器错误

301/302/303

(网站搬家了,跳转)重定向

面向对象与面向过程编程

面向过程分析出解决问题所需要的步骤,然后用函数把这些步骤实现,使用的时候依次调用

优点性能比面向对象高   

缺点不易维护、不易复用、不易扩展

面向对象把事务分解成为一个个对象,然后由对象之间分工与合作

优点易维护、易复用、易扩展   可以设计出低耦合,高内聚的页面

缺点性能比面向过程低 

找出数组中的重复数据

 const str = 'jshdjsihh';
 const obj = str.split('').reduce((pre, item) => {
     pre[item] ? pre[item]++ : pre[item] = 1
         return pre
     }, {}
 )
console.log(obj) // {j: 2, s: 2, h: 3, d: 1, i: 1}

1 使用数组的 reduce() 方法,为数组中的每一个元素依次执行回调函数。以下为该方法的参数

  • pre:上次调用函数的返回值(对象)
  • item:当前元素
  • index:当前元素索引
  • arr:被遍历的数组

2 判断返回值中,是否存在当前元素,如果存在,就将当前元素数量++,否则,数量为1

3 然后将最终的值返出

4 用一个变量去接受最后的返回值,这个数据,就是拥有的元素与对应数量的键值对的集合

es6的新方法

let const  箭头函数  promise  asyen  awite  class类  set  map  模板字符串   解构赋值   

数组的新方法:find  findIndex  Object.keys()  Object.value()  includes    

前端性能优化

减少http请求次数   封装公共模块  路由懒加载  节流与防抖   封装公共css模块 

合适使用let var const     keepAlive缓存不活动的组件实例     封装axios接口

将图片尽量使用网址图片,图标,精灵图    

合理使用v-if  v-show    v-for循环使用key

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值