js

180 阅读14分钟

js

ES6

ES6 新的api

新特性:Proxy | Reflect | 箭头函数
块级作用域:ES5只有全局作用域和函数作用域,块级作用域的好处是不再需要立即类的形式 可以在Class内部同时定义普通对象的属性方法,定义构造函数对象上面的方法,定义原型对象上面的方法属性 值得注意的是通过静态关键字只能在构造函数对象上面添加方法,也就是说只能定义静态的方法,不能定义静态的属性 构造函数的形式 在构造函数内部只能定义普通对象上面的方法和属性 静态方法也就是构造函数对象上面的方法,只能通过显式的追加 原型对象上面的方法和属性也需要显式的追加执行的函数表达式,循环体中的闭包不再有问题
rest参数:用于获取函数的多余参数,这样就不需要使用arguments对象了,
promise:一种异步编程的解决方案,比传统的解决方案回调函数和事件更合理强大
模块化:其模块功能主要有两个命令构成,export和import,export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能

let和const和var的区别

变量提升:
var声明的变量存在变量提升(将变量提升到当前作用域的顶部)。变量可以在声明之前调用,值为undefined。
let和const不存在变量提升。即它们所声明的变量一定要在声明后使用,否则报错。

重复申明:
var允许重复声明变量。let和const在同一作用域不允许重复声明变量。

块级作用域:
var不存在块级作用域。let和const存在块级作用域。
let 声明的变量只在 let 命令所在的代码块内有效。

是否能修改声明的变量
var和let可以修改声明的变量
const 声明一个只读变量,声明之后不允许改变。意味着,一旦声明必须初始化,否则会报错。

箭头函数

类似于匿名函数,可减少代码量,代码简洁
箭头函数内的 this 值继承自外围作用域

字符串中定义变量

es5:"string"+var
es6:`aa ${var} aa`

class类和构造函数的区别

一、function 声明了一个函数 Person,叫构造函数
这个构造函数中会有一个属性prototype,这个属性指向的就是这个构造函数对应的原型对象;原型对象中有一个属性constructor,这个属性指向的是这个构造函数。 构造函数创建出来的对象有一个__proto__属性,指向的是构造函数的原型对象
给原型对象添加属性和方法,那么 new 出来的这个对象就可以访问到该原型对象的属性和方法
二、原型链: 因为 new 出来的 stu 是一个对象,我们也可以给它直接设置属性,如果找到直接返回值,如果stu对象本身没有这个属性,那么就会向上找stu对象的__proto__属性指向的原型对象中查找,如果查找到则返回。(如果原型中也没有找到,则继续向上找原型的原型 这就是所说的原型链。
三. 与原型有关的几个方法

  1. prototype属性 prototype 存在于构造函数中 (其实任意函数中都有,只是不是构造函数的时候prototype我们不关注而已) ,他指向了这个构造函数的原型对象。
  2. constructor属性
    constructor属性存在于原型对象中,他指向了构造函数
  3. proto 属性
    用构造方法创建一个新的对象之后,这个对象中默认会有一个属性__proto__, 这个属性就指向了构造方法的原型对象。

四. class(类)
class 的实例化必须通过 new 关键字。
通过extend实现子类继承父类。

五、区别
1.类的内部所有定义的方法,都是不可枚举的(但是在es5中prototype的方法是可以进行枚举的)(属性能否被for…in查找遍历到。)
2.类的构造函数,不使用new是没法调用的,会报错。这是它跟普通构造函数的一个主要区别,后者不用new也可以执行
3.Class不存在变量提升
4.ES5的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6的继承机制完全不同,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this。

symbol

Symbol是ES6 的新增属性,代表用给定名称作为唯一标识,这种类型的值可以这样创建,let id=symbol(“id”) Symbl确保唯一,即使采用相同的名称,也会产生不同的值,我们创建一个字段,仅为知道对应symbol的人能访问,使用symbol很有用,symbol并不是100%隐藏,有内置方法Object.getOwnPropertySymbols(obj)可以获得所有的symbol。 也有一个方法Reflect.ownKeys(obj)返回对象所有的键,包括symbol。

所以并不是真正隐藏。但大多数库内置方法和语法结构遵循通用约定他们是隐藏的,

js判断类型

  1. typeof(), typeof可以识别基本类型boolean,number,undefined,string,symbol,但是不能识别null。不能识别引用数据类型,会把null、array、object统一归为object类型,但是可以识别出function。

  2. instanceof instanceof 是用来判断 A 是否为 B 的实例。 不能识别出基本的数据类型

  3. toString.call()
    toString() 是 Object 的原型方法,调用该方法,默认返回当前对象的 [[Class]] 。这是一个内部属性,其格式为 [object Xxx] ,其中 Xxx 就是对象的类型。
    对于 Object 对象,直接调用 toString() 就能返回 [object Object] 。而对于其他对象,则需要通过 call / apply 来调用才能返回正确的类型信息。

  4. constructor:
    console.log(bool.constructor === Boolean);// true
    null、undefined没有construstor方法,因此constructor不能判断undefined和null。
    但是他是不安全的,因为contructor的指向是可以被改变。

ES6-数组的新方法

  1. arr.push() 从后面添加元素,返回值为添加完后的数组的长度

  2. arr.pop() 从后面删除元素,只能是一个,返回值是删除的元素

  3. arr.shift() 从前面删除元素,只能删除一个 返回值是删除的元素

  4. arr.unshift() 从前面添加元素, 返回值是添加完后的数组的长度

  5. arr.concat() 连接两个数组 返回值为连接后的新数组

  6. arr.splice(i,n,[item1要添加到数组的新元素]) 删除从i(索引值)开始之后的那个元素。返回值是删除的元素

  7. str.split() 将字符串转化为数组

  8. arr.sort() 将数组进行排序,返回值是排好的数组,(a, b) =>a - b)从小到大

  9. arr.reverse() 将数组反转,返回值是反转后的数组

  10. arr.slice(start,end) 切去索引值start到索引值end的数组,不包含end索引的值,返回值是切出来的数组

  11. arr.forEach(callback) 遍历数组,无return callback的参数: 当前索引的值,索引,原数组 arr.forEach( (value,index,array)=>{console.log(value:${value} index:${index} array:${array})})

  12. arr.map(callback) 映射数组(遍历数组),有return 返回一个新数组 。
    callback的参数: value --当前索引的值,index --索引,array --原数组

    arr.map( (value,index,array)=>{
        value = value * 2
        console.log(`value:${value}    index:${index}     array:${array}`)
    })   
    

    arr.forEach()和arr.map()的区别

    1. arr.forEach()是和for循环一样,是代替for。arr.map()是修改数组其中的数据,并返回新的数据。
    2. arr.forEach() 没有return arr.map() 有return
  13. arr.filter(callback) 过滤数组,返回一个满足要求的数组 callback的参数: value 当前索引的值,index 索引

    let arr = [1,2,3,4,5]
    let arr1 = arr.filter( (value, index) => value<3)
    console.log(arr1)    // [1, 2]
    
  14. arr.every(callback) 依据判断条件,数组的元素是否全满足条件,若满足则返回ture,有一个不满足返回false callback的参数: value --当前索引的值,index --索引

    let arr = [1,2,3,4,5]
    let arr1 = arr.every( (value, index) =>value<3)
    console.log(arr1) // false
    
  15. arr.some() 依据判断条件,数组的元素是否有一个满足,若有一个满足则返回ture
    callback的参数:value --当前索引的值,index --索引

  16. arr.reduce(callback, initialValue) 迭代数组的所有项,累加器,数组中的每个值(从左到右)相加为一个值 previousValue 必选 --上一次调用回调返回的值,或者是提供的初始值(initialValue),
    currentValue 必选 --数组中当前被处理的数组项,
    index 可选 --当前数组项在数组中的索引值,
    array 可选 --原数组,
    initialValue: 可选 --初始值

    let arr = [0,1,2,3,4] 
    let arr1 = arr.reduce((preValue, curValue) =>   
    preValue + curValue  
    )  
    console.log(arr1)    // 10  
    
  17. arr.indexOf() 查找某个元素的索引值,若有重复的,则返回第一个,到的索引值若不存在,则返回 -1

  18. arr.lastIndexOf() 和arr.indexOf()的功能一样,不同的是从后往前查找

  19. Array.from() 将伪数组变成数组,就是只要有length的就可以转成数组。 —es6

  20. Array.of() 将一组值转换成数组,类似于声明数组 —es6

  21. arr.find(callback) 找到第一个符合条件的数组成员

  22. arr.findIndex(callback) 找到第一个符合条件的数组成员的索引值

  23. arr.fill(target, start, end) 使用给定的值,填充一个数组,ps:填充完后会改变原数组   参数: target – 待填充的元素       start – 开始填充的位置-索引       end – 终止填充的位置-索引(不包括该位置)

  24. arr.includes() 判断数中是否包含给定的值

    let arr = [1,2,3,4,5]
    let arr1 = arr.includes(2)  
    console.log(arr1)   // ture
    

ps:与indexOf()的区别: 1 indexOf()返回的是数值,而includes()返回的是布尔值 2 indexOf() 不能判断NaN,返回为-1 ,includes()则可以判断

浅拷贝和深拷贝

区别:浅复制只复制一层对象的属性,而深复制则递归复制了所有层级。

浅拷贝实现方式:

  1. for···in只循环第一层 :
function simpleCopy(obj1) {
   var obj2 = Array.isArray(obj1) ? [] : {};
   for (let i in obj1) {
   obj2[i] = obj1[i];
  }
   return obj2;
}
  1. ect.assign方法
var obj = {
    a: 1,
    b: 2
}
var obj1 = Object.assign(obj);
obj1.a = 3;
console.log(obj.a) // 3

深拷贝的实现方式

  1. 递归拷贝所有层级对象
function deepClone(obj){
var newObj= obj instanceof Array ? []:{};
for(var item in obj){
var temple= typeof obj[item] == 'object' ? deepClone(obj[item]):obj[item];
newObj[item] = temple;
}
return newObj;
}
  1. 用slice实现对数组的深拷贝

this的指向,bind,call,apply

当以函数形式调用的时候,this指向window
当以对象的方法调用的时候,this指向当前的对象
构造函数new出来的对象中的this指向当前被new出来的对象。 bind,call,apply可以改变函数this的指向
call 接收多个参数,第一个为函数上下文也就是this,后边参数为函数本身的参数。
apply接收两个参数,第一个参数为函数上下文this,第二个参数为函数参数只不过是通过一个数组的形式传入的。
bind 接收多个参数,第一个是bind返回值返回值是一个函数上下文的this,不会立即执行。

解构和聚合

...写在等号的左边或者右边 左边是聚合,右边是解构

闭包

  1. 一句话可以概括:闭包就是能够读取其他函数内部变量的函数,或者子函数在外调用,子函数所在的父函数的作用域不会被释放。

  2. 为什么要用: 一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。

匿名自执行函数:我们知道所有的变量,如果不加上var关键字,则默认的会添加到全局对象的属性上去,这样的临时变量加入全局对象有很多坏处,比如:别的函数可能误用这些变量;造成全局对象过于庞大,影响访问速度(因为变量的取值是需要从原型链上遍历的)。除了每次使用变量都是用var关键字外,我们在实际情况下经常遇到这样一种情况,即有的函数只需要执行一次,其内部变量无需维护,可以用闭包。

结果缓存:我们开发中会碰到很多情况,设想我们有一个处理过程很耗时的函数对象,每次调用都会花费很长时间,那么我们就需要将计算出来的值存储起来,当调用这个函数的时候,首先在缓存中查找,如果找不到,则进行计算,然后更新缓存并返回值,如果找到了,直接返回查找到的值即可。闭包正是可以做到这一点,因为它不会释放外部的引用,从而函数内部的值可以得以保留。

  1. 好处

①保护函数内的变量安全 ,实现封装,防止变量流入其他环境发生命名冲突

②在内存中维持一个变量,可以做缓存(但使用多了同时也是一项缺点,消耗内存)

③匿名自执行函数可以减少内存消耗

坏处

①由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE 中可能导致内存泄露。解决方法是可以在使用完变量后手动为它赋值为null;

②其次由于闭包涉及跨域访问,所以会导致性能损失,我们可以通过把跨作用域变量存储在局部变量中,然后直接访问局部变量,来减轻对执行速度的影响

什么是内存泄漏? 内存泄漏指任何对象在您不再拥有或需要它之后仍然存在。setTimeout的第一个参数使用字符串而非函数的话,会引发内存泄漏。

闭包会引起内存泄露?
先问,为什么我们要使用闭包?一种场景是我们主动把变量封闭在闭包中,以便后续调用,这种做法与把变量放到全局作用域对内存的影响是一致的。(所以,不一定)

什么情况下容易出现内存泄漏?
闭包作用域中包含着一些 DOM 节点,由于垃圾回收机制的设计问题,如果对象之间形成循环引用,那么这些对象便无法回收。

如何解决呢?
只需要把循环引用中的变量设为 null,切断引用连接即可

事件

什么是事件流

事件流描述的是从页面中接收事件的顺序,DOM2级事件流包括下面几个阶段。 事件捕获阶段 处于目标阶段 事件冒泡阶段

捕获期间就触发事件

addEventListener(event, function, useCapture):addEventListener接收3个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。最后这个布尔值参数如果是true,表示在捕获阶段调用事件处理程序;如果是false,表示在冒泡阶段调用事件处理程序,默认为false。

IE只支持事件冒泡。

事件委托

事件委托指的是,不在事件的发生地(直接dom)上设置监听函数,而是在其父元素上设置监听函数,通过事件冒泡,父元素可以监听到子元素上事件的触发,通过判断事件发生元素DOM的类型,来做出不同的响应。

js的各种位置,比如clientHeight,scrollHeight,offsetHeight ,以及scrollTop, offsetTop,clientTop的区别?

clientHeight:表示的是可视区域的高度,不包含border和滚动条
offsetHeight:表示可视区域的高度,包含了border和滚动条

scrollHeight:表示了所有区域的高度,包含了因为滚动被隐藏的部分。

clientTop:表示边框border的厚度,在未指定的情况下一般为0

scrollTop:滚动后被隐藏的高度,获取对象相对于由offsetParent属性指定的父坐标(css定位的元素或body元素)距离顶端的高度。

==和===、以及Object.is的区别

  1. ==:等同,比较运算符,两边值类型不同的时候,先进行类型转换,再比较;
  2. ===:恒等,严格比较运算符,不做类型转换,类型不同就是不等;如果两个值中至少一个是NaN,那么不相等
  3. Object.is()是ES6新增的用来比较两个值是否严格相等的方法,与===的行为基本一致。 +0不等于-0。 NaN等于自身。

ajax

AJAX 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。 异步的JavaScript,不加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容。
写一个ajax请求

new ajax = XMLHttpRequest();
ajax.onreadystatechange = function(){
    if (ajax.readyState==4 && ajax.status==200) {
        console.log("xml.responseText");
    }
}
ajax.open("get","",true);
ajax.send();

readyState
0 - (未初始化)还没有调用send()方法
1 - (载入)已调用send()方法,正在发送请求
2 - (载入完成)send()方法执行完成,已经接收到全部响应内容
3 - (交互)正在解析响应内容
4 - (完成)响应内容解析完成,可以在客户端调用了
猜测着写一个ajax类

function Ajax (){
    this.onreadystatechange = null;
    this.readyState = 0;
    this.status = "";
    this.responseText;
}
Ajax.prototype.open = "";
Ajax.prototype.send = "";

axios

const instance1 = axios.create({
 baseURL: 'http://123.207.32.32:8000',
  timeout: 5000
 })

 instance1({
 url: '/home/data',
}).then((res)=>{console.log(res)})

回调地狱

什么是回调地狱

在使用JavaScript时,为了实现某些逻辑经常会写出层层嵌套的回调函数,如果嵌套过多,会极大影响代码可读性和逻辑,这种情况也被成为回调地狱。比如说你要把一个函数 A 作为回调函数,但是该函数又接受一个函数 B 作为参数,甚至 B 还接受 C 作为参数使用,就这样层层嵌套,人称之为回调地狱,代码阅读性非常差。比如:

如何解决异步回调地狱

promise、
generator、 async/await

prosime

● 介绍一下promise,及其底层如何实现 参考回答: Promise是一个对象,保存着未来将要结束的事件,她有两个特征: 1、对象的状态不受外部影响,Promise对象代表一个异步操作,有三种状态,pending进行中,fulfilled已成功,rejected已失败,只有异步操作的结果,才可以决定当前是哪一种状态,任何其他操作都无法改变这个状态,这也就是promise名字的由来

2、一旦状态改变,就不会再变,promise对象状态改变只有两种可能,从pending改到fulfilled或者从pending改到rejected,只要这两种情况发生,状态就凝固了,不会再改变,这个时候就称为定型resolved,

Promise的基本用法,

 new Promise((resolve,reject)=>{
      //填写函数
      setTimeout(()=>{
        //成功的时候调用,填写函数的参数
        resolve('hello');
        //失败的时候调用
        reject('error message')
      },1000)
    }).then((data)=>{
      //成功填写函数体
      console.log('data')
    //第一次Pro的then的结束符
    }).catch((err)=>{
      console.log('err')
    //第一次Pro的catch的结束符
    })	

promise链式调用

  new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('hello');
      }, 2000)
    }).then((data) => {
      console.log(data);
      //then的返回结果直接调用promise的resolve函数,传数据即可,可以省略promise.resolve
      return data+'111';
    }).then((data) => {
      console.log(data);
      return (data+'222');
    }).then((data) => {
      console.log(data);
      return (data+'333');
    })

promise.all Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值,处理多个异步处理时非常有用,比如说一个页面上需要等两个或多个ajax的数据回来以后才正常显示,在此之前只显示loading图标。

Promise.all([
     new Promise((resolve, reject) => {
       setTimeout(() => {
         resolve('hello');
       }, 2000)
     }),
     new Promise((resolve, reject) => {
       setTimeout(() => {
         resolve('promise');
       }, 1000)
     })
   ]).then((datas) => {
     console.log(datas);
     console.log(datas[0]);
     console.log(datas[1]);

   })
   //此时的then只有拿到两个promise的结果才会执行,且将结果封装为数组

Promse.race
Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。

generator、

yield/next:这是控制代码执行顺序的一对好基友。 通过yield语句可以在生成器函数内部暂停代码的执行使其挂起,此时生成器函数仍然是运行并且是活跃的,其内部资源都会保留下来,只不过是处在暂停状态。 在迭代器上调用next()方法可以使代码从暂停的位置开始继续往下执行。

// 首先声明一个生成器函数
function *main() {
    console.log('starting *main()');
    yiled; // 打住,不许往下走了
    console.log('continue yield 1');
    yield; // 打住,又不许往下走了
    console.log('continue yield 2');
}
// 构造处一个迭代器it
let it = main(); 

// 调用next()启动*main生成器,表示从当前位置开始运行,停在下一个yield处
it.next(); // 输出 starting *main()

// 继续往下走
it.next(); // 输出 continue yield 1

// 再继续往下走
it.next(); // 输出 continue yield 2

async/await

async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句。

async function fn() {
    await f1()
    console.log('fn')
  }
  function f1() {
   return new Promise(resolve => {
      setTimeout(() => {
        console.log('f1')
        resolve()
      }, 1000);
    })
  }
  fn()
// f1
// fn

js的节流和防抖

防抖函数:是函数在特定的时间内不被再调用后执行

debounce(func,delay) {
      let timer = null;
      return function(...args){
        if (timer) clearTimeout(timer)
        timer = setTimeout(()=>{
          func.apply(this,args)
        },delay)
      }
    },

节流(throttle) :是指连续触发事件但是在 n 秒中只执行一次函数。

function throttle(func, wait) {
    let previous = 0;
    return function(...argus) {
        let now = Date.now();
        if (now - previous > wait) {
            func.apply(context,...argus);
            previous = now;
        }
    }
}

js监听对象属性的改变

参考回答: 我们假设这里有一个user对象, (1)在ES5中可以通过Object.defineProperty来实现已有属性的监听

Object.defineProperty(user,'name',{ set:function(key,value){ } }) 缺点:如果id不在user对象中,则不能监听id的变化

(2)在ES6中可以通过Proxy来实现

var user = new Proxy({},{ set:function(target,key,value,receiver){ } }) 这样即使有属性在user中不存在,通过user.id来定义也同样可以这样监听这个属性的变化哦~

实现vue双向绑定

Vue 2.x的Object.defineProperty版本

const data = {
  text: 'default'
};
const input = document.getElementById('input');
const span = document.getElementById('span');
// 数据劫持
Object.defineProperty(data, 'text', {
  // 数据变化 —> 修改视图
  set(newVal) {
    input.value = newVal;
    span.innerHTML = newVal;
  }
});
// 视图更改 --> 数据变化
input.addEventListener('keyup', function(e) {
  data.text = e.target.value;
});

Vue 3.x的proxy 版本

const data = {
  text: 'default'
};
const input = document.getElementById('input');
const span = document.getElementById('span');
// 数据劫持
const handler = {
  set(target, key, value) {
    target[key] = value;
    // 数据变化 —> 修改视图
    input.value = value;
    span.innerHTML = value;
    return value;
  }
};
const proxy = new Proxy(data, handler);

// 视图更改 --> 数据变化
input.addEventListener('keyup', function(e) {
  proxy.text = e.target.value;
});