前端综合面试题一(持续更新)

Map和Object的区别

es6提供了一个Map类,这是新增的一个数据结构,用起来有点像Object

区别:

Object本质上是哈希结构的键值对的集合,它只能用字符串、数字或者Symbol等简单数据类型当作键,这就带来了很大的限制。

Map类继承了Object,并对Object功能做了一些拓展,Map的键可以是任意的数据类型。

二者的区别主要有以下几点:

同名碰撞

我们知道,对象其实就是在堆开辟了一块内存,其实Map的键存的就是这块内存的地址。只要地址不一样,就是两个不同的键,这就解决了同名属性的碰撞问题,而传统的Object显然做不到这一点。

可迭代

Map实现了迭代器,可用for…of遍历,而Object不行。

长度

Map可以直接拿到长度,而Object不行。

有序性

填入Map的元素,会保持原有的顺序,而Object无法做到。

可展开

Map可以使用省略号语法展开,而Object不行。

Map和Set的区别

==Map==是一组键值对的结构,具有极快的查找速度。初始化Map需要一个二维数组,或者直接初始化一个空Map。

Map具有以下方法:

var m = new Map(); // 空Map
m.set('Adam', 67); // 添加新的key-value
m.set('Bob', 59);
m.has('Adam'); // 是否存在key 'Adam': true
m.get('Adam'); // 67
m.delete('Adam'); // 删除key 'Adam'
m.get('Adam'); // undefined

由于一个key只能对应一个value,所以多次对一个key放入value,后面的值会把前面的值冲掉

var m = new Map();
m.set('Adam', 67);
m.set('Adam', 88);
m.get('Adam'); // 88

==Set==也是一组key的集合,与Map类似。但是区别是Set不存储value,并且它的key不能重复。 创建一个Set,需要提供一个Array作为输入,或者直接创建一个空Set:

var s1 = new Set(); // 空Set
var s2 = new Set([1, 2, 3]); // 含1, 2, 3

重复元素会在Set中自动被过滤**(注:数字3和字符串’3’是不同的元素)**:

var s = new Set([1, 2, 3, 3, '3']);
s; // Set {1, 2, 3, "3"}

Set具有以下方法:

//通过add(key)方法可以添加元素到Set中,可以重复添加,但不会有效果
s.add(4);
s; // Set {1, 2, 3, 4}
s.add(4);
s; // 仍然是 Set {1, 2, 3, 4}

//通过delete(key)方法可以删除元素
var s = new Set([1, 2, 3]);
s; // Set {1, 2, 3}
s.delete(3);
s; // Set {1, 2}


add(value):添加某个值,返回Set结构本身。 
delete(value):删除某个值,返回一个布尔值,表示删除是否成功。 
has(value):返回一个布尔值,表示该值是否为Set的成员。 
clear():清除所有成员,没有返回值。

总结:

1、Map 对象是键值对集合,和 JSON 对象类似,但是 key 不仅可以是字符串还可以是其他各种类型的值包括对象都可以成为Map的键 其方法有:set get has delete

var map = new Map();
var obj = {
     
      name: '小缘', age: 14 };
map.set(obj, '小缘喵');
map.get(obj); // 小缘喵
map.has(obj); // true
map.delete(obj) ;// true
map.has(obj); // false

2、Set 对象类似于数组,且成员的值都是唯一的

数组的filter、every、flat的作用是什么

filter的作用

创建了一个新数组,新数组中是符合条件的所有元素

every的作用

查询数组中元素是否都满足要求,都满足则返回true,否则返回false

some的作用

查询数组中是否有元素满足要求,有满足则返回true并且不再往下执行,否则返回false

flat的作用

扁平化数组,例如将二维数组转换成一维数组,三维数组转成一维二维数组。接受一个参数,不传入默认是1,表示拉平的层数。

ES6新特性

1.let关键字

let关键字用来声明变量,使用 let声明的变量有几个特点:

  1. 不允许重复声明
  2. 块级作用域
  3. 不存在变量提升(不能在未定义之前使用)
  4. 不影响作用域链

2.const关键字

const 关键字用来声明常量, const声明有以下特点:

  1. 声明必须赋初始值
  2. 标识符一般为大写
  3. 不允许重复声明
  4. 值不允许修改
  5. 块级作用域

3.变量的解构赋值

ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构赋值。

//1. 数组的结构
const Hua = ['小花','刘花','赵花','宋花'];
let [xiao, liu, zhao, song] = Hua;
// 结构赋值完,可以直接使用
console.log(xiao);
console.log(liu);
console.log(zhao);
console.log(song);

//2. 对象的解构
const zhao = {
    
    
    name: '赵本山',
    age: '不详',
    xiaopin: function(){
    
    
        console.log("我可以演小品");
    }
};

let {
    
    name, age, xiaopin} = zhao;
console.log(name);
console.log(age);
console.log(xiaopin);
xiaopin();

let {
    
    xiaopin} = zhao;
xiaopin();

4.模板字符串

模板字符串(template string)是增强版的字符串,用反引号(`)标识 ,特点:

  1. 字符串中可以出现换行符
  2. 可以使用 ${xxx} 形式输出变量

5.简化对象写法

ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。

6.箭头函数

ES6 允许使用 「 箭头 」(=>)定义函数 。箭头函数的写法,也就两种,省略小括号,省略大括号。(注意:它没有自己的this)

箭头函数的注意点:

  1. 如果形参只有一个,则小括号可以省略(省略小括号)
  2. 函数体如果只有一条语句,则花括号可以省略,函数的返回值为该条语句的执行结果(省略大括号)
  3. 箭头函数 this指向声明时所在作用域下 this 的值
  4. 箭头函数不能作为构造函数实例化
  5. 不能使用 arguments

7.Symbol

  1. Symbol基本使用
    ES6 引入了一种新的原始数据类型 Symbol,表示独一无二的值。它是JavaScript 语言的第七种数据类型,是一种类似于字符串的数据类型。

Symbol使用场景: 给对象添加属性和方法

Symbol特点:

Symbol的值是唯一的,用来解决命名冲突的问题
Symbol值不能与其他数据进行运算
Symbol定义的对象属性不能使用 for…in循环遍历,但是可以使用Reflect.ownKeys来获取对象的所有键名

注: 遇到唯一性的场景时要想到 Symbol

USONB you are so niubility(JavaScript七种数据类型)

U undefined

S string symbol

O object

N null number

B boolean

8.Promise

Promise是 ES6引入的异步编程的新解决方案 。语法上 Promise是一个构造函数,用来封装异步操作并可以获取其成功或失败的结果。

Promise构造函数 : Promise (excutor) {}
Promise.prototype.then方法
Promise.prototype.catch独享守卫:

 //实例化 Promise 对象,成功是resolve,失败是reject
const p = new Promise(function(resolve, reject){
    
    
    setTimeout(function(){
    
    
        // let data = '数据库中的用户数据';
        // resolve(data);

        let err = '数据读取失败';
        reject(err);
    }, 1000);
});

//调用 promise 对象的 then 方法
p.then(function(value){
    
    
    console.log(value);
}, function(reason){
    
    
    console.error(reason);
})

// 发送ajax请求,接口地址: https://api.apiopen.top/getJoke
const p = new Promise((resolve, reject) => {
    
    
    //1. 创建对象
    const xhr = new XMLHttpRequest();
    //2. 初始化
    xhr.open("GET", "https://api.apiopen.top/getJ");
    //3. 发送
    xhr.send();
    //4. 绑定事件, 处理响应结果
    xhr.onreadystatechange = function () {
    
    
        //判断
        if (xhr.readyState === 4) {
    
    
            //判断响应状态码 200-299
            if (xhr.status >= 200 && xhr.status < 300) {
    
    
                //表示成功
                resolve(xhr.response);
            } else {
    
    
                //如果失败
                reject(xhr.status);
            }
        }
    }
})

//指定回调
p.then(function(value){
    
    
    console.log(value);
}, function(reason){
    
    
    console.error(reason);
});

常见 Promise 面试题

  1. Promise 解决了什么问题?
  2. Promise 的业界实现都有哪些?
  3. Promise 常用的 API 有哪些?
  4. 能不能手写一个符合 Promise/A+ 规范的 Promise?
  5. Promise 在事件循环中的执行过程是怎样的?
  6. Promise 有什么缺陷,可以如何解决?

你在项目中遇到什么问题?是怎么解决的

  1. 处理token

  2. 数据驱动视图不是立即的(组件异步渲染拿不到DOM元素)

    解决方案:使用updated钩子函数 this.$nextTick() setTimeOut()

    想要立即操作DOM Vue是异步更新的

    数据更新之后,因为视图更新是异步的,所以需要一个叫this.$nextTick(()=>{可以拿到更新之后的DOM})

    注意点:如果有v-if的话就不可以使用

  3. 关于后端返回数据大数字问题

    js的大数字问题:(超出js安全整数范围)

    后端返回了一些id值,然后我拿id值请求响应的数据,发现请求不到,又和后端去协调,发现我转换成数值型的id和后端给的字符串id不匹配

    一直搞不懂什么原因造成的,后来去百度,翻文档发现是因为js不能精确的表示超过2的53次方的数据好

    我又去找后端,让他把id修改,但是后端表示改不动

    只能自己找解决办法,后来找到一个插件来解决这个问题:npm i json-bigint

    ①导入 import jsonBig from 'json-bigint'

    ②转成数值:const obj =jsonBig.parse(JSON数据) obj.属性名.toString() —>值

    大数字的数据转JSON数据 使用JSON.stringify(JSONBig.parse(jsonStr)) ----->JSON数据 ----->传给后端

Promise.all和Promise.race的区别和使用

Promise.all

比如当数组里的P1,P2都执行完成时,页面才显示。

值得注意的是,返回的数组结果顺序不会改变,即使P2的返回要比P1的返回快,顺序依然是P1,P2

Promise.all成功返回成功数组,

失败返回失败数据,一但失败就不会继续往下走

        let p1 = Promise.resolve('aaa')
        let p2 = Promise.resolve('bbb')
        let p3 = Promise.reject('ccc')
        let p4 = Promise.resolve('ddd')
        Promise.all([p1, p2, p3, p4]).then(res => {
     
     
            console.log(res); //返回数组
        }).catch(err => {
     
     
            console.log(err);
        })

promise.race()

Promise.race是赛跑的意思,也就是说Promise.race([p1, p2, p3])里面的结果哪个获取的快,就返回哪个结果,不管结果本身是成功还是失败

使用场景:

我们做一个操作可能得同时需要不同的接口返回的数据,为了考虑返回数据的顺序问题,这时我们就可以使用Promise.all

有好几个服务器的好几个接口都提供同样的服务,我们不知道哪个接口更快,就可以使用Promise.race,哪个接口的数据先回来我们就用哪个接口的数据。

v-mode的本质(原理)是什么

v-model本质就是 v-bind :value=“” 和 @input事件的语法糖

当一个组件上面同时有value属性和input自定义事件的时候,且操作的是同一个变量,即可使用v-model来简化

如何修改v-model 自定义的默认的规则属性 通过model进行修改

 model: {
     
     
    prop: 'isFollowed',
    event: 'changed'
  }

知识点小记

less语法:@import ’ '; 一定要加引号

在main.js中引入import ,import 之间只允许有注释不许有其他语句

import导入规则:

一、下载的第三方包:import: ‘包名’ (去node_modules去找index.js文件)

二、手动导入:import: ‘./文件名’

推送提交:

git push -u origin master 的含义:

完整写法:git push --set-upstream origin master:master

解析:

master:master 是将本地master提交到远程仓库主分支master

–set-upstream 是对于每个最新的或成功推送的分支,添加上游(跟踪)引用 简写为-u (简单理解为记住本次提交信息,下次提交就可以直接git push)

origin: 就是添加的远程仓库地址

创建实例:

const request = axios.create ({})

==好处:==可以根据不同的路径去请求不同的服务器 ,如果写在main.js中会显得代码臃肿或者这种:axios.defaults.baseURL:会直接把路径写死

懒加载:

好处:提高首屏速度 Vue是单页面(只有一个html)

怎么提升加载速度?

如果使用的是原始导入方式,webpack在打包的时候会把所有的组件打包成1个js文件,在页面刚刷新的时候就把这个js加载完毕了

懒加载:webpack会把懒加载的组件单独打包成一个一个的js文件,在用户跳转到对应的组件的时候才会去加载

具名插槽:

新语法:必须带template标签包裹 v-slot:名字 简写为#名字

旧语法:在具体标签里 slot=“名字”

例:<i slot="名字"></i>

JSON数据:

双引号的格式:属性和值都是带双引号的

json数据不支持undefined和函数

对象和数组转json数据 通过:JSON.stringfly() 相反是:JSON.parse

深拷贝:

  • 正反序列化(缺点:不能含有undefined和函数的数据)
  • 通过递归实现深拷贝

封装请求传参要求:

  • 在data里如果是直接写如下①,在封装的函数里传参就要是②
//①
data(){
    
    
    return {
    
    
        a:'',
        b:''
    }
}
//②
export const login = (tel,code) => {
    
    
  return request({
    
    
    method: 'POST',
    url: '/v1_0/authorizations',
    data:{
    
    
      mobile:tel,
      code:code
    }
  })
}
  • 精简写法:

在data里写一个对象把数据写到对象里 如下:

  data () {
    
    
    return {
    
    
      user: {
    
    
        mobile: '',
        code: ''
      }
    }
  },
  
  
  //
  export const login = data => {
    
    
  return request({
    
    
    method: 'POST',
    url: '/v1_0/authorizations',
    data
  })
}

token值相关知识点:

第一次登录成功后服务器返回token值

为了防止刷新token值丢失,都需要把数据备份到本地存储window.localStorage.setItem('仓库名字',JSON.stringify())

注意: 本地存储只能存储字符串格式JSON.stringify() 序列化

即存储的时候序列化JSON.stringify()

取的时候反序列化JSON.parse

vuex和本地存储的区别:

vuex:①vuex是响应式数据 ②临时存储

本地存储:

sessionStorage:生命周期-浏览器关闭数据消失,刷新数据不消失,数据是当前页面共享,内存5M,存储数据是JSON数据

localStorage:生命周期-永久,相同浏览器数据共享(同一域名,必须相同主机名),内存大小20M,存储数据是JSON数据

token在你的项目内是怎么样存储的?

一、先存vuex 二、存本地存储

解构的属性名必须和response里的属性名一致{data}

密码加密(md5.js)

如果后端需要密码加密,就需要去下载这个插件

本地存储如何做时效处理?

①存token—>额外的时间戳( timer: +new Date() )

②用token的时候先拿到当前的时间戳 - 存储里面的时间戳 / 1000 / 60 (分) > 时间差

③如果超时了,跳转页面让用户重新登录,如果没有超时,正常使用token

在vue中的css使用图片路径:background: url("~@/assets/banner.png");

路由的跳转:

name和path都是路由的跳转,但是name更好维护

点击登录跳转到另一个页面使用编程式:@click="$router.push('/login')" 这是简写方法

具体写法:

this.$router.push({
    path:'/login',
    ...需要传递的参数
})

跳转完后需要回到另一个页面:$router.back() 或者:$router.go(-1) -1回退一次

差值表达式里不需要写this

什么是单页面应用(SPA ):

单页面应用(SPA)的核心思想之一,就是更新视图而不重新请求页面,简单来说,它在加载页面时,不会加载整个页面,只会更新某个指定的容器中的内容。对于大多数单页面应用,都推荐使用官方支持的vue-router。

其中网易音乐中就使用到了单页面应用

Vue 的路由实现模式:hash 模式和 history 模式

hash模式:

vue-router默认为hash模式,使用URL的hash来模拟一个完整的URL,当URL改变时,页面不会重新加载。#就是hash符号,中文名为哈希符或者锚点,在hash符号后的值称为hash值。

路由的hash模式是利用了window 可以监听onhashchange事件来实现的,也就是说hash值是用来指导浏览器动作的,对服务器没有影响,HTTP请求中也不会包括hash值,同时每一次改变hash值,都会在浏览器的访问历史中增加一个记录,使用“后退”按钮,就可以回到上一个位置。所以,hash模式是根据hash值来发生改变,根据不同的值,渲染指定DOM位置的不同数据。

history模式:

hash模式的URL中会自带#号,影响URL的美观,而history模式不会出现#号,这种模式充分利用了history.pushState()来完成URL的跳转,而且无须重新加载页面。使用history模式时,需要在路由规则配置中增加mode:‘history’,实例代码如下:

// main.js 文件
const router = new VueRouter ({
    
    
    mode:'history',
    routes: [...]
})

HTML5中history有两个新增的API,分别是history.pushState()history.replaceState() 可以对浏览器历史记录栈进行修改,以 及 popState 事件的监听到状态变更 .

它们都接收3个参数,即状态对象(state object)、标题(title)和地址(URL)。下面我们就简单介绍这3个参数的含义。

(1)状态对象(state object):一个JavaScript对象,与用pushState()方法创建的新历史记录条目关联。

(2)标题(title):FireFox浏览器目前会忽略该参数。为了安全性考虑,建议传一个空字符串。或者也可以传入一个简短的标题,标明将要进入的状态。

(3)地址(URL):新的历史记录条目的地址。

vue全家桶是指什么?有哪些东西?

Vue全家桶一般来说指的是脚手架vue-cli、路由vue-Router、状态管理模式vuex、Axios、elementUI等ui框架和打包工具webpack

命令包:

npm i -g nrm —切换镜像源

npm ls 查看

npm i 装包

或者 npm i -g cnpm

js文件里怎么访问vuex的数据?

导入vuex的仓库的store

请求拦截器:

作用:统一给有权限的接口注入请求头token

添加判断条件:

如果用户没有登录,token值为null,报错,所以要添加判断条件if(user && user.token){} === if(null && null.token) 如果为null 则退出判断条件

在config配置项里添加headers请求头

在请求拦截器添加headers后就可以把api/user.js中的headers注释掉了

无感刷新(响应拦截器)

短token:用来发请求,在请求头里携带,一般两小时过期

refresh_token:长token 用来获取新的短token 一般14天过期

能不能把短token过期时间加长 比如14天?

答:不能,因为涉及到安全问题,防止token被盗 (只有两个一起盗取才会泄漏信息)我们可以对登录密码加密—md5.js加密,(MD5加密本质是一个插件,调用他的方法就可以生成加密后的代码) 我们需要和后端配合(后端解密)

使用长token的思路:

  1. 用户发请求,后端返回401,说明token过期 这时要统一监测后端返回的401代码

  2. 监测到401报错,就拿长token refresh_token 发请求获取最新的token

  3. 自动的再发一次用户上次发的请求

  4. 添加响应拦截器

    • 主要作用:
      • 无感刷新
      • 对后端返回的数据进行统一的处理
      • 对后端数据的错误进行统一的处理

使用长token的思路(具体步骤):

  1. 当用户发请求时,如果后端返回401,就说明token过期 这时我们要统一监测后端返回的401代码

    • 我们需要添加响应拦截器,在拦截器里的报错函数中监测判断token是否过期,代码如下:

      if (error.response.status === 401) {
              
              
               console.log('token过期')
      }
      
  2. 监测到401报错,就拿长token refresh_token 发请求获取最新的token

    • 注意点:不能用request发,会死循环,因为request设置了请求拦截器,会注入老token (前提是自己封装了request)代码示例如下:

      // 用axios发请求
          const {
              
               data } = await axios({
              
              
            url: 'http://toutiao.itheima.net/v1_0/authorizations',
            method: 'PUT',
            headers: {
              
              
              Authorization: `Bearer ${
                
                store.state.token.refresh_token}` /* 怎么拿refresh_token */
            }
          })
      
  3. 接着用新token存到vuex中,存到本地存储,在js文件中不能用辅助函数 ,利用解构赋值覆盖旧token ,代码如下:

     store.commit('setUser', {
          
           ...store.state.token, token: data.data.token })
    
  4. 自动的再发一次用户上次发的请求

    return request(error.config)
    

完整代码如下:

// 添加响应拦截器
request.interceptors.response.use(function (response) {
    
    
  // 2xx 范围内的状态码都会触发该函数。
  // 对响应数据做点什么
  return response
}, async function (error) {
    
    
  /* //监测token过期 */
  if (error.response.status === 401) {
    
    
    // console.log('token过期')
    // 发请求,刷新token
    // 注意:不能用request发,会死循环,因为request设置了请求拦截器,会注入老token
    // 用axios发请求
    const {
    
     data } = await axios({
    
    
      url: 'http://toutiao.itheima.net/v1_0/authorizations',
      method: 'PUT',
      headers: {
    
    
        Authorization: `Bearer ${
      
      store.state.token.refresh_token}` /* //怎么拿refresh_token */
      }
    })
    // 新token存到vuex中,存到本地存储,在js文件中不能用辅助函数
    // 利用解构赋值覆盖旧token
    store.commit('setUser', {
    
     ...store.state.token, token: data.data.token })
    // 再发一次上次发送过得请求(error里有上一次发送的地址和错误的token)
    return request(error.config)
  }
  // 超出 2xx 范围的状态码都会触发该函数。
  // 对响应错误做点什么
  return Promise.reject(error)
})

在组件内修改全局的外来组件样式,需要加/deep/(必须是less语法)(在scss语法中用::v-deep)

JSON.parse()是反序列化,是将json格式的字符串转化为对象 (json格式字符串必须是双引号,其属性和值都是双引号包裹) 报错的一种比如:JSON.parse('sfdsfsa') 这种用单引号包裹的就是错误的json格式 就会报错

为什么列表滚动会相互影响?

因为他们并不是在自己内部滚动,而是整个body页面在滚动。

记住列表的滚动位置:
方法:
让每个标签内容文章列表产生自己的滚动容器,这样就不会相互影响了

如何给文章列表产生自己的滚动容器:
设置固定高度,height:xxx ; (如果没有效果,可以考虑用视口单位vh,vw 他们不受父级的影响)

.article-list {
    
    
height: xxx ;
overflow-y:auto;  //垂直溢出滚动
}

使用解构的方式添加数据:

this.list = [...this.list, ...data.data.results] 把新的数据追加到尾部

给标签内容添加高度小技巧

给局部内容设置高度,在任何设备下都可以适应

height: calc(100vh - 274px);
  overflow-y: auto;

过滤器在全局和局部怎么去使用?

配置好全局的dayjs后,要在全局使用的话一定要导出

下拉刷新数据会遇到留白的情况:

这是因为数据不够,数据撑不开内容得高度

解决办法:需要把数据填满内容得高度

如何处理相对时间

Day.js (opens new window)是一个轻量的处理时间和日期的 JavaScript 库,和 Moment.js (opens new window)的 API 设计保持完全一样

  1. 安装
npm i dayjs
  1. 创建 utils/dayjs.js
import Vue from 'vue'
import dayjs from 'dayjs'

// 加载中文语言包
import 'dayjs/locale/zh-cn'

import relativeTime from 'dayjs/plugin/relativeTime'

// 配置使用处理相对时间的插件
dayjs.extend(relativeTime)

// 配置使用中文语言包
dayjs.locale('zh-cn')

// 全局过滤器:处理相对时间
Vue.filter('relativeTime', value => {
     
     
  return dayjs().to(dayjs(value))
})
  1. 在 main.js 中加载初始化
import './utils/dayjs'
  1. 使用
<span>{
     
     {
     
      article.pubdate | relativeTime }}</span>

拓展如何在组件局部使用

首先要把配置好的dayjs默认导出

export default dayjs

接着要在组件中导入:

// 组件内使用过滤时间的插件
import dayjs from '@/utils/dayjs'

然后再methods里定义处理相对时间的函数:

methods: {
     
     
    relativeTime (value) {
     
      // value就是管道符前面的数据
      return dayjs().to(dayjs(value)) //to为多少年前
    }
  }

最后在需要的地方使用相对时间函数:

<span>{
     
     {
     
      article.pubdate | relativeTime }}</span>

计算属性:

计算属性会观测内部依赖数据的变化,如果依赖的数据发生变化,则计算属性会重新执行

用sync修饰符修改父组件的数据

子组件:

用sync修饰符修改父组件的数据

this.$emit('update:active', index)

父组件:

:actives.sync="active"

注意:update后面更新的是 属性名

this.$parent:

在当前组价可以访问父组件的所有数据和方法

拓展知识点:

  1. .sync修饰符: 做父传子数据的时候, 能够实现子组件修改父组件数据功能
    父组件: :属性名.sync = “基本数据类型” 子组件: ①props: {属性名} 接收 ②修改父组件对应数据 this.$emit('update:属性名', 要修改的值)注意不能使用sync操作复杂数据类型,比如:数组、对象)
  2. this.$parent 在当前组件可以访问父组件的所有数据包括方法, 可以在子组件实现修改父组件数据的功能, 还能调用父组件方法
  3. this.$children 是一个数组, 可以拿到当前组件的所有亲儿子组件
  4. this.$refs ①可以拿DOM元素 ②也可以拿组件

搜索框需要做哪些优化?

  1. 防抖:持续输出的事件,只执行一次(最后一次),核心定时器,每一次事件触发1清空上一次的定时器
  2. 搜索历史优化:搜索的内容保存到本地存储,用户每一次搜索的时候,先从本地存储有没有搜过的关键字,如果有直接从本地存取,如果没有,就发请求拿数据渲染

axios的post和get带参数的请求方法是不大一样的。

GET方式带参数的请求是 params

POST方式带参数的请求是data

PATCH也是data

PUT也是data

适配:

自适应、响应式

自适应技术:

一、通过媒体查询切换html的font-size + rem

缺点:要写很多媒体查询,繁琐

二、手淘flexible.js + rem flexible.js通过js监听(通过onresize事件)屏幕尺寸变化,**核心算法:**html的font-size=屏幕宽度/10

三、vw/vh 自适应屏幕宽度高度 视口宽度/10 (视口单位)

使用 postcss-pxtorem (opens new window)将 px 转为 rem:不会转行内样式

rem计算原则:(元素像素)px / 根字号=rem

rem适配原理(重要)

目标:实现在不同宽度的设备中,网页元素尺寸等比缩放效果

rem单位尺寸

  1. 根据视口宽度,设置不同的HTML标签字号
  2. 书写代码,尺寸是rem单位
    • 确定设计稿对应的设备的HTML标签(基准根字号)字号
    • 查看设计稿宽度 → 确定参考设备宽度(视口宽度) → 确定基准根字号(1/10视口宽度)
    • rem单位的尺寸 (N为rem)
      • N * 37.5 = 68 → N = 68 / 37.5
      • rem单位的尺寸 = px单位数值 / 基准根字号
    • rem单位的尺寸 = px单位数值 / 基准根字号 (结果取3位小数)

(设计稿为750px 把一行分为10份,1份为75 ,一般来说750的设计稿要先除2为375 在除10,一份也就是37.5)

Vue 原理

1.组件化基础=>(MVVM模型)

传统组件,知识静态渲染,更新依赖于操作DOM。

Vue的核心理念是数据驱动的理念,所谓的数据驱动的理念:当数据发生变化的时候,用户界面也会发生相应的变化,开发者并不需要手动的去修改dom。

优点:

    不需要在代码中去频繁的操作dom了,这样提高了开发的效率,同时也避免了在操作Dom的时候出现的错误。

Vue.js的数据驱动是通过MVVM这种框架来实现的,MVVM 框架主要包含三部分:Model, View, ViewMode

数据驱动视图 - Vue MVVM

MVVM是Model-View-ViewModel缩写,也就是把MVC中的Controller演变成ViewModel。Model代表数据模型,View代表UI组件,ViewModel是View和Model层的桥梁,数据会绑定到ViewModel层并自动将数据渲染到页面中,视图变化的时候通知viewModel层更新数据。

2. Vue的响应式原理

核心实现类:

Observer : 它的作用是给对象的属性添加 getter 和 setter,用于依赖收集和派发更新。
Dep : 用于收集当前响应式对象的依赖关系,每个响应式对象包括子对象都拥有一个 Dep 实例(里面 subs 是 Watcher 实例数组),当数据有变更时,会通过 dep.notify()通知各个 watcher。
Watcher : 观察者对象 , 实例分为渲染 watcher (render watcher),计算属性 watcher (computed watcher),侦听器 watcher(user watcher)三种
Watcher 和 Dep 的关系
watcher 中实例化了 dep 并向 dep.subs 中添加了订阅者,dep 通过 notify 遍历了 dep.subs 通知每个 watcher 更新。

依赖收集(getter)
initState 时,对 computed 属性初始化时,触发 computed watcher 依赖收集
initState 时,对侦听属性初始化时,触发 user watcher 依赖收集
render()的过程,触发 render watcher 依赖收集
re-render 时,vm.render()再次执行,会移除所有 subs 中的 watcer 的订阅,重新赋值。
派发更新(setter)
组件中对响应的数据进行了修改,触发 setter 的逻辑
调用 dep.notify()
遍历所有的 subs(Watcher 实例),调用每一个 watcher 的 update 方法。

原理:

    当创建 Vue 实例时,vue 会遍历 data 选项的属性,利用 Object.defineProperty 为属性添加 getter 和 setter 对数据的读取进行劫持(getter 用来依赖收集,setter 用来派发更新),并且在内部追踪依赖,在属性被访问和修改时通知变化。

    每个组件实例会有相应的 watcher 实例,会在组件渲染的过程中记录依赖的所有数据属性(进行依赖收集,还有 computed watcher,user watcher 实例),之后依赖项被改动时,setter 方法会通知依赖与此 data 的 watcher 实例重新计算(派发更新),从而使它关联的组件重新渲染。

Object.defineProperty 实现响应式

监听对象,监听数组
复杂对象,深度监听

const obj = {
    
    };
const data = {
    
    };
const name = 'zhangsan';
Object.defineProperty(data, 'name', {
    
    
    get: function () {
    
    
        console.log('get');
        return name;
    },
    set: function (newVal) {
    
    
        console.log('set');
        obj.name = newVal;
    }
})
console.log(data.name);
data.name = 'lisi';
console.log(obj.name);  

Object.defineProperty 的缺点

深度监听需要递归到底,一次性计算量大
无法监听新增属性、删除属性(要使用 Vue.set Vue.delete)
无法原生监听数组,需要特殊处理【对数组的方法重写,[‘push’, ‘pop’, ‘shift’, ‘unshift’, ‘splice’,‘sort’,‘reverse’] 这几个方法更改数组才会响应式变化,直接更改索引不会响应式改变】

总结:

Vue双向数据绑定的原理:
vue.js 采用数据劫持结合发布-订阅模式,通过 Object.defineproperty 来劫持各个属性的 setter,getter,在数据变动时发布消息给订阅者,触发响应的监听回调。    
    

Vue双向数据绑定的原理(重要)

我们都知道 Vue 是数据双向绑定的框架,双向绑定由三个重要部分构成

  • 数据层(Model):应用的数据及业务逻辑
  • 视图层(View):应用的展示效果,各类UI组件
  • 业务逻辑层(ViewModel):框架封装的核心,它负责将数据与视图关联起来

而上面的这个分层的架构方案,可以用一个专业术语进行称呼:MVVM这里的控制层的核心功能便是 “数据双向绑定” 。自然,我们只需弄懂它是什么,便可以进一步了解数据绑定的原理

理解ViewModel

它的主要职责就是:

  • 数据变化后更新视图
  • 视图变化后更新数据

当然,它还有两个主要部分组成

  • 监听器(Observer):对所有数据的属性进行监听
  • 解析器(Compiler):对每个元素节点的指令进行扫描跟解析,根据指令模板替换数据,以及绑定相应的更新函数

Vue实现双向数据绑定是采用数据劫持和发布者-订阅者模式。数据劫持是利用ES5的Object.defineProperty(obj,key,val)方法来劫持data选项中每个属性的getter和setter,在数据变动时发布消息给订阅者,从而触发相应的回调来更新视图

总之就是,在创建Vue实例的时候给传入的data进行数据劫持,同时视图编译的时候,对于使用到data中数据的地方进行创建Watcher对象,然后在数据劫持的getter中添加到发布者对象中,当劫持到数据发生变化的时候,就通过发布订阅模式以回调函数的方式通知所有观察者操作DOM进行更新,从而实现数据的双向绑定。

/**
 * 对Object.defineProperty()进行封装
 */
function defineReactive(obj, key, value) {
     
     
    //递归 - 对象的属性仍是对象
    observe(value);
    //变化侦测
    Object.defineProperty(obj, key, {
     
     
        get() {
     
     
            return value;
        },
        set(newVal) {
     
     
            if (newVal !== value) {
     
     
                updateView();
                value = newVal;
                observe(newVal)
            }
        }
    })
}

/**
 * 对一个对象所有属性的变化侦测
 */
function observe(target) {
     
     
    //非对象,直接返回
    if (typeof target !== 'object') {
     
     
        return target;
    }
    //将每个属性转换为getter和setter形式
    for (let key in target) {
     
     
        defineReactive(target, key, target[key])
    }
}
//模拟更新视图的方法
function updateView() {
     
     
    console.log("更新视图");
}

通过直接调用observe侦测对象属性的变化

存在的问题

  1. 性能较差
  2. 对象上新增属性无法侦测
  3. 改变数组的length属性无法被侦测

当创建 Vue 实例时,vue 会遍历 data 选项的属性,利用 Object.defineProperty 为属性添加 getter 和 setter 对数据的读取进行劫持(getter 用来依赖收集,setter 用来派发更新),并且在内部追踪依赖,在属性被访问和修改时通知变化。

每个组件实例会有相应的 watcher 实例,会在组件渲染的过程中记录依赖的所有数据属性(进行依赖收集,还有 computed watcher,user watcher 实例),之后依赖项被改动时,setter 方法会通知依赖与此 data 的 watcher 实例重新计算(派发更新),从而使它关联的组件重新渲染。

记录鼠标滚动的位置

一级路由跳转会销毁重建,需要给一级路由App.vue 包裹上缓存组件<keep-alive> 会触发两个钩子函数,分别为activated deactivated

可以通过组件内的导航守卫beforeRouteLeave

// 离开这个组件的路由导航守卫
  beforeRouteLeave (to, from, next) {
    
    
    // 跳走的时候获取组件的滚动位置
    this.top = this.$refs.article[this.active].$el.scrollTop
    next()
  },
  // 缓存组件激活触发的钩子函数
  activated () {
    
    
    if (this.$refs.article) {
    
    
      this.$refs.article[this.active].$el.scrollTop = this.top
    }
  }

浏览器的事件循环(JS的执行机制)

浏览器中的事件循环会涉及到一个任务队列(task queue)的概念。所有的异步任务执行完毕后都会将对应的回调函数放到任务队列中,并进行排队依次处理。而任务队列又分为宏任务队列微任务队列,并且只有在主线程的调用栈被清空的时候,才会执行任务队列中的任务,这也就是所谓的JavaScript的运行机制


浏览器宏任务主要有setTimeout()setInterval()
浏览器微任务主要有Promise.then()requestAnimationFrame()、process.nextTick

并且微任务优先级要高于宏任务。当主线程调用栈空闲时,就会检测任务队列中是否有任务要执行,首先看一下微任务队列中是否有任务要执行,如果有则执行微任务队列中的任务,直到微任务队列清空为止,然后接着开始执行宏任务队列中的任务,宏任务清空后,又接着检测微任务队列中是否有任务,如此往复下去形成事件循环

事件循环第一种

  • 首先判断JS是同步还是异步,同步就进入主线程,异步就进入event table(宿主环境中)
  • 异步任务在event table中注册函数,当满足触发条件后,被推入event queue(任务队列)
  • 同步任务进入主线程后一直执行,直到主线程空闲时,才会去event queue中查看是否有可执行的异步任务,如果有就推入主线程中

以上三步循环执行,这就是event loop

事件循环第二种

 setTimeout(function(){
     
     
     console.log('A')
 });
 
 new Promise(function(resolve){
     
     
     console.log('B');
     for(var i = 0; i < 10000; i++){
     
     
         i == 99 && resolve();
     }
 }).then(function(){
     
     
     console.log('C')
 });
 
 console.log('D');

上面代码执行结果为:B D C A

解析一下原因为什么执行循序是这样的?

这是因为异步任务又分为宏任务和微任务

  • macro-task(宏任务):包括整体代码script,setTimeout,setInterval
  • micro-task(微任务):Promise.then,process.nextTick
  • 按照这种分类方式:JS的执行机制是 :
  • 执行一个宏任务,过程中如果遇到微任务,就将其放到微任务的【事件队列】里
  • 当前宏任务执行完成后,会查看微任务的【事件队列】,并将里面全部的微任务依次执行完

重复以上2步骤,结合event loop(1) event loop(2) ,就是更为准确的JS执行机制了。

具体执行过程分析为:

首先执行script下的宏任务,遇到setTimeout,将其放到宏任务的【队列】里

遇到 new Promise直接执行,打印"B"

遇到then方法,是微任务,将其放到微任务的【队列里】

遇到同步任务console.log('D') 直接打印 "D"

本轮宏任务执行完毕,查看本轮的微任务,发现有一个then方法里的函数, 打印"C"

到此,本轮的event loop 全部完成。


下一轮的循环里,先执行一个宏任务,发现宏任务的【队列】里有一个 setTimeout里的函数,执行打印"A"

所以执行结果为:B D C A

看一个复杂的例子:

console.log('1');
setTimeout(function() {
    
    
 
 
console.log('2');
process.nextTick(function() {
    
    
 
console.log('3');
 
 
})
new Promise(function(resolve) {
    
    
 
console.log('4');
resolve();
}).then(function() {
    
    
 
console.log('5')
})
})
//1 2 4 3 5 

执行过程:

1、宏任务同步代码 console.log(‘1’)

2、setTimeout,加入宏任务 Event Queue,没有发现微任务,第一轮事件循环走完

3、第二轮事件循环开始,先执行宏任务,从宏任务 Event Queue 中读取出 setTimeout 的回 调函数

4、同步代码 console.log(‘2’),发现 process.nextTick,加入微任务 Event Queue

5、new Promise,同步执行 console.log(‘4’),发现 then,加入微任务 Event Queue

6、宏任务执行完毕,接下来执行微任务,先执行 process.nextTick,然后执行 Promise.then

7、微任务执行完毕,第二轮事件循环走完,没有发现宏任务,事件循环结束

总结

事件循环先执行宏任务,其中同步任务立即执行,异步任务加载到对应的 Event Queue 中, 微任务也加载到对应的微任务的 Event Queue 中,所有的同步微任务执行完之后,如果发现微任 务的 Event Queue 中有未执行完的任务,先执行他们这样算是完成了一轮事件循环。接下来查看 宏任务的队列中是否有异步代码,有的话执行第二轮的事件循环,以此类推。

SQL基本语法

-- SELECT * FROM my_db_01.users;
-- 1.查询整个表所有的数据
-- select * from users
-- 2.查询具体的字段名字
-- select username,password from users
-- 3.查询id是1的这个字段 查询的条件写在where后面
-- select * from users where id=1
-- 4.查询users表里username这个字段 模糊查询 Z%表示以z开头的 %Z写在后面后面表示以z结尾的
-- select * from users where username like 'Z%'
-- 5.条件查询
-- select username,password from users where id>1 and status=0
-- 6.排序 desc(降序) asc(升序)
-- select * from users order by id desc
-- select * from users order by id asc
-- 7.添加
-- insert into users(username,password,status) values('zl',111,1)
-- 8.删除
-- delete from users where id=3
-- 9.修改(更新)update 表明 set 字段属性(多个字段用英文逗号隔开) where (修改的字段对应的id) 如果没有where 将修改整个表
-- update users set username='zs',password=555 where id=1
-- 10.函数count(*)
-- select count(*) from users where status=0

watch、methods 和 computed 的区别?

从作用机制上:

  1. methods,watch 和 computed 都是以函数为基础的,但各自却都不同
  2. watch 和 computed 都是以 Vue 的依赖追踪机制为基础的,当某一个数据发生变化的时候,所有依赖这个数据的“相关”数据“自动”发生变化,也就是自动调用相关的函数去实现数据的变动
  3. 对 methods:methods 里面是用来定义函数的,它需要手动调用才能执行。而不像 watch 和 computed 那样,“自动执行”预先定义的函数,相比于 watch / computed,methods 不处理数据逻辑关系,只提供可调用的函数

从性质上:

  1. methods 里面定义的是函数,仍然需要去调用它。
  2. computed计算属性,事实上和 data 对象里的数据属性是同一类的(使用上)。
  3. **watch:**类似于监听机制+事件机制

watch 和 computed 区别

  1. 功能上:computed是计算属性,watch是监听一个值的变化,然后执行对应的回调。
  2. 是否调用缓存:computed中的函数所依赖的属性没有发生变化,那么调用当前的函数的时候会从缓存中读取,而watch在每次监听的值发生变化的时候都会执行回调。
  3. 是否调用return:computed中的函数必须要用return返回,watch中的函数不是必须要用return
  4. watch擅长处理的场景:一个数据影响多个数据 -------搜索框。
  5. computed擅长处理的场景:一个数据受多个数据影响 – 使用场景:当一个值受多个属性影响的时候--------购物车商品结算

**watch:**属性监听

  1. watch中的函数名称必须要和data中的属性名一致,因为watch是依赖data中的属性,当data中的属性发生改变的时候,watch中的函数就会执行。
  2. watch中的函数有两个参数,前者是newVal,后者是oldVal。
  3. watch中的函数是不需要调用的。
  4. watch只会监听数据的值是否发生改变,而不会去监听数据的地址是否发生改变。也就是说,watch想要监听引用类型数据的变化,需要进行深度监听。“obj.name”(){}------如果obj的属性太多,这种方法的效率很低,obj:{handler(newVal){},deep:true}------用handler+deep的方式进行深度监听。
  5. 特殊情况下,watch无法监听到数组的变化,特殊情况就是说更改数组中的数据时,数组已经更改,但是视图没有更新。更改数组必须要用splice()或者 s e t 。 t h i s . a r r . s p l i c e ( 0 , 1 , 100 ) − − − − − 修 改 a r r 中 第 0 项 开 始 的 1 个 数 据 为 100 , t h i s . set。this.arr.splice(0,1,100)-----修改arr中第0项开始的1个数据为100,this. setthis.arr.splice(0,1,100)arr01100this.set(this.arr,0,100)-----修改arr第0项值为100。
  6. immediate:true 页面首次加载的时候做一次监听。
  7. 一个数据影响多个数据 — 应用场景:搜索框、表单输入、异步更新、动画
  8. 如果监听的是复杂数据类型内需要开启深度监听deep:true

computed:计算属性

  • 据依赖关系进行缓存的计算,并且只在需要的时候进行更新。
  • 一个计算属性里面可以完成各种复杂的逻辑,最终返回一个结果;计算属性可以依赖多个vue实例的数据,只要其中一个任何一个数据发生变化,计算属性就会重新执行,视图也会更新。除此之外,计算属性还可以依赖其他计算属性和其他实例的数据
  • 一个数据受多个数据影响 ---- 购物车结算 受到单价 数量 还有是否被选中的影响

HTTP 与 HTTPS 的区别(必会)

很多人总以为http就是https。其实http和https有着很大的区别的,下面我们来看一下https和http有什么区别?

一:先说一下什么是https和http

https是http的安全版本,也叫超文本安全传输,具有加密传输协议的通道,并且SSL提供了安全加密基础,作用是用于http的传输。

http是一种普通的超文本传输协议,在互联网上,所有的文件都要遵守这个HTTP协议,作用就是实现客户端和服务器的相互请求。

二:然后https和http有什么区别

https和http的区别:

**1、**https的端口是443,而http的端口是80,并且两者的连接方式不同 http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议 ;

**2、**HTTP协议以明文方式发送内容,不提供任何方式的数据加密,举个例子:如果黑客截取了Web浏览器和网站服务器之间的传输报文,就可以直接读取其中的信息,因此,HTTP协议不适合传输一些敏感信息,比如:银行卡号卡号、密码等支付信息。 ,为了解决HTTP协议的这一缺陷 HTTPS在HTTP的基础上加入了SSL协议,为浏览器和服务器之间的通信进行加密。 HTTPS协议之所以安全,是因为HTTPS协议对传输的数据进行加密,而加密过程是由非对称加密实现的。

**3、**https是需要到CA申请证书的,而http不需要。

https和http有什么区别

SSL证书是什么?(拓展)

因为HTTP协议以明文方式发送内容,使用HTTP协议传输隐私信息非常不安全,为了保证这些隐私数据能加密传输,于是网景公司设计了SSL协议 ,用于对HTTP协议传输的数据进行加密,从而就诞生了HTTPS。 其中S代表安全。security

SSL证书是数字证书的一种,类似于我们驾驶证的电子副本。因为配置在服务器上,也称为SSL服务器证书。 作用就是用于保护网络交易、数据传输和登录。所以SSL逐渐已成为一种规范(就如ECMAscript是js语法的规范)

nextTick的原理

由于Vue DOM更新是异步执行的,即修改数据时,视图不会立即更新,而是会监听数据变化,并缓存在同一事件循环中,等同一数据循环中的所有数据变化完成之后,再统一进行视图更新。为了确保得到更新后的DOM,所以设置了 Vue.nextTick()方法。

vue中有一个较为特殊的API,nextTick。根据官方文档的解释,它可以在DOM更新完毕之后执行一个回调

尽管MVVM框架并不推荐访问DOM,但有时候确实会有这样的需求,尤其是和第三方插件进行配合的时候,免不了要进行DOM操作。而nextTick就提供了一个桥梁,确保我们操作的是更新后的DOM。

应用场景:

  • 在Vue生命周期的created()钩子函数进行的DOM操作一定要放在Vue.nextTick()的回调函数中
    • **原因:**是created()钩子函数执行时DOM其实并未进行渲染。
  • 在数据变化后要执行的某个操作,而这个操作需要使用随数据改变而改变的DOM结构的时候,这个操作应该放在Vue.nextTick()的回调函数中。
    • 原因:Vue异步执行DOM更新,只要观察到数据变化,Vue将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变,如果同一个watcher被多次触发,只会被推入到队列中一次。

总结

以上就是vue的nextTick方法的实现原理了,总结一下就是:

  1. vue用异步队列的方式来控制DOM更新和nextTick回调先后执行
  2. microtask因为其高优先级特性,能确保队列中的微任务在一次事件循环前被执行完毕
  3. 因为兼容性问题,vue不得不做了microtask向macrotask的降级方案

模块拖拽的原理

拖拽功能主要是用在让用户做一些自定义的动作,比如拖动排序弹出框拖动移动等等,效果还是蛮不错的。下面就来讲一下拖拽的原理:

一、拖拽的流程动作

①鼠标按下
②鼠标移动
③鼠标松开

这里要注意:移动事件要写在按下事件的里面

二、拖拽流程中对应的JS事件

①鼠标按下会触发onmousedown事件

②鼠标移动会触发onmousemove事件

③鼠标松开会触发onmouseup事件

三、实现的原理讲解

拖拽其实是通过获取鼠标移动的距离来实现的,即计算移动前的位置的坐标(x,y)与移动中的位置的坐标(x,y)差值。
当鼠标按下或鼠标移动时,都可以获取到当前鼠标的位置,即移动前的位置与移动中的位置。

还有一点,被拖拽的元素的样式要设置成绝对或相对位置才有效果。

总结:

①在页面中拖拽的原理: 鼠标按下并且移动, 之后松开鼠标

触发事件是鼠标按下 mousedown, 鼠标移动mousemove 鼠标松开 mouseup

③拖拽过程: 鼠标移动过程中,获得最新的值赋值给模态框的left和top值, 这样模态框可以跟着鼠标走了

鼠标在页面的坐标 减去 鼠标在盒子内的坐标, 才是模态框真正的位置。

⑤鼠标按下,我们要得到鼠标在盒子的坐标。

⑥鼠标移动,就让模态框的坐标 设置为 : 鼠标在页面的坐标 减去盒子坐标即可,注意移动事件写到按下事件里面

⑦鼠标松开,就停止拖拽,就是可以让鼠标移动事件解除

核心代码

 		// 鼠标按下获取鼠标在盒子内的坐标
        obj.addEventListener('mousedown', function(e) {
     
     
          //获取鼠标在盒子中的坐标=鼠标在页面中的坐标减去盒子在页面中的坐标  
            var x = e.pageX - box.offsetLeft
            var y = e.pageY - box.offsetTop
                // 鼠标移动的时候,把鼠标在页面中的坐标,减去 鼠标在盒子内的坐标就是模态框的left和top值
                // 在全局页面中拖拽时要用document,如果用了obj,事件会来不及触发,导致鼠标脱离
            document.addEventListener('mousemove', move)
		function move(e) {
     
     
            box.style.left = e.pageX - x + 'px'
            box.style.top = e.pageY - y + 'px'
        }
        // 鼠标弹起,就让鼠标移动事件移除
        document.addEventListener('mouseup', function() {
     
     
            document.removeEventListener('mousemove', move)
        })

内存泄漏

什么是内存泄漏

应用程序不再需要占用内存的时候,由于某些原因,内存没有被操作系统或可用内存池回收,就叫做内存泄漏

哪些操作会造成内存泄漏

1、全局变量引起的内存泄漏
2、闭包引起的内存泄露
3、被遗忘的定时器或回调
4、dom清空或删除时,事件未清除导致的内存泄漏
5、反复重写同一个属性会造成内存大量占用(但关闭IE后内存会被释放)

6、setTimeout 的第一个参数使用字符串而非函数的话,会引发内存泄漏

JS内存泄漏的解决方式

1、注意程序逻辑,避免“死循环”之类的
2、减少不必要的全局变量,或者生命周期较长的对象,及时对无用的数据进行垃圾回收
3、避免创建过多的对象 原则:不用了的东西要及时归还

new操作符具体干了什么

1、创建一个空对象
2、this指向这个新对象
3、执行构造函数里的代码,为新对象添加属性和方法
4、返回新对象:判断返回值,返回对象就用该对象,没有的话就创建一个对象

堆和栈的区别

  • 栈区(stack):由编译器自动分配和释放,存放函数的参数值、局部变量的值等。其操作方式类似于数据结构中的栈。
  • 堆区(heap):一般由程序员分配和释放,若程序员不释放,程序结束时可能由操作系统回收。它与数据机构中的堆是两回事,分配方式类似于链表。

他们的区别

1申请方式

  • 栈:由系统自动分配。例如在声明函数的一个局部变量int b,系统自动在栈中为b开辟空间。
  • 堆:需要程序员自己申请,并指明大小,在C中用malloc函数;在C++中用new运算符。

2申请后系统的响应

  • 栈:只要栈的剩余空间大于所申请的空间系统将为程序提供内存,否则将报异常提示栈溢出。
  • 堆:操作系统有一个记录空间内存地址的链表,当系统收到程序的申请时,会遍历链表,寻找第一个空间大于所申请空间的堆节点,然后将节点从内存空闲节点链表中删除,并将该节点的空间分配给程序

总结:

(1)heap是堆,stack是栈;

(2)栈的空间由操作系统自动分配/释放,堆上的空间手动分配/释放;

(3)栈空间有限,堆是很大的自由内存区;

(4)C中的malloc函数分配的内存空间即在堆上,C++中对应的是new操作符。 程序在编译对变量和函数分配内存都在栈上进行,且内存运行过程中函数调用时参数的传递在栈上进行。

堆和栈的概念存在于数据结构中和操作系统内存中。

在数据结构中,栈中数据的存取方式为先进后出。而堆是一个优先队列,是按优先级来进行排序的,优先级可以按照大小来规定。完全二叉树是堆的一种实现方式。

在操作系统中,内存被分为栈区和堆区。

栈区内存由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

堆区内存一般由程序员分配释放,若程序员不释放,程序结束时可能由垃圾回收机制回收。
复制代码

如何上传文件

​ 基于服务端的裁切使用 getData 方法获取裁切参数

​ console.log(this.cropper.getData())

​ 纯客户端的裁切使用 getCroppedCanvas 获取裁切的文件对象

  • 首先看接口文档

  • 看请求头:
    如果接口要求 Content-Type 是 application/json
    则传递普通 JavaScript 对象
    axios会自动转为json对象
    
    如果接口要求 Content-Type 是 multipart/form-data
    则你必须传递 FormData 对象
    
    const formData = new FormData()
            formData.append('photo', blob)
    
    // 点击完成时间
    onConfirm () {
    
    
      // 基于服务端的裁切使用 getData 方法获取裁切参数
      // console.log(this.cropper.getData())

      // 纯客户端的裁切使用 getCroppedCanvas 获取裁切的文件对象
      this.cropper.getCroppedCanvas().toBlob(async blob => {
    
    
        // console.log(blob) // 裁剪后的结果信息
        // 弹出loading页面
        this.$toast.loading({
    
    
          message: '保存中...',
          forbidClick: true, // 禁止背景点击
          duration: 0 // 持续展示
        })
        try {
    
    
          // 1.创建formData对象
          const obj = new FormData()
          // 2.给对象obj添加key和value
          obj.append('photo', blob)
          // 3.发请求
          const {
    
     data } = await updateUserPhoto(obj)
          // 4.更新父组件对象
          this.$emit('update:photo', data.data.photo)
          // 5.关闭弹层
          this.$emit('update:close', false)
          this.$toast.success('修改成功')
        } catch (error) {
    
    
          this.$toast.fail('修改失败')
        }
      })
    }

什么是BFC

块格式化上下文(Block Formatting Context,BFC) 是 Web 页面的可视 CSS 渲染的一部分,是块级盒子的布局过程发生的区域,也是浮动元素与其他元素交互的区域。

下列方式会创建块格式化上下文:

  • 根元素(<html>
  • 浮动元素(float 值不为 none
  • 绝对定位元素(position 值为 absolutefixed
  • 行内块元素(display 值为 inline-block
  • 表格单元格(display 值为 table-cell,HTML 表格单元格默认值)
  • 表格标题(display 值为 table-caption,HTML 表格标题默认值)
  • 匿名表格单元格元素(display 值为 tabletable-rowtable-row-grouptable-header-grouptable-footer-group(分别是 HTML table、tr、tbody、thead、tfoot 的默认值)或 inline-table
  • overflow 值不为 visibleclip 的块元素
  • display 值为 flow-root 的元素
  • contain 值为 layoutcontentpaint 的元素
  • 弹性元素(display 值为 flexinline-flex 元素的直接子元素),如果它们本身既不是 flexgrid 也不是 table 容器
  • 网格元素(display 值为 gridinline-grid 元素的直接子元素),如果它们本身既不是 flexgrid 也不是 table 容器
  • 多列容器(column-countcolumn-width (en-US) 值不为 auto,包括column-count1
  • column-span 值为 all 的元素始终会创建一个新的 BFC,即使该元素没有包裹在一个多列容器中 (规范变更, Chrome bug)

格式化上下文影响布局,通常,我们会为定位和清除浮动创建新的 BFC,而不是更改布局,因为它将:

有三种情况会形成外边距重叠:

  1. 相邻的两个元素之间的外边距重叠,除非后一个元素加上clear-fix 清除浮动
  2. 没有内容将父元素和后代元素分开 ,就会出现父块元素和其内后代块元素外边界重叠,重叠部分最终会溢出到父级块元素外面。
  3. 空的块级元素 :当一个块元素上边界margin-top 直接贴到元素下边界margin-bottom时也会发生边界折叠。这种情况会发生在一个块元素完全没有设定边框border、内边距padding、高度height、最小高度min-height 、最大高度max-height 、内容设定为 inline 或是加上clear-fix的时候。

==注意:==有设定float浮动和position=absolute绝对定位的元素不会产生外边距重叠行为。

js 延迟加载的方式有哪些?

js 的加载、解析和执行会阻塞页面的渲染过程,因此我们希望 js 脚本能够尽可能的延迟加载,提高页面的渲染速度。 我了解到的几种方式是:

第一种方式是我们一般采用的是将 js 脚本放在文档的底部,来使 js 脚本尽可能的在最后来加载执行。

第二种方式是给 js 脚本添加 defer 属性,这个属性会让脚本的加载与文档的解析同步解析,然后在文档解析完成后再执行这个脚本文件,这样的话就能使页面的渲染不被阻塞。多个设置了 defer 属性的脚本按规范来说最后是顺序执行的,但是在一些浏览器中可能不是这样。

第三种方式是给 js 脚本添加 async 属性,这个属性会使脚本异步加载,不会阻塞页面的解析过程,但是当脚本加载完成后立即执行 js 脚本,这个时候如果文档没有解析完成的话同样会阻塞。多个 async 属性的脚本的执行顺序是不可预测的,一般不会按照代码的顺序依次执行

第四种方式是动态创建 DOM 标签的方式,我们可以对文档的加载事件进行监听,当文档加载完成后再动态的创建 script 标签来引入 js 脚本。

Ajax 是什么? 如何创建一个 Ajax?

我对 ajax 的理解是,它是一种异步通信的方法,通过直接由 js 脚本向服务器发起 http 通信,然后根据服务器返回的数据,更新网页的相应部分,而不用刷新整个页面的一种方法。 创建一个 ajax 有这样几个步骤 :

首先是创建一个 XMLHttpRequest 对象。

然后在这个对象上使用 open 方法创建一个 http 请求,open 方法所需要的参数是请求的方法、请求的地址、是否异步和用户的认证信息。 在发起请求前,我们可以为这个对象添加一些信息和监听函数。比如说我们可以通过 setRequestHeader 方法来为请求添加头信息。我们还可以为这个对象添加一个状态监听函数。一个 XMLHttpRequest 对象一共有 5 个状态,当它的状态变化时会触发onreadystatechange 事件,我们可以通过设置监听函数,来处理请求成功后的结果。当对象的 readyState 变为 4 的时候,代表服务器返回的数据接收完成,这个时候我们可以通过判断请求的状态,如果状态是 2xx 或者 304 的话则代表返回正常。这个时候我们就可以通过 response 中的数据来对页面进行更新了。 当对象的属性和监听函数设置完成

最后我们调用 sent 方法来向服务器发起请求,可以传入参数作为发送的数据体。 xhr.send(null);

let xhr = new XMLHttpRequest();

// 创建 Http 请求
xhr.open("GET", SERVER_URL, true);

// 设置状态监听函数
xhr.onreadystatechange = function() {
     
     
  if (this.readyState !== 4) return;

  // 当请求成功时
  if (this.status === 200) {
     
     
    handle(this.response);
  } else {
     
     
    console.error(this.statusText);
  }
};

// 设置请求失败时的监听函数
xhr.onerror = function() {
     
     
  console.error(this.statusText);
};

// 设置请求头信息
xhr.responseType = "json";
xhr.setRequestHeader("Accept", "application/json");

// 发送 Http 请求
xhr.send(null);

什么是防抖和节流?

介绍

在 JavaScript 中,防抖和节流其实是一个很重要的概念。主要应用场景就是会频繁触发的事件,比如监听滚动、点赞功能,总不能点一次赞就向后台发送一次数据,这时候就要用到防抖和节流。

防抖和节流的核心就是定时器,我们要知道定时器的一个概念,就是在定时之后,在没触发之前清除定时器,这个定时器方法不会被触发。

防抖函数的作用就是控制函数在一定时间内的执行次数。防抖意味着 N 秒内函数只会被执行一次(最后一次),如果 N 秒内再次被触发,则重新计算延迟时间。

节流函数的作用是规定一个单位时间,在这个单位时间内最多只能触发一次函数执行,如果这个单位时间内多次触发函数,只能有一次生效。

在vue中我们可以使用lodash中的一个_debounce

注意第一个参数是函数,第二个参数是延迟时间

预解析

​ 1. 我们js引擎运行js 分为两步: 预解析 代码执行

​ (1). 预解析 js引擎会把js 里面所有的 var 还有 function 提升到当前作用域的最前面

​ (2). 代码执行 按照代码书写的顺序从上往下执行

​ 2. 预解析分为 变量预解析(变量提升) 和 函数预解析(函数提升)

​ (1) 变量提升 就是把所有的变量声明提升到当前的作用域最前面 不提升赋值操作

​ (2) 函数提升 就是把所有的函数声明提升到当前作用域的最前面 不调用函数

  • 注意
    • 全局变量访问不到局部变量
    • 函数内部加了var关键字就是局部的,形参也是局部的(内部不加var 声明变量赋值也是全局变量)
    • 没有加var关键字或者没有传递参数 此时 a=1 就是全局的

原型链

  • 只要是__proto__原型 都指向原型对象,但是Object.prototype.__proto__ 指向的为空(null)

​ 每一个实例对象都有一个__proto__属性,指向的构造函数的原型对象,构造函数的原型对象也是一个对象,也有__proto__属性,这样一层一层往上找就形成了原型链

构造函数实例和原型对象三角关系

1.构造函数的prototype属性指向了构造函数原型对象
2.实例对象是由构造函数创建的,实例对象的__proto__属性指向了构造函数的原型对象
3.构造函数的原型对象的constructor属性指向了构造函数,实例对象的原型的constructor属性也指向了构造函数

原型链和成员的查找机制

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

任何对象都有原型对象,也就是prototype属性,任何原型对象也是一个对象,该对象就有__proto__属性,这样一层一层往上找,就形成了一条链,我们称此为原型链;

当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
如果没有就查找它的原型(也就是 __proto__指向的 prototype 原型对象)。
如果还没有就查找原型对象的原型(Object的原型对象)。
依此类推一直找到 Object 为止(null)。


查找原则:
	实例对象访问属性:
		先去本身构造函数上去查找,如果本身没有要查找的属性或者方法,会自动去原型对象去查找
		如果原型对象也没有,再去原型对象的原型对象去查找。
		如果都没有返回undefined。 如果找到了停止查找
设置原则:
	给实例对象设置一个属性,本质上是给实例添加属性,并不会修改原型的内容。

猜你喜欢

转载自blog.csdn.net/qq_43375584/article/details/125565184