?

245 阅读53分钟

HTML

一、HTML5的新特性

  • 语义化标签
    <header></header> <section></section> 
    <footer></footer> <article></article>
  • 增强型表单
    1. 多个新的表单 Input 输入类型
          <input type="color" /> // 颜色选择器
          <input type="date" /> // 日期选择器
          <input type="url" /> // url输入框
    
    1. placeholder
    2. ...
  • 新增视频标签和音频标签
  • canvas、svg绘图。
  • 地理定位
navigator.geolocation
  • 拖放Api
  • Web Storage(localstorage和sessionStorage) 二、Doctype的作用
DOCTYPE是document type(文档类型)的简写,用来说明你用的XHTML或者HTML是什么版本。

三、如何实现浏览器多个页签之间的通信

  • 使用loacalStorage.
  • 使用cookie+setInterval
    在页面A设置一个使用 setInterval 定时器不断刷新,检查 Cookies 的值是否发生变化,
    如果变化就进行刷新的操作。由于 Cookies 是在同域可读的,
    所以在页面 B 审核的时候改变 Cookies 的值,页面 A 自然是可以拿到的。
    document.cookie = ''. 
    

四、src、href的区别

src用于替代这个元素,而href用于建立这个标签与外部资源之间的关系。

五、Cookie、localStorage、sessionStorage的区别

特性CookielocalStoragesessionStorage的区别
数据的生命期一般由服务器生成,可设置失效时间,用于记录用户信息,来返于服务端和客户端之间.如果在浏览器端生成Cookie,默认是关闭浏览器后失效除非被清除,否则永久保存仅在当前会话下有效,关闭页面或浏览器后被清除
存放数据大小4K左右5MB5MB
与服务器端通信每次都会携带在HTTP头中,如果使用cookie保存过多数据会带来性能问题仅在客户端(即浏览器)中保存,不参与和服务器的通信仅在客户端(即浏览器)中保存,不参与和服务器的通信
易用性需要程序员自己封装,源生的Cookie接口不友好源生接口可以接受,亦可再次封装来对Object和Array有更好的支持源生接口可以接受,亦可再次封装来对Object和Array有更好的支持.

六、HTML5的离线存储和原理

HTML5离线存储存储功能非常强大,它的作用是:在用户没有与因特网连接时,可以正常访问站点或应用,在用户与因特网连接时,自动更新缓存数据。
原理:
HTML5的离线存储是基于一个新建的.appcache文件的,通过这个文件上的解析清单离线存储资源,这些资源就会像cookie一样被存储了下来。之后当网络在处于离线状态下时,浏览器会通过被离线存储的数据进行页面展示.

```html
<!DOCTYPE HTML>
<html manifest = "cache.manifest">
...
</html>

书写cache.manifest文件:

七、如何解决移动端1px被渲染成2px

  • 局部处理
   meta标签中的 viewport属性 ,initial-scale 设置为 1 
   rem 按照设计稿标准走,外加利用transformscale(0.5) 缩小一倍即可
  • 全局处理
   meta标签中的 viewport属性 ,initial-scale 设置为 0.5
   rem 按照设计稿标准走即可

JS

一、0.1 + 0.2 === 0.3吗?为什么

console.log(0.1+0.2);  // 0.30000000000000004
// 解决办法: 
0.1+0.2 => (0.1*10+0.2*10)/10
parseFloat((0.1+0.2).toFixed(10)) ===0.3
其实这都是因为浮点数运算的精度问题。
简单来说,因为计算机只认识二进制,在进行运算时,需要将其他进制的数值转换成二进制,然后再进行计算。
由于浮点数用二进制表达时是无穷的:

二、js有哪些数据类型

基本数据类型:undefinednullnumberbooleanstring
引用数据类型也就是对象类型Object:object、array、date、function
symble(es6新增类型):symbol 本质上是一种唯一标识符,可用作对象的唯一属性名,这样其他人就不会改写或覆盖你设置的属性值。

三、js整数是怎么表示的

var num = 123
var num = Number(123)

四、Number的存储空间是多大?如果后台发送了一个超过最大自己的数字怎么办

Number类型的最大值为253次方,即9007199254740992,
如果超过这个值,比如900719925474099222,那么得到的值会不精确,也就是900719925474099200

五、深拷贝和浅拷贝的区别?深拷贝的方法?手动实现深拷贝(基本类型和引用类型的传值区别)

深拷贝和浅拷贝最根本的区别在于是否真正获取一个对象的复制实体,而不是引用。
直接用=的方式把一个对象赋值给另一个对象,会导致修改新对象时,原对象也发生变化
原因:JavaScript 中对象的赋值是默认引用赋值的(两个对象指向相同的内存地址)
基本类型的数据是存放在栈内存中的,而引用类型的数据是存放在堆内存中的.

js的5种基本数据类型 number,string,null,undefined,boolean 在赋值传递时是值传递,
js的引用数据类型(object,array,function)进行引用传递,其实底层都是对象
var object1 = new Object();
var object2 = object1;
object2.name = 'jhon';
alert(object1.name); //jhon

avatar

定义了一个对象其实是在栈内存中存储了一个指针,这个指针指向堆内存中该对象的存储地址。两个指针都指向同一个对象,所以若其中一个修改了,则另一个也会改变。对象引用本身是传递引用地址,重新初始化变量会改变引用地址,不会更改原变量
  • 对象的深拷贝
JSON.parse(JSON.stringify(obj))
Object.assign({}, obj)
  • 数组的深拷贝
let arrc = [1, 2, 3].concat([4, 5, 6], [7, 8, 9])
let arrs = [1, 2, 3].slice(0, 2)
  • 手写深拷贝
 function deepClone(obj) {
    let objClone = Array.isArray(obj) ? [] : {}
    if(obj && typeof obj === 'object') {
       for(let key in obj) {
            if(obj[key] && typeof obj[key] === 'object') {
                objClone[key] = deepClone(key)
            } else {
                objClone[key] = obj[key]
            }           
       }
    } 
    return objClone
 }

六、事件流

js和html元素的交互几乎都是通过‘事件’完成的,事件从触发到响应分为3个阶段:捕获阶段、目标对象调用事件处理程序阶段、冒泡阶段
事件发生时,响应事件的顺序.这个顺序是按照一个流进行的.这就是事件流.这个流是从内向外流,还是从外向里流,不同浏览器事件流的流向不一样.
chrome、ie:事件冒泡流(从里向外流)
firefox: 事件捕获流(从外向里流)
事件冒泡流和事件捕获的概念:
![avatar](https://img-blog.csdn.net/20150511232044150?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxNDIwNTk2NQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
比如body里面有个div,div里面有一个button,当点击button的时候,浏览器会认为你同时点击了div,也点击了body。
如果button,div,body上都绑定了onclick事件,当点击button时,onclick事件就会按照事件流的流向依次做出响应,那么事件的响应顺序是从里往外流,还是从外向里?
可以用“事件监听”方式绑定时间的时候来设置
例如:

button.addEventListener(“click”,function( ){....},false);
//将第三个参数设置为true,就表示在“捕获阶段”响应事件,此时事件流就是从外向里流的(先响应body上的事件,在响应div上的,最后响应button上的事件。

七、new一个函数时发生了什么

function x() {
    console.log(this)
    debugger
    this.w = 1
    this.a()
}
x.prototype = {
    a: function() {
        console.log(1)
    }
}
console.log(new x())
在断点处输出this的时候,this对象已经完成了基本的构建,也就是说this代表的是一个对象,并且已经完成了原型链的构建,继续往下执行,只是在this代表的这个对象里增加新的属性,并且可以调用原型里的属性了.
当我们new一个函数的时候,在执行函数里第一条语句之前,首先创建一个空的对象{},并且this等于这个空对象且完成了原型链的构建,接下来执行函数里面的代码,最后返回这个对象.

八、new过程的四个步骤

var Func = function() {}
var func = new Func()
// 一、创建一个空对象
var obj = new Object()
// 二、设置原型链(当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象)
obj.__proto__ = Func.prototype
// 三、让Func中的this指向obj,并执行Func的函数体
var result = Func.call(obj)
// 四、 判断Func的返回值类型. 如果是值类型,返回obj.如果是引用类型,就返回这个引用类型的对象
if(typeof(result) === 'object') {
    func = result
} else {
    func = obj
}

九、new 一个构造函数,如果函数返回 return {} 、 return null , return 1 , return true 会发生什么情况?

默认情况下,没有return的函数的返回值为undefined(即没有定义返回值),如果定义了return,则返回制定对象.
但是构造函数比较特殊,new构造函数在没有return的情况下默认返回新创建的对象.在有return的情况下,需要分为两个情况考虑.
  • 如果返回值为基本数据类型(string,number,boolean,undefined,null),那么返回值为新建对象实例,即this
var a = function S() {
    this.x = 3
    return 1
}
var b = new a()
console.log(b) // { x: 3 }
  • 如果返回值为一个非基本数据类型的对象,函数的返回值为指定的对象.this值所引用的对象被丢弃.
var a = function S() {
    this.x = 3
    return a
}
var b = new a()
console.log(b) // S(){ this.x = 3; return a }
直观的例子
var a = function User(name, age){
  this.name = name
  this.age = age
  // return; // 返回 this
  // return null // 返回 this
  // return this // 返回 this
  // return true // 返回 this
  // return 'string' // 返回 this
  // return 1 // 返回 this
  // 以上都返回 { name: '哈哈', age: 18 }
  // return [] // 返回 新建的 []
  // return function(){} // 返回 新建的 function,抛弃 this
  // return new Boolean(false) // 返回 新建的 boolean,抛弃 this
  // return new String('hello world') // 返回 新建的 string,抛弃 this
  // return new Number(32) // 返回新的 number,抛弃 this
}
var b = new a('哈哈',18)
console.log(b);

十、闭包

  • 闭包的概念
闭包是在函数里面声明函数,并且内部函数可以访问外部函数作用域的变量
  • 闭包的作用
1、形成命名空间,存储私有变量/常量
2、类似形成一个es6 export的模块,对外提供(类似es6的导出)一些属性和方法,供外部调用
  • 为什么会有闭包
js之所以会有闭包,是因为js不同于其他规范的语言,js允许一个函数中再嵌套子函数,正是因为这种允许函数嵌套,导致js出现了所谓闭包。
  • 闭包的特性
 1、函数嵌套函数
 2、函数内部可以引用函数外部的参数和变量
 3、参数和变量不会被垃圾回收机制回收:
 一般的函数,调用完毕之后,系统自动注销函数,而对于闭包来说,在外部函数被调用之后,闭包结构依然存在。由于闭包携带包含它函数的作用域,因此比其他函数占用的内存更多,泄露内存
function a(){
    function b(){
    
    }
    return b
}
var f = a()
这个例子中,父函数调用时,函数体内创建了子函数b,但是子函数并没有立即调用,而是返回了函数指针,以备“日后再调用”,因为“准备日后调用”,此时父函数a执行完了,就不敢注销自己的作用域中的数据了,因为一旦注销了,就把创建的子函数b的结构都从内存中注销,子函数连结构都没有了,日后还怎么调用?同时如果注销了,即便子函数能够日后再调用,但它在调用时沿着函数作用域链往上访问数据,也没有数据可以访问了。
函数ab的父函数,而b被赋给了一个全局变量,这导致b始终在内存中,而b的存在依赖于a,因此a也始终在内存中,不会在调用结束后被垃圾回收机制回收

正因此,子函数要“日后调用”,导致父函数不敢注销自己的作用域数据,那么这个子函数就是“闭包函数”。
  • 闭包的应用场景
setTimeout传递参数
//原生的setTimeout传递的第一个函数不能带参数
setTimeout(function(param){
    alert(param)
},1000)
function a(param) {
    return function() {
        console.log(param)
    }
}
var b = a(true)
setTimeout(() => {
    b()
}, 1000)
防抖节流
    <div id="content" 
        style="height:150px;line-height:150px;text-align:center; color: #fff;
            background-color:#ccc;
            font-size:80px;">
    </div>
    <div id="content2" 
        style="height:150px;line-height:150px;text-align:center; color: #fff;
            background-color:#ccc;
            font-size:80px; margin-top: 30px;">
    </div>
/**
 * 在前端开发的过程中,我们经常会需要绑定一些持续触发的事件,
 * 如 resize、scroll、mousemove 等等,
 * 但有些时候我们并不希望在事件持续触发的过程中那么频繁地去执行函数。
    通常这种情况下我们怎么去解决的呢?一般来讲,防抖和节流是比较好的解决方案。 
    防抖和节流都是为了解决频繁触发某个事件的情况造成的性能消耗。
    防抖: 输入搜索
    节流: 拖拽
    防抖就是在触发后的一段时间内执行一次,例如:在进行搜索的时候,
    当用户停止输入后调用方法,节约请求资源
    节流就是在频繁触发某个事件的情况下,每隔一段时间请求一次,类似打游戏的时候长按某个按键,
    动作是有规律的在间隔时间触发一次
*/
let num = 1
let content = document.getElementById('content')
let content2 = document.getElementById('content2')

function count() {
   content.innerHTML = num++
}
function count2() {
   content2.innerHTML = num++
}
// content.onmousemove = count

/**
 * 所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,
 * 则会重新计算函数执行时间。
 */

//    function debounce(fn, wait) {
//        let timeout
//        return function(e) {
//            console.log('event->', event)
//            let context = this
//            let args = arguments
//            console.log('args ->', args)
//         //    console.log(context)
//            if(timeout) clearTimeout(timeout)
//            timeout = setTimeout(() => {
//                console.log(this)
//         //  为了让 debounce 函数最终返回的函数 this 指向不变以及依旧能接受到 e 参数。

//                fn.apply(context, args)
//            }, wait)
//        }
//    }
function debounce(func,wait) {
    let timeout;
    return function () {
    let context = this;
    let args = arguments;

    if (timeout) clearTimeout(timeout);

    let callNow = !timeout;
    timeout = setTimeout(() => {
        timeout = null;
    }, wait)

    if(callNow) func.apply(context, args)
}
}
content.onclick = debounce(count, 1000)

/**
 * 所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数。节流会稀释函数的执行频率
 * 对于节流,一般有两种方式可以实现,分别是时间戳版和定时器版
 */

// 时间戳版:
    function throttle(func, wait) {
        let previous = 0
        return function() {
            let now = Date.now()
            let context = this
            let arg = arguments
            if(now - previous > wait) {
                func.apply(context, arg)
                previous = now
            }
        }
    }
    content2.onmousemove = throttle(count2, 3000)

十一、var、let、const的区别?什么是变量提升?

const 声明常量,后面不能修改.复合变量可以修改
let 声明变量,块作用域,后面不能覆盖之前声明的值
var 声明变量,函数作用域,后面能覆盖之前声明的值
(1varlet是变量,const是常量。(2var没有块级作用域,letconst有块级作用域
变量提升: 在进入一个执行上下文后,先把 varfunction(函数声明才行.字面量声明的函数不行例如 let aa = function()) 声明的变量前置,再去顺序执行代码
console.log(a); 
var a = 1;//输出的是undefined

console.log(b);
let b = 1; //输出报错, Uncaught ReferenceError: b is not defined

因为var在声明变量的时候,会提到当前作用域的顶端,而赋值操作在原处不变 上面的例子相当于

var a; 
console.log(a); 
a = 1;

十二、什么是作用域

作用域是可访问变量的集合

十三、什么是作用域链

 一般情况下,变量取值到 创建 这个变量 的函数的作用域中取值。

 但是如果在当前作用域中没有查到值,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链。

十四、对this指向的理解

**ES5**

1.在普通函数中的this总是代表他的直接调用者,默认情况下指向windos

2.在严格模式下,没有直接调用者的函数中的this是undefined使用

3.call,apply,bind,this指向的是绑定的对象;

**ES6**

箭头函数没有自己的this,他的this是继承来的,默认指向在定义他时所在的对象
this永远指向一个对象
this的指向取决于函数执行的位置或者说是取决于函数所处的运行环境(函数运行时所在的对象)
js支持运行环境动态切换,因此this的指向也是动态的
function fun(){
    console.log(this.s)
}
var obj = {
    s: '1',
    f: fun
}
var s = '2'
obj.f() //1
fun() //2

var A = {
    name: '张三',
    f: function () {
        console.log('姓名:' + this.name);
    }
}
var B = {
    name: '李四'
}
B.f = A.f
B.f()   // 姓名:李四
A.f()   // 姓名:张三
// A.f属性被赋给B.f,也就是A对象将匿名函数的地址赋值给B对象

那么在调用时,函数分别根据运行环境的不同,指向对象A和B
  • 事件绑定中的this
事件绑定共有三种方式:行内绑定、动态绑定、事件监听
行内绑定的两种情况:
<input type="button" value="按钮" onclick="clickFun()">
<script>
    function clickFun(){
        this // 此函数的运行环境在全局window对象下,因此this指向window;
    }
</script>
<input type="button" value="按钮" onclick="this">
<!-- 运行环境在节点对象中,因此this指向本节点对象 -->
行内绑定事件的语法是在html节点内,以节点属性的方式绑定,属性名是事件名称前面加'on',属性的值则是一段可执行的 JS 代码段;而属性值最常见的就是一个函数调用;

当事件触发时,属性值就会作为JS代码被执行,当前运行环境下没有clickFun函数,因此浏览器就需要跳出当前运行环境,在整个环境中寻找一个叫clickFun的函数并执行这个函数,所以函数内部的this就指向了全局对象window;如果不是一个函数调用,直接在当前节点对象环境下使用this,那么显然this就会指向当前节点对象
动态绑定与事件监听
<input type="button" value="按钮" id="btn">
<script>
    var btn = document.getElementById('btn');
    btn.onclick = function(){
        this ;  // this指向本节点对象
    }
</script>
因为动态绑定的事件本就是为节点对象的属性(事件名称前面加'on')重新赋值为一个匿名函数,因此函数在执行时就是在节点对象的环境下,this自然就指向了本节点对象;

事件监听中this指向的原理与动态绑定基本一致,所以不再阐述
  • 构造函数中的this
function Pro(){
    this.x = '1'
    this.y = function(){}
}
var p = new Pro()

avatar

  • window定时器中的this
var obj = {
    fun:function(){
        this
    }
}
setInterval(obj.fun,1000)      // this指向window对象
setInterval('obj.fun()',1000)  // this指向obj对象
setInterval(obj.fun,1000) 的第一个参数是obj对象的fun
因为 JS 中函数可以被当做值来做引用传递,实际就是将这个函数的地址当做参数
传递给了 setInterval 方法,换句话说就是 setInterval 的第一参数接受了一个函数,
那么此时1000毫秒后,函数的运行就已经是在window对象下了,
也就是函数的调用者已经变成了window对象,所以其中的this则指向的全局window对象;
而在 setInterval('obj.fun()',1000) 中的第一个参数,实际则是传入
的一段可执行的 JS 代码;1000毫秒后当 JS 引擎来执行这段代码时,则是通过 obj 对象
来找到 fun 函数并调用执行,那么函数的运行环境依然在 对象 obj 内,所以函数内部的this也就指向了 obj 对象
  • 函数对象的call()、apply() 方法
call()方法
函数名称.call(obj,arg1,arg2...argN);
参数说明:
obj:函数内this要指向的对象,
arg1,arg2...argN :参数列表,参数与参数之间使用一个逗号隔开
var lisi = {names:'lisi'};
var zs = {names:'zhangsan'};
function f(age){
    console.log(this.names);
    console.log(age);
    
}
f(23);//undefined
//将f函数中的this指向固定到对象zs上;
f.call(zs,32);//zhangsan
apply()方法
函数名称.apply(obj,[arg1,arg2...,argN])
参数说明:
obj :this要指向的对象
[arg1,arg2...argN] : 参数列表,要求格式为数组
var lisi = {name:'lisi'}; 
var zs = {name:'zhangsan'}; 
function f(age,sex){
    console.log(this.name+age+sex); 
}
//将f函数中的this指向固定到对象zs上;
f.apply(zs,[23,'nan']);
  • 箭头函数和普通函数的区别
1、箭头函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
2、箭头函数没有arguments,如果要用,可以用 rest 参数代替 (注意在node环境下是有arguments的)
3、箭头函数不能作为构造函数,不能使用new
4、箭头函数没有原型,不能继承
5、箭头函数不能当做Generator函数,不能使用yield关键字
6、箭头函数体内的this对象,就是定义该函数时所在的作用域指向的对象,而不是使用时所在的作用域指向的对象。

十五、js继承的几种形式

什么是js继承?  
定义:
如果一个类能够重用另一个类的属性和或方法,就称之为继承。面向对象的语言多数都支持继承。
特点: 
子类可以使用父类的所有功能,并且对这些功能进行拓展。
继承最重要的优点就是代码复用,从而构建大型软件系统。
- 继承的几种形式
```js
function Person(name) {
    this.name = name
    this.sum = function() {
        console.log(this.name)
    }
}
Person.prototype.age = 10
- 原型链继承(传统形式)
function Per() {
    this.name = 'ker'
}
Per.prototype = new Person()
var per1 = new Per()
console.log(per1.age) // 10
console.log(per1 instance of Person) //true



function DomElement(id){
    this.dom = document.getElementById(id);
}
DomElement.prototype.html = function (val){
    var ele = this.dom
    if (val) {
        ele.innerHTML = val
        return this
    }else{
        return ele.innerHTML
    }
}
DomElement.prototype.on = function (type,fn){
    vae ele = this.dom
    ele.addEventListener(type,fn)
    return this
}
var div1 = new DomElement('div1')
console.log(div1.html());
div1.html('<p>这是一段文字</p>').on('click',function(){
    console.log('clicked')
})

- 借用构造函数
function Person(name, age, sex) { 
    this.name = name;
    this.age = age;
    this.sex = sex;
}
function Student(name, age, sex, grade) {
    Person.call(this, name, age, sex)
    this.grade = grade
}
var student = new Student('hehe', 40, 'male', 18)

- 组合继承(组合原型链继承和借用构造函数继承)(常用)
function SubType(name) {
    Person.call(this, name) // 借用构造函数模式
}
Subtype.prototype = new Person() // 原型链继承
var sub = new SubType('gar')
console.log(sub.name) // gar, 继承了构造函数属性
console.log(sub.age) // 10, 继承了父类原型的属性
  • es6 extends

class Plane {    
    static alive() {        
        return true 
    }    
    constructor(name) {        
        this.name = name || '普通飞机'       
        this.blood = 100;    
    }    
    fly() {        
        console.log('fly')   
    }
};
class AttackPlane extends Plane{    
    constructor(name) {        
        super(name);        
        this.logo = 'duyi'  
    }    
    dan() {        
        console.log('bububu')   
    }
}
var oAp = new AttackPlane('攻击机')
console.log(oAp)

十六、什么是原型、原型链、继承?

  • 什么是原型?
每个函数都有prototype(原型)属性,这个属性是一个指针,指向一个对象,这个对象的用途是包含特定类型的所有实例共享的属性和方法,这个对象(原型对象)是用来给实例共享属性和方法的。  
function Person() {}
Person.prototype.name = 'Nicholas';
Person.prototype.age = '29';
Person.prototype.job = 'SoftWare Engineer';
Person.prototype.sayName = function() {
    console.log(this.name);
};

var person1 = new Person();
person1.sayName(); // Nicholas

var person2 = new Person();
person1.sayName(); // Nicholas

console.log(person1.sayName === person2.sayName); // true
  • 什么是原型链?
假设一个原型对象等于另一个类型的实例,另一个类型的原型对象又等于另一个类型的实例。就像这样一层层递进,就构成了实例与原型的链条,这个就是所谓的原型链的基本概念
  • 概念
当查找一个对象的某个属性时,会先从它自身的属性上查找,

如果找不到的话会从它的_proto_属性上查找,就是这个构造函数的prototype属性,

如果还没找到就会继续在父级的_proto_上查找,直到最顶层,找不到则为undefined,

像这样一层一层去查找形成一个链式的称为原型链
  • prototype的作用
为了写一些方法给构造函数的实例对象使用,因为构造函数的每个实例都可以访问它的prototype

十七、事件循环

js在运行中的任务,有一套收集,排队,执行的特殊机制,这种机制就是事件循环.
遇到同步事件直接执行,遇到异步事件分为宏任务和微任务
宏任务:包括整体代码script,setTimeoutsetInterval、I/O、UI 交互事件
微任务(一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前):Promise process.nextTick

avatar avatar 十八、Promise

Promise是什么?
Promise 是异步编程的一种解决方案:
从语法上讲,promise是一个对象,从它可以获取异步操作的消息;
从本意上讲,它是承诺,承诺它过一段时间会给你一个结果。
promise有三种状态:pending(等待态),fulfiled(成功态),rejected(失败态) ;
状态一旦改变,就不会再变。创造promise实例后,它会立即执行。
// 手写Promise
function MyPromise(fn) {
    let value = null, callbacks = []
    this.then = function(onFullFilled) {
        callbacks.push(onFullFilled)
    }
    function resolve() {
        // 利用event loop推到下一个队列执行,确保执行顺序
        setTimeout(() => {
            callbacks.forEach(callback => {
                callback(value)
            })
        }, 0)
    }
    fn(resolve)
}

// 测试
const a = new MyPromise(function(resolve, reject) => {
    setTimeout(() => {
        resolve('hi')
    }, 2000)
}).then(value => {
    console.log(value) // hi
})


v2

十九、面向对象

1、抽象:抽指把核心的东西抽出来,把与我们要解决的问题有关的东西拿出来摆在面前 

2、封装:让使用对象的人不考虑内部实现,只考虑功能使用  把内部的代码保护起来,只留出一些个api接口供用户使用

3、继承:就是为了代码的复用,从父类上继承出一些方法和属性,子类也有自己的一些属性 

4、多态:实际上是不同对象作用与同一操作产生不同的效果。多态的思想实际上是把“想做什么”和“谁去做“分开

二十、判断数组的方式

  • instance of
let arr = []
console.log(arr instanceof Array) // true
  • 对象构造函数的contructor
let arr = []
console.log(arr.constructor === Array) // true
  • Object.prototype.toString
let arr = []
console.log(Object.prototype.toString.call(arr) === '[object Array]') // true
  • Array.isArray
let arr = []
console.log(Array.isArray(arr)) // true

二十一、基本类型和引用类型的区别

  • 基本类型
基本数据类型有:undefinednullnumberbooleanstring
基本数据类型的访问是按值访问的,就是说你可以操作保存在变量中的实际的值

一、基本类型的值是无法改变的

// 任何方法都无法改变一个基本类型的值,比如一个字符串:
var name = 'jozo';
name.toUpperCase(); // 输出 'JOZO'
console.log(name); // 输出  'jozo'

var a = 10 ;\
var b = a ;\
b = 20 ;\
console.log(a); // 10值

二、基本类型的比较是值的比较

只有在它们的值相等的时候它们才相等
  • 引用类型 一、引用类型的值是可变的
var person = {};//创建个控对象 --引用类型
person.name = 'jozo';
person.age = 22;
person.sayName = function(){console.log(person.name);} 
person.sayName();// 'jozo'
 
delete person.name; //删除person对象的name属性
person.sayName(); // undefined

var obj1 = new Object();\
var obj2 = obj1;\
obj2.name = "我有名字了";\
console.log(obj.name); //我有名字了

二、引用类型的比较是引用的比较

  • 具体区别
1、声明变量时不同的内存分配
基本类型: 存储在栈(stack)中的数据段,它们的值是直接存储在变量访问的位置
引用类型: 存储在堆(heap)中的对象,也就是说,存储在变量处的值是一个指针(point),指向存储对象的内存地址.
2、不同的内存分配机制也带来了不同的访问机制
在js中是不允许直接访问保存在堆内存中的对象的,所以在访问一个对象时,首先得到的是这个对象在堆内存中的地址,然后再按照这个地址去获得这个对象中的值,这就是按引用访问.
引用数据类型存储在堆内存中,因为引用数据类型占据空间大、占用内存不固定。 如果存储在栈中,将会影响程序运行的性能; 引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。 当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体
基本数据类型是可以直接访问的到的
3、复制变量时的不同
- 原始值:** 在将一个保存着原始值的变量复制给另一个变量时,会将原始值的副本赋值给新变量,此后这两个变量是完全独立的,他们只是拥有相同的value而已。

- 引用值:**在将一个保存着原始值的变量赋值给另一个变量时,会把这个内存地址赋值给新变量。也就是说这两个变量都指向了内存中的同一对象,它们中任何一个做出的改变都会反映到另一个身上。(这里要理解一点的就是,复制对象时并不会在堆内存中新生成一个一模一样的对象,只是对了一个保存指向这个对象指针的变量而已)。

**二十二、检测数据类型的方式

- typeof 其中数组、对象、null都会被判断为Object,其他判断都正确
- instanceof 只能判断引用数据类型,不能判断基本数据类型
- constructor 它有2个作用 一是判断数据的类型,
二是对象实例通过constructor对象访问它的构造函数。需要注意的事情是如果创建一个对象来改变它的原型,constructor就不能来判断数据类型了
Object.prototype.toString.call()

二十三、Set和Map的区别

1Map是键值对,Set是值得集合,当然键和值可以是任何得值
2Map可以通过get方法获取值,而set不能因为它只有值
3、都能通过迭代器进行for...of 遍历
4Set的值是唯一的可以做数组去重,而Map由于没有格式限制,可以做数据存储

二十四、类数组转换为数组

类数组: 有对象和数组的特性,既可以当作对象来用,也可以当作数组来用.可以从下标获取到值,但没有数组的所有方法.
常见的类数组:arguments、获取Dom元素方法返回的结果(比如getElementsByTagName)

二十四、什么是内存泄漏以及造成内存泄漏的场景

内存泄露是指当一块内存不再被应用程序使用的时候,由于某种原因,这块内存没有返还给操作系统或者内存池的现象.内存泄漏说白了就是本该被回收的内存因为一些异常没有被回收掉,而一直存在于内存中占用内存, 虽然说js有垃圾自动回收机制,但是如果我们的代码写法不当,会让变量一直处于“进入环境”的状态.内存泄漏可能会导致应用程序卡顿或者崩溃

垃圾回收机制: JavaScript的解释器可以检测到何时程序不再使用一个对象了,当他确定了一个对象是无用的时候,他就知道不再需要这个对象,可以把它所占用的内存释放掉了.

导致内存泄漏的情况
1、意外的全局变量:由于使用未声明的变量,而意外的创建了一个全局变量,而使这个变量一直留在内存中无法被回收
2、被遗忘的计时器或回调函数:设置了 setInterval 定时器,而忘记取消它,如果循环函数有对外部变量的引用的话,那么这个变量会被一直留在内存中,而无法被回收。
3DOM中的addEventLisner 函数及派生的事件监听.脱离 DOM 的引用:获取一个 DOM 元素的引用,而后面这个元素被删除,由于一直保留了对这个元素的引用或监听,所以它也无法被回收。解决方案: removeEventListener
4、闭包:不合理的使用闭包,从而导致某些变量一直被留在内存当中。

二十五、什么是类数组,和数组的区别?

    类数组对象,就是指可以通过索引属性访问元素并且拥有 length 属性的对象
    - 函数里面的参数对象 argumens;
    - 用 getElementsByTagName / className 获得的 HTMLCollection;
    - 用 querySelector 获得的 NodeList
区别:
    - 类数组数据类型为Object,数组数据类型为Array;
    - 类数组有length属性可以通过索引获取,但不具备数组的方法里如果forEach等
    - 类数组长度不可变

image.png

function fun() {
   console.log(arguments)
}
function fun2() {
   console.log(document.getElementsByTagName('div'))
}
fun(1, 2, 3, 4, 5)

image.png

// 类数组转为数组
//通过call调用数组的slice方法来实现转换
Array.prototype.slice.call(arrayLike)

//通过call调用数组的splice方法来实现转换
Array.prototype.splice.call(arrayLike,0)

//通过apply调用数组的concat方法来实现转换
Array.prototype.concat.apply([],arrayLike)
//通过Array.from方法来实现转换
Array.from(arrayLike)

二十六、for... in和 for...of的区别

    - for ... in 返回值是keyfor ... of返回的是值
    - for ... of 只能遍历底层带有iterator接口的数据例如Set、Map、String、Array。对于原生对象没有部署iterator接口使用for ... of会报错

CSS

一、css3的新特性

1、过渡 transition: 花费时间,效果曲线(默认ease),延迟时间(默认0)复制代码
2、animation: 动画名称,一个周期花费时间,运动曲线(默认ease, 循环 infinite),动画延迟(默认0),播放次数(默认1),是否反向播放动画(默认normal),是否暂停动画(默认running)
3、形状转换 transform
4、阴影 box-shadow
5、边框
6、媒体查询
7、弹性布局 flex
8、盒模型定义
CSS3中的盒模型有以下两种:标准盒模型、IE盒模型
盒模型都是由四个部分组成的,分别是margin、border、padding和content
标准盒模型和IE盒模型的区别在于设置width和height时, 所对应的范围不同
1、标准盒模型的width和height属性的范围只包含了content
2、IE盒模型的width和height属性的范围包含了border、padding和content
可以通过修改元素的box-sizing属性来改变元素的盒模型;
1、box-sizing:content-box表示标准盒模型(默认值
2、box-sizing:border-box表示IE盒模型(怪异盒模型)

二、重绘和重排

  • 重排
任何会改变元素几何信息(元素的位置和尺寸大小)的操作都会触发重排
只改变外观、风格,不影响布局,会引发重绘

三、伪元素和伪类的区别

伪类:单冒号。当我们希望样式在某些状态特定状态下才被呈现到指定的元素时,也就是说当某个元素状态改变时,我们希望给这个元素添加一些特殊效果,就可以往元素的选择器后面添加对应的伪类例如:hover
伪元素:双冒号。创建不在文档树中的元素,并为其添加样式。需要注意的是,伪元素的样式里需要加一个contnet属性。比如::before{ content: '*' }
二者核心区别在于是否创造了新的元素

VUE

一、MVC和MVVM

  • MVC
MVC 全名是 Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范
- Model(模型):是应用程序中用于处理应用程序数据逻辑的部分。通常模型对象负责在数据库中存取数据
- View(视图): 是应用程序中处理数据显示的部分。通常视图是依据模型数据创建的
- Controller(控制器): 是应用程序中处理用户交互的部分。通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据

avatar

MVC 的思想:一句话描述就是 Controller 负责将 Model 的数据用 View 显示出来,换句话说就是在 Controller 里面把 Model 的数据赋值给 View
  • MVVM
MVVM新增了VM类
- ViewModel: 做了两件事达到了数据的双向绑定 一是将【模型】转化成【视图】,即将后端传递的数据转化成所看到的页面。实现的方式是:数据绑定。二是将【视图】转化成【模型】,即将所看到的页面转化成后端的数据。实现的方式是:DOM 事件监听。

avatar

MVVM 与 MVC 最大的区别就是:它实现了 View 和 Model 的自动同步,也就是当 Model 的属性改变时,我们不用再自己手动操作 Dom 元素,来改变 View 的显示,而是改变属性后该属性对应 View 层显示会自动改变(对应Vue数据驱动的思想)

![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ec5b474840484591ae246f838434b9eb~tplv-k3u1fbpfcp-watermark.image?)
整体看来,MVVM 比 MVC 精简很多,不仅简化了业务与界面的依赖,还解决了数据频繁更新的问题,不用再用选择器操作 DOM 元素。因为在 MVVM 中,View 不知道 Model 的存在,Model 和 ViewModel 也观察不到 View,这种低耦合模式提高代码的可重用性

二、vue2响应式原理,和vue3的区别

Vue 采用声明式编程替代过去的类 Jquery 的命令式编程,并且能够侦测数据的变化,更新视图。这使得我们可以只关注数据本身,而不用手动处理数据到视图的渲染,避免了繁琐的 DOM 操作,提高了开发效率

当你把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty()来劫持各个属性的settergetter,每个组件实例都有相应的 watcher实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 watcher 重新计算,从而致使它关联的组件得以更新。在数据变动时发布消息给订阅者,触发相应的监听回调。
  • vue2: Object.defineProperty
    _proxyData(data) {
        // 遍历data中的所有属性
        Object.keys(data).forEach(key => {
        // 把data的属性注入到Vue实例中
            Object.defineProperty(this, key, {
                enumerable: true,
                configurable: true,
                get() {
                    console.log('get--', data[key])
                    return data[key]
                },
                set(newVal) {
                    console.log('set--', newVal)
                    if(newVal === data[key]) return
                    data[key] = newVal
                }
            })
        })
    }
  • vue3: proxy
    _proxyData(data) {
        console.log('data', data)
        // 遍历data中的所有属性
        let vm = new Proxy(data, {
            get(target, key) {
                console.log('target', target)
                console.log('get, key:', key, target[key])
                return target[key]
            },
            set(target, key, newVal) {
                console.log('set, key:', key, newVal)
                if(target[key] === newVal) return
                target[key] = newVal
            }
        })
        return vm
    }

三、 SPA单页面应用优缺点以及首屏性能优化

SPA应用:只有一个html文件,整个网站的所有内容都在这一个html里,通过js来处理) 不仅仅是在页面交互是无刷新的,连页面跳转都是无刷新的。为了实现单页应用 ==> 前后端分离 + 前端路由。 (更新视图但不重新请求页面)
- 优点:
1.体验好,不刷新,减少 请求  数据ajax异步获取 页面流程;
2.前后端分离
3.减轻服务端压力
4.共用一套后端程序代码,适配多端
- 缺点:
1.首屏加载过慢;
2.SEO 不利于搜索引擎抓取
- 首屏性能优化:
0.路由懒加载
1.减小入口文件积
2.静态资源本地缓存
3.UI框架按需加载
4.图片资源的压缩
5.组件重复打包
6.开启GZip压缩
1npm i -D compression-webpack-plugin
2、在vue.config.js中配置
3ngnix配置
gizp压缩是一种http请求优化方式,通过减少文件体积来提高加载速度。
htmljscss文件甚至json数据都可以用它压缩,可以减小60%以上的体积
7.使用SSR(想要更快)
8.预渲染(想要更快)
预渲染是在js加载前,就生成了一个首页的静态页面,用于加载,不会让你等着了,静态页面的性能不用说了吧,嗖嗖的。
预渲染依赖的是prerender-spa-plugin插件,使用起来也非常的简单,但是坑非常多,一个地方尊重不到就会报错
  • 异步路由加载(路由懒加载)
正常的加载方式
import Login from '@/components/pages/signIn/signIn'

export default new Router({
  routes: [ {
      path: '/login',
      component: Login,
    }]
})
第一种异步加载方式
export default new Router({
  routes: [ {
      path: '/login',
      component: resolve=>require(['@/components/pages/signIn/signIn'],resolve),
    }]
})
第二种异步加载方式
export default new Router({
  routes: [ {
      path: '/login',
      component: ()=>import('@/components/pages/signIn/signIn'),
    }]
})
  • 关闭sourcemap
sourcemap是为了方便线上调试用的,因为线上代码都是压缩过的,导致调试极为不便,而有了sourcemap,就等于加了个索引字典,出了问题可以定位到源代码的位置。
但是,这个玩意是每个js都带一个sourcemap,有时sourcemap会很大,拖累了整个项目加载速度,为了节省加载时间,我们将其关闭掉。
1、运行 cnpm run build 开始打包

2、会在项目目录下自动创建dist目录,打包好的文件都在其中

解决办法:去src/config/index.js中改一个参数:

productionSourceMap:false
  • element-ui按需加载 四、vuex
  • vuex是什么?怎么使用?哪种功能场景使用它?
vuex是一个专门为vue.js应用程序开发的状态管理模式.它采用集中存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化
场景:多个组件共享数据或者是跨组件传递数据时
  • vuex的流程
页面通过mapAction异步提交事件到action。action通过commit把对应参数同步提交到mutation,mutation会修改state中对应的值。最后通过getter把对应值跑出去,在页面的计算属性中,通过,mapGetter来动态获取state中的值
  computed: {
    ...mapGetters(['getuserId','getToken']),
  },
  methods: {
       ...mapActions({
        add: 'increment' // 映射 this.add() to this.$store.dispatch('increment')
     })
  }

  • vuex有哪几种属性
有五种,分别是State , Getter , Mutation , Action , Module (就是mapAction)
1、state: vuex的基本数据,用来存储变量
2、getter: 从基本数据(state)计算(派生)出的数据,相当于计算属性computed
3、mutation: 提交更新数据的方法,必须是同步的(如果需要异步使用action)。每个mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数,提交载荷作为第二个参数。
4、action: 和mutation的功能大致相同,不同之处在于 ==》1. Action 提交的是 mutation,而不是直接变更状态。 2. Action 可以包含任意异步操作。
5、modules: 模块化vuex,可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理。
  • 如何解决刷新导致vuex数据被清空的问题
方法一、localStorage
1window.addEventListener("beforeunload",()=>{ }) 监听页面刷新
2、使用localStorage 临时存储数据
思路:在页面刷新的时候,将vuex中的数据存储到localstorage ,然后刷新结束后再将localstorage里的数赋值给store并清除localstorage
方法二、 vuex-persist
  • 为什么 Vuex 的 mutation 中不能做异步操作?
1、Vuex中所有的状态更新的唯一途径都是mutation,
异步操作通过 Action 来提交 mutation实现,这样可以方便地跟踪每一个状态的变化,
从而能够实现一些工具帮助更好地了解我们的应用。
2、每个mutation执行完成后都会对应到一个新的状态变更,
这样devtools就可以打个快照存下来,然后就可以实现 time-travel 了。
如果mutation支持异步操作,就没有办法知道状态是何时更新的,无法很好的进行状态的追踪,
给调试带来困难。

五、Vue 数据更新但页面没有更新的几种情况

  • Vue 无法检测实例被创建时不存在于 data 中的 property
原因:由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,
所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的
  • Vue 无法检测对象 property 的添加或移除
原因:官方 - 由于 JavaScript(ES5) 的限制,Vue.js 不能检测到对象属性的添加或删除。因为 Vue.js 在初始化实例时将属性转为 getter/setter,所以属性必须在 data 对象上才能让 Vue.js 转换它,才能让它是响应的。
解决办法
// 动态添加
Vue.set(vm.obj, propertyName, newValue)
// 或
vm.$set(vm.obj, propertyName, newValue)
// 动态移除
Vue.delete(vm.obj, propertyName)
// 或
vm.$delete(vm.obj, propertyName)
  • Vue 不能检测通过数组索引直接修改一个数组项
原因:Object.defineProperty() 可以监测数组的变化
Object.defineProperty() 可以监测数组的变化。但对数组新增一个属性(index)不会监测到数据变化,因为无法监测到新增数组的下标(index),删除一个属性(index)也是。
解决办法
// Vue.$set
Vue.set(vm.items, indexOfItem, newValue)
// vm.$set
vm.$set(vm.items, indexOfItem, newValue)
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)
  • Vue 不能监测直接修改数组长度的变化
原因:官方 - 由于 JavaScript 的限制,Vue 不能检测数组和对象的变化;尤雨溪 - 性能代价和获得用户体验不成正比。
  • 循环嵌套层级太深,视图不更新
vm.$forceUpdate() // 不推荐
  • 路由参数变化时,页面不更新(数据不更新)
原因:路由视图组件引用了相同组件时,当路由参会变化时,会导致该组件无法更新,也就是我们常说中的页面无法更新的问题。
解决办法
// $route监听
      watch: {
       '$route': function() {
           this.message = this.$route.params.name
        }
    }
// 给<router-view>绑定key属性
 <router-view :key="key"></router-view>

五、路由传递参数

  • params和query
params:/router1/:id ,/router1/123,/router1/789 ,这里的id叫做params

query:/router1?id=123 ,/router1?id=456 ,这里的id叫做query。

router.js:

使用params方法传参的时候,要在路由后面加参数名,并且传参的时候,参数名要跟路由后面设置的参数名对应。使用query方法,就没有这种限制,直接在跳转里面用就可以。

如果路由上面不写参数,也是可以传过去的,但不会在url上面显示出你的参数,并且当你跳到别的页面或者刷新页面的时候参数会丢失(如下图所示),那依赖这个参数的http请求或者其他操作就会失败。
  • 区别
1、用法上的

刚query要用path来引入,params要用name来引入,接收参数都是类似的,分别是this.$route.query.name和this.$route.params.name。

注意接收参数的时候,已经是$route而不是$router

2、展示上的

query更加类似于我们ajax中get传参,params则类似于post,说的再简单一点,前者在浏览器地址栏中显示参数,后者则不显示

3params是路由的一部分,必须要有。query是拼接在url后面的参数,没有也没关系。

params一旦设置在路由,params就是路由的一部分,如果这个路由有params传参,但是在跳转的时候没有传这个参数,会导致跳转失败或者页面会没有内容。

比如:跳转/router1/:id

4params、query不设置也可以传参,params不设置的时候,刷新页面或者返回参数会丢失.

$router为VueRouter实例,想要导航到不同URL,则使用$router.push方法

$route为当前router跳转对象,里面可以获取name、path、query、params

六、vue路由有几种模式

Hash: 使用URL的hash值来作为路由。支持所有浏览器。
History: 以来HTML5 History API 和服务器配置。参考官网中HTML5 History模式
Abstract: 支持所有javascript运行模式。如果发现没有浏览器的API,路由会自动强制进入这个模式。

七、如何封装axios

这个还是根据需求来:
1、axios可以使用拦截器做一系列在请求或者响应时需要重复做的工作,比如请求时的token可以在请求拦截中去添加,还有loading等等.
2、在响应拦截器中也可以捕获信息做错误处理,而不需要在每个请求中都去catch.
3、一般请求之类的都会抽离一个api文件夹下面,然后导出封装好的函数供给外界使用,这样的话比较好管理.

八、vue的钩子(生命周期)

1、实例、组件通过new Vue() 创建出来之后会初始化事件和生命周期,然后就会执行beforeCreate钩子函数,这个时候,数据还没有挂载呢,只是一个空壳,无法访问到数据和真实的dom,一般不做操作

2、挂载数据,绑定事件等等,然后执行created函数,这个时候已经可以使用到数据,也可以更改数据,在这里更改数据不会触发updated函数,在这里可以在渲染前倒数第二次更改数据的机会,不会触发其他的钩子函数,一般可以在这里做初始数据的获取

3、接下来开始找实例或者组件对应的模板,编译模板为虚拟dom放入到render函数中准备渲染,然后执行beforeMount钩子函数,在这个函数中虚拟dom已经创建完成,马上就要渲染,在这里也可以更改数据,不会触发updated,在这里可以在渲染前最后一次更改数据的机会,不会触发其他的钩子函数,一般可以在这里做初始数据的获取

4、接下来开始render,渲染出真实dom,然后执行mounted钩子函数,此时,组件已经出现在页面中,数据、真实dom都已经处理好了,事件都已经挂载好了,可以在这里操作真实dom等事情...

5、当组件或实例的数据更改之后,会立即执行beforeUpdate,然后vue的虚拟dom机制会重新构建虚拟dom与上一次的虚拟dom树利用diff算法进行对比之后重新渲染,一般不做什么事儿

6、当更新完成后,执行updated,数据已经更改完成,dom也重新render完成,可以操作更新后的虚拟dom

7、当经过某种途径调用$destroy方法后,立即执行beforeDestroy,一般在这里做一些善后工作,例如清除计时器、清除非指令绑定的事件等等

8、组件的数据绑定、监听...去掉后只剩下dom空壳,这个时候,执行destroyed,在这里做善后工作也可以

九、vue路由模式及区别

  • 前言
前后端分离 ===> 利用Ajax,可以在不刷新浏览器的情况下异步数据请求交互。

单页应用(只有一个html文件,整个网站的所有内容都在这一个html里,通过js来处理)
不仅仅是在页面交互是无刷新的,连页面跳转都是无刷新的。为了实现单页应用 ==> 前后端分离 + 前端路由。
(更新视图但不重新请求页面)

前端路由实现起来其实也很简单,就是匹配不同的 url 路径,进行解析,加载不同的组件,然后动态的渲染出区域 html 内容。
  • hash模式
`hash` 模式是一种把前端路由的路径用井号 `#` 拼接在真实 `url` 后面的模式。当井号 `#` 后面的路径发生变化时,浏览器并不会重新发起请求,而是会触发 `onhashchange` 事件。
使用 URL hash 值来作路由。支持所有浏览器,包括不支持HTML5 History Api的浏览器;
-  hash变化会触发网页跳转,即浏览器的前进和后退。
-  hash` 可以改变 `url` ,但是不会触发页面重新加载(hash的改变是记录在 `window.history` 中),即不会刷新页面。也就是说,所有页面的跳转都是在客户端进行操作。因此,这并不算是一次 `http` 请求,所以这种模式不利于 `SEO` 优化。`hash` 只能修改 `#` 后面的部分,所以只能跳转到与当前 `url` 同文档的 `url` 。
- `hash` 通过 `window.onhashchange` 的方式,来监听 `hash` 的改变,借此实现无刷新跳转的功能。
- `hash` 永远不会提交到 `server` 端(可以理解为只在前端自生自灭)
  • history模式
history API` 是 `H5` 提供的新特性,允许开发者直接更改前端路由,即更新浏览器 `URL` 地址而不重新发起请求。
依赖 HTML5 History API 和服务器配置。

history模式原理
history 路由模式的实现主要基于存在下面几个特性:
- 新的 `url` 可以是与当前 `url` 同源的任意 `url` ,也可以是与当前 `url` 一样的地址,但是这样会导致的一个问题是,会把重复的这一次操作记录到栈当中。
- 通过 `history.state` ,添加任意类型的数据到记录中。
- 可以额外设置 `title` 属性,以便后续使用。
- 通过 `pushState``replaceState` 来实现无刷新跳转的功能
- pushState 和 repalceState 两个 API 来操作实现 URL 的变化 ;
我们可以使用 popstate 事件来监听 url 的变化,从而对页面进行跳转(渲染);
history.pushState() 或 history.replaceState() 不会触发 popstate 事件,
这时我们需要手动触发页面跳转(渲染)。
  • abstract模式
支持所有javascript运行模式。如果发现没有浏览器的API,路由会自动强制进入这个模式。
  • 区别
前面的hashchange,你只能改变#后面的url片段。而pushState设置的新URL可以是与当前URL同源的任意URL。
history模式则会将URL修改得就和正常请求后端的URL一样,如后端nginx没有配置对应/user/id的路由处理,则会返回404错误

history模式怕啥
不怕前进,不怕后退,就怕刷新,(如果后端没有准备的话),因为刷新是实实在在地去请求服务器的。
在history模式下,你可以自由的修改path。history模式最终的路由都体现在url的pathname中,这部分是会传到服务器端的,因此需要服务端对每一个可能的path值都作相应的映射。
因此,在使用 `history` 模式时,需要通过服务端来允许地址可访问,如果没有设置,就很容易导致出现 `404` 的局面。
    hash的路由地址上有 #,history模式没有;
    在做回车刷新的时候,hash模式会加载对应页面,history模式没有;
    hash模式支持低版本浏览器,history不支持,因为是history是h5新增Api;
    hash不会重新加载页面,单页面应用必备;
    history是有历史记录的,h5也新增了history.pushState和replaceState方法用于对历史记录进行修改的功能。
    
    history模式重写后URL路径并不包含原有路径文件的访问地址,比如进入/admin的一个子路由/admin/xxx,刷新后地址只有/admin,所以刷新会404。在生产环境需要配合服务器的转发规则重写,将admin后的路径进行重写。用以支持history模式路由的加载。

image.png routeroute和router的区别

1. $route从当前router跳转对象里面可以获取name、path、query、params等(<router-link>传的参数由 this.$route.query或者 this.$route.params 接收)

2. $router为VueRouter实例。想要导航到不同URL,则使用$router.push方法;返回上一个history也是使用$router.go方法

十、v-model语法糖是怎么实现的

<!-- v-model 只是语法糖而已 --
<!-- v-model 在内部为不同的输入元素使用不同的property并抛出不同的事件 -->
<!-- text和textarea 元素使用value property 和 input事件 -->
<!-- checkbox 和radio使用checked property 和 change事件-->
<!-- select 字段将value 作为prop 并将change 作为事件 -->
<!-- 注意:对于需要使用输入法(如中文、日文、韩文等)的语言,你将会发现v-model不会再输入法\
组合文字过程中得到更新 -->
<input v-model="sth" /> // 这一行等于下一行
<input v-bind:value="sth" v-on:input="sth = $event.target.value" />

十一、data为什么是一个函数而不是一个对象

JavaScript中的对象是引用类型的数据,当多个实例引用同一个对象时,只要一个实例对这个对象进行操作,其他实例中的数据也会发生变化。
而在Vue中,我们更多的是想要复用组件,那就需要每个组件都有自己的数据,这样组件之间才不会相互干扰。
所以组件的数据不能写成对象的形式,而是要写成函数的形式。数据以函数返回值的形式定义,这样当我们每次复用组件的时候,就会返回一个新的data,也就是说每个组件都有自己的私有数据空间,它们各自维护自己的数据,不会干扰其他组件的正常运行。

十二、父子组件生命周期钩子函数执行顺序

<!-- 加载渲染过程 -->
<!-- 父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created ->
子beforeMount -> 子mounted -> 父mounted -->
<!-- 子组件更新过程 -->
<!-- 父beforeUpdate -> 子beforeUpdate -> 子updaed -> 父updated -->
<!-- 父组件跟新过程 -->
<!-- 父beforeUpdate -> 父updated -->
<!-- 销毁过程 -->
<!-- 父beforeDestroy -> 子beforeDestroy -> 子destroyed ->父destroyed -->

十三、实现EventBus(事件总线)

        class EventEmitter {
            constructor() {
                this.cache = {}
            }
            on(name, fn) {
                if(this.cache[name]) {
                    this.cache[name].push(fn)
                } else {
                    this.cache[name] = [fn]
                }
            }
            off(name, fn) {
                let tasks = this.cache(name)
                if(tasks) {
                    const index = tasks.find(f => f === fn || f.callback() === fn) 
                    if(index >= 0) {
                        tasks.splice(index, 1)
                    }
                }
            }
            emit(name, once = false, ...args) {
                if(this.cache[name]) {
                    let tasks = this.cache[name].slice() // 创建副本,如果回调函数内继续注册相同事件,会造成死循环
                    for(let fn of tasks) {
                        fn(...args)
                    }
                    if (once) {
                        delete this.cache[name]
                    }   
                }
            }
        }
        function bindOn() {
            let eventBus = new EventEmitter()
            let fn1 = (name, age) => {
                console.log(`${name} ${age}`)
            }
            eventBus.on('change', fn1)
            console.log(eventBus.cache)
            // eventBus.emit('change', false, '戏命师', 27)
        }

十四、$nextTick概念及使用场景

nextTick 接收一个回调函数作为参数,并将这个回调函数延迟到DOM更新后才执行
因为 vue 采用的异步更新策略,当监听到数据发生变化的时候不会立即去更新DOM,
而是开启一个任务队列,并缓存在同一事件循环中发生的所有数据变更;
这种做法带来的好处就是可以将多次数据更新合并成一次,减少操作DOM的次数,
如果不采用这种方法,假设数据改变100次就要去更新100次DOM,而频繁的DOM更新是很耗性能的;

十五、vue3的变化

1.响应式原理的改变 Vue3.x 使用Proxy取代 Vue2.x 版本的Object.defineProperty.
2. Fragment vue3 允许我们支持多个根节点,vue3会自动将多个标签用fragment包裹.
3. Tree shaking
简单来讲,就是在保持代码运行结果不变的前提下,去除无用的代码
在Vue2中,无论我们使用什么功能,它们最终都会出现在生产代码中。
主要原因是Vue实例在项目中是单例的,捆
绑程序无法检测到该对象的哪些属性在代码中被使用到
而Vue3源码引入tree shaking特性,将全局 API 进行分块。
如果你不使用其某些功能,它们将不会包含在你的基础包中
就是比如你要用watch 就是import {watch} from 'vue' 其他的computed 没用到就不会给你打包减少体积
4.Composition Api
Setup 函数式编程 也叫vue Hook,例如 ref  reactive watch computed toRefs toRaws
    vue3中的hooks其实是函数的写法,就是将文件的一些单独功能的js代码进行抽离出来,放到单独的js文件中。这样其实和我们在vue2中学的mixin比较像。下面我们总结一下如何去书写hooks。

十六、Dom和虚拟Dom的区别

DOM的本质: 浏览器概念,浏览器从服务器端读取html页面,浏览器将html解析成一棵元素嵌套关系的dom树,用对象来表示页面上的元素,并提供操作dom对象的api。

根据参考vue官网的介绍,VNode就是虚拟创建的一个DOM对象,但并没有挂载到页面上,那他的作用是什么? 很简单,那就是个真实DOM做对比,对比两课DOM树的差别来进行局部更新,这样就不用重新渲染整个虚拟DOM树来进行更新,也符合了“数据驱动”的思想,避免了重绘和重排,提高了Web性能。

十七、vite的优势以及和webpack的对比差异

1、快速冷启动
Vite只启动一台静态页面的服务器,不会打包全部项目文件代码,服务器根据客户端的请求加载不同的模块处理,实现按需加载.
而我们所熟知的webpack则是,一开始就将整个项目都打包一遍,再开启`dev-server`,如果项目规模庞大,打包时间必然很长.
默认的构建目标浏览器是能在script标签上支持原生ESM和原生ESM动态导入.
webpack构建的项目基于入口的js文件进行编译,比如entry里的index.js路径.vite是基于src目录底下的index.html进行编译的
<script type="module" src="/src/main.ts"></script>

image.png

2、热模块更新
对于热更新问题,Vite采用`立即编译当前修改文件`的办法,同时结合vite的缓存机制(http缓存 ==》 Vite内置缓存,加载更新后的文件内容,热更新速度巨快.
3.初始化项目速度快.通过传统的vue cli 的vue create命令构建的webpack项目需要下载很多的依赖,所以安装的时间也更长.启动速度也比vite慢很多.

解决跨域

何为跨域?
浏览器从一个域名的网页去请求另一个域名的资源时,域名、端口、协议任一不同,都是跨域。
在前后端分离的模式下,前后端的域名是不一致的,此时就会发生跨域访问问题。在请求的过程中我们要想回去数据一般都是post/get请求,所以..跨域问题出现
post请求:向指定的资源提交要被处理的数据,用于将数据发送给服务器,一般用于修改和写入数据。
get请求和post请求本质上就是TCP链接,并无差别。但是由于HTTP的规定和浏览器/服务器的限制,导致他们在应用过程中体现出一些不同。
区别
(1)post请求更安全(不会作为url的一部分,不会被缓存、保存在服务器日志、以及浏览器浏览记录中,get请求的是静态资源,则会缓存,如果是数据,则不会缓存)
(2)post请求发送的数据更大(get请求有url长度限制,http协议本身不限制,请求长度限制是由浏览器和web服务器决定和设置)
(3)post请求能发送更多的数据类型(get请求只能发送ASCII字符)
(4)传参方式不同(get请求参数通过url传递,post请求放在request body中传递)
(5get请求的是静态资源,则会缓存,如果是数据,则不会缓存
(6get请求产生一个TCP数据包;post请求产生两个TCP数据包(get请求,浏览器会把http header和data一并发送出去,服务器响应200返回数据;post请求,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 返回数据)
 (7)GET请求只能进行url编码(application/x-www-form-urlencoded),POST支持多种编码方式(application/x-www-form-urlencoded 或 multipart/form-data。为二进制数据使用多重编码。)
  • jsonp
JSONP 是 JSON with padding(填充式 JSON 或参数式 JSON)的简写。
JSONP实现跨域请求的原理简单的说,就是动态创建<script>标签,然后利用<script>的src 不受同源策略约束来跨域获取数据。

JSONP 由两部分组成:回调函数和数据。回调函数是当响应到来时应该在页面中调用的函数。回调函数的名字一般是在请求中指定的。而数据就是传入回调函数中的 JSON 数据。

动态创建<script>标签,设置其src,回调函数在src中设置:
var script = document.createElement("script");
script.src = "https://api.douban.com/v2/book/search?q=javascript&count=1&callback=handleResponse";
document.body.insertBefore(script, document.body.firstChild)

function handleResponse(response){
    // 对response数据进行操作代码
}

性能优化

vue(代码编写层面)

路由懒加载: 有效拆分App尺寸、访问才异步加载

const router = createRouter({
    routes: [
        // 借助webpack的import()实现异步组件
        {
            path: '/foo', component: import('./foo.vue')
        }
    ]
})

keep-alive组件缓存页面:避免重复创建组件实例,且能保留缓存组件状态,而且能保存滚动条状态,用户体验会更好

<router-view v-slot="{ Component }">
    <keep-alive>
        <component :is="Component"></component>
    </keep-alive>
</router-view>

v-show 复用DOM:避免重复创建组件

一些耗费大量加载时间的重型组件或者需要频繁切换是否展示状态的时候使用v-show的隐藏和显示代替v-if的删除再创建.

v-for遍历避免同时和v-if在同一标签上同时使用.在vue2里只是出现警告,但在vue3里已经是错误写法了

v-once和v-memo

v-once

不再变化的数据使用v-once, 初次动态渲染后,就视为静态内容了.

v-memo

按条件更新时使用v-memo,下面这个列表只会更新选中状态的变化项
<div v-for="item in list" :key="item.id" v-memo="[item.id === selected]"></div>

长列表性能优化

 虚拟列表大致原理:当列表data中有n个item项,我们只渲染可视区域(比如10条)的item,页面滚动时获取到scrollTop,scrollTop / itemHeight = startIndex(当前滚动了多少条的索引),可视区域的数据 = data.slice(startIndex, startIndex + 10)),将可视区域数据渲染到页面即可。
如果是大数据常列表,可采用虚拟滚动,只渲染少部分区域的内容
一些开源库:
vue-virtual-scroller 原理是它只把展示给用户的那部分渲染出来,比如滚到上面的 dom 就回收掉。这个跟 `iOS` 的 `ReuseableCell` 很像,那个是复用的。
<recycle-scroller 
    class="items"
    :items="items"
    :item-size="24"
    >
    <template v-slot="{ item }">
        <FetchItemView
            :item="item"
            @vote="voteItem(item)"
        />
    </template>
       
</recycle-scroller>

事件的销毁

Vue组件销毁时,会自动解绑它的全部指令及事件监听器,但是仅限于组件本身的事件.对于一些用户自定义的一些事件例如定时器setInterval是不会取消的
export default {
    created() {
        this.timer = setInterval(this.refresh, 2000)
    },
    beforeUnmount() {
        clearinterval(this.timer)
    }
}

图片懒加载

对于图片过多的图片,为了加速页面加载速度,所以很多时候需要将页面未出现在可视区域内的图片先不做加载,等到滚动到可视区域后再去加载.参考项目: vue-lazyload
<img -v-lazy="">

第三方插件按需引入

像element-plus 这样的第三方组件库可以按需引入避免体积太大

服务端渲染/静态网站生成: SSR/SSG

如果SPA应用有首屏渲染慢的问题,可以考虑SSR、SSG方案优化.
SSR(Server Side Render) 是指服务端渲染,当客户端向服务器发出请求,然后运行时动态生成 html 内容并返回给客户端。

SSG(Static Site Generation) 是静态站点生成,解析是在构建时执行的,当发出请求时,html 将静态存储,直接发送回客户端。

通常来说,静态站点在运行时会更快,因为不需要服务端做处理,但缺点是对数据的任何更改都需要在服务端进行完全重建;而服务端渲染则会动态处理数据,不需要进行完全重建。
这两种方式对SEO会更好
  • 请求
1、减少http请求
2、静态资源使用 CDN,可以减少打包体积
  • html
1、将 CSS 放在文件头部,JavaScript 文件放在底部
所有放在 head 标签里的 CSS 和 JS 文件都会堵塞渲染(CSS 不会阻塞 DOM 解析)。
如果这些 CSS 和 JS 需要加载和解析很久的话,那么页面就空白了。
所以 JS 文件要放在底部,等 HTML 解析完了再加载 JS 文件。
2、使用字体图标 iconfont 代替图片图标
  • js
1、善用缓存,不重复加载相同的资源
2、事件委托
document.querySelector('ul').onclick = (event) => {
  const target = event.target
  if (target.nodeName === 'LI') {
    console.log(target.innerHTML)
  }
}
3、防抖节流
  • css
1、减少重绘重排
操作 DOM 的成本是非常高的,而且如果不小心改掉元素的几何属性,就会触发重排和重绘.
1) 浏览器本身的优化策略**

浏览器会维护一个队列,将所有引起重排和重绘的操作都放在这个队列里,当操作达到一定的数量或者时间间隔时,浏览器会批量执行来优化重排过程。这样可以让多次的重排重绘,变成一次。但是有的特殊 style 属性会使这种优化失效,例如offsetTop,scrollTop,clientTop等属性,这些属性都是要实时返回给用户的几何属性或者布局属性,因此浏览器需要立即执行,触发重排返回正确的值。

2) 最小化重排和重绘**
避免设置大量的 style 行内样式。修改单个 DOM 节点的多条语句合并成一个语句来执行。
DOM 元素的动画属性最好设置为 absolute 或者 fixed 定位。
3) css 动画和性能处理**
减少 js 操作元素的样式,使用修改 class 类名方式修改样式。
开启动画的 GPU 加速,渲染计算交给 GPU 处理。
避免频繁计算样式,可以缓存变量,并且在变量中工作。
可以使用 querySelectorAll() 获取的静态集合替代 getElementByXX() 获取的动态集合。
2、降低 CSS 选择器的复杂性
3、雪碧图
4、减少css计算
  • 图片
1、图片延迟加载
在页面中,先不给图片设置路径,只有当图片出现在浏览器的可视区域时,才去加载真正的图片,
这就是延迟加载。对于图片很多的网站来说,一次性加载全部图片,会对用户体验造成很大的影响
<img data-src="https://avatars0.githubusercontent.com/u/22117876?s=460&u=7bd8f32788df6988833da6bd155c3cfbebc68006&v=4">

等页面可见时,使用 JS 加载图片:

const img = document.querySelector('img')
img.src = img.dataset.src

2、图片压缩
3、尽可能利用css3代替图片

axios

一、axios请求中断

axios.get('http://jsonplaceholder.typicode.com/comments', {
  cancelToken: new CancelToken(function executor(c) {
    self.cancel = c
    console.log(c)
    // 这个参数 c 就是CancelToken构造函数里面自带的取消请求的函数,这   里把该函数当参数用
  })
}).then(res => {
  this.items = res.data
}).catch(err => {
  console.log(err)
})
//手速够快就不用写这个定时器了,点击取消获取就可以看到效果了
setTimeout(function () {
  //只要我们去调用了这个cancel()方法,没有完成请求的接口便会停止请求
  self.cancel()
}, 100)
},
//cancelGetMsg 方法跟上面的setTimeout函数是一样的效果,因为手速不够快,   哦不,是因为网速太快,导致我来不及点取消获取按钮,数据就获取成功了
cancelGetMsg () {
// 在这里去判断你的id 1 2 3,你默认是展示的tab1,点击的时候不管你上一   个请求有没有执行完都去调用这个cancel(),
this.cancel()
}

二、ajax和axios的区别

  • 区别
axios是通过promise实现对ajax技术的一种封装,就像jQuery实现封装ajax封装一样.
简单来说,ajax基于web的XMLHttpRequest对象进行封装,而axios是ajax的一种实现手段,返回promise对象.ajax实现了网页的局部数据刷新.axios实现了对ajax的封装.axios是ajax,ajax不只axios

三、Vuex状态持久化

vuex只是在内存保存状态,刷新之后就会丢失,如果要持久化就药存起来。
可以使用localStorage,提交mutation时同时存入localStorage,store中把localstorage中取出来作为初始值。
但是并不是所有状态都需要做持久化,如果需要保存的状态很多,每个提交都要做单独保存处理不够优雅。所以可以使用vuex提供的subscribe方法做订阅处理一些第三方库例如vuex-persist.内部的实现就是订阅mutation的变化做统一处理,通过插件的选项控制哪些需要持久化。

Webpack

一、热更新模块配置

 模块热替换(HMR - Hot Module Replacement)是 webpack 提供的最有用的功能之一。它允许在运行时替换,添加,删除各种模块,而无需进行完全刷新重新加载整个页面,其思路主要有以下几个方面
1、保留在完全重新加载页面时丢失的应用程序的状态
2、只更新改变的内容,以节省开发时间
3、调整样式更加快速,几乎等同于就在浏览器调试器中更改样式

一个带有热替换功能的webpack.config.js 文件的配置如下,做了这么几件事

引入了webpack库
使用了new webpack.HotModuleReplacementPlugin()
设置devServer选项中的hot字段为true
  const path = require('path');
  const HtmlWebpackPlugin = require('html-webpack-plugin');
  const CleanWebpackPlugin = require('clean-webpack-plugin');
  const webpack = require('webpack');

  module.exports = {
    entry: {
      app: './src/index.js'
    },
    devtool: 'inline-source-map',
    devServer: {
      contentBase: './dist',
      hot: true
    },
    plugins: [
      new CleanWebpackPlugin(['dist']),
      new HtmlWebpackPlugin({
        title: 'Hot Module Replacement'
      }),
      new webpack.HotModuleReplacementPlugin()
    ],
    output: {
      filename: '[name].bundle.js',
      path: path.resolve(__dirname, 'dist')
    }
  };


二、css-loader和style-loader的区别和使用

webpack是用JS写的,运行在node环境,所以默认webpack打包的时候只会处理JS之间的依赖关系!!!

因为像 .css 这样的文件不是一个 JavaScript 模块,你需要配置 webpack 使用 css-loader 或者 style-loader 去合理地处理它们。
【Loader】:用于对模块源码的转换,loader描述了webpack如何处理非javascript模块,并且在buld中引入这些依赖。loader可以将文件从不同的语言(如TypeScript)转换为JavaScript,或者将内联图像转换为data URL。比如说:CSS-LoaderStyle-Loader等。例如:
如果在JS中导入了css,那么就需要使用 css-loader 来识别这个模块,通过特定的语法规则进行转换内容最后导出
css-loader会处理 import / require() @import / url 引入的内容。
【Plugin】:目的在于解决loader无法实现的其他事,从打包优化和压缩,到重新定义环境变量,功能强大到可以用来处理各种各样的任务。webpack提供了很多开箱即用的插件:CommonChunkPlugin主要用于提取第三方库和公共模块,避免首屏加载的bundle文件,或者按需加载的bundle文件体积过大,导致加载时间过长,是一把优化的利器。而在多页面应用中,更是能够为每个页面间的应用程序共享代码创建bundle。

三、loader和plugin有什么区别

Loader:直译为"加载器"。Webpack将一切文件视为模块,但是webpack原生是只能解析js文件,如果想将其他文件也打包的话,就会用到`loader`。所以Loader的作用是让webpack拥有了加载和解析非JavaScript文件的能力。Plugin:直译为"插件"。Plugin可以扩展webpack的功能,让webpack具有更多的灵活性。在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。

小程序

一、小程序登录流程 avatar

1、通过 wx.login() 获取到用户的code
2、通过 wx.request() 方法请求我们自己的后端,我们自己的服务端把 appid , appsecret 和 3code 一起发送到微信服务器。 appid 和 appsecret 都是微信提供的,可以在管理员后台找到
微信服务器返回了 openid
4、我们在自己的数据库中,查找 openid ,如果没有查到记录,说明该用户没有注册,如果有记录,则继续往下走
5、我们生成一个第三方 session , 也就是 session_id , 也就是用户唯一标识符。在 redis 中,把 session_id 和用户的身份存进去。
6、返回 3rd_session
7、小程序把 3rd_session 存到 storage 里面
8、下次请求时,先从 storage 里面读取,然后带给服务端
9、服务端从 redis 里面找到 3rd_session 对应的记录,然后校验有效期

二、小程序组件通信

// 父组件
<childA dataChildA=‘{{gotoChildA}}‘/>

/// 子组件
properties: {
    dataChildA:String
},
this.triggerEvent(‘myevent‘,value)//myevent需要与父组件的事件保持一致

// 父组件
 <view>*****我是子组件B*******</view>
 <childB bind:myevent="onMyEvent"/>
 onMyEvent(e){
    console.log(e)
    this.setData({
      paramB:e.detail.val
    })
  },

浏览器

一、浏览器的渲染机制

1、处理HTML并构建DOM树
2、处理CSS构建CSSOM(CSS Object Model -> 将CSS转为CSS对象模型)
3、把DOM和CSSOM结合生成render tree(渲染树)
4、根据渲染树布局,计算每个节点的位置
5、将节点绘制到页面上
    - 回流(重排)
    当render tree中的一些元素的结构或者尺寸等发生改变,浏览器重新渲染部分或者全部文档叫做回流。
    (1)触发条件
    添加或删除可见的DOM元素
    元素的位置发生变化
    元素的尺寸发生变化(包括外边距、内边框、边框大小、高度和宽度等)
    内容发生变化,比如文本变化或图片被另一个不同尺寸的图片所替代
    页面一开始渲染的时候(这避免不了)
    浏览器的窗口尺寸变化(因为回流是根据视口的大小来计算元素的位置和大小的)
    - 重绘
    当DOM的修改导致了样式的变化,并且没有影响几何属性的时候,会导致重绘( repaint)
    (1) 触发条件
    当页面中元素样式的改变不影响它在文档流中的位置,浏览器会将新样式赋予给元素,这个过程叫做重绘,或者是一个元素的外观被改变,(背景色,字体颜色,边框颜色等)

二、从浏览器输入url到页面展示的过程

1、输入网址

2、DNS解析URL
缓存判断:浏览器会判断所请求的资源是否在缓存里,如果请求的资源在缓存里并且没有失效,那么就直接使用,否则向服务器发起新的请求

DNS解析的过程就是寻找哪个服务器上有请求的资源。因为ip地址不容易记忆,一般会使用URL域名(如www.baidu.com)作为网址。DNS解析就是将域名翻译成IP地址的过程。

3、浏览器发送请求与服务器交互的过程

客户端发送HTPP请求

服务器处理请求

服务器响应请求

浏览器对接收到的html页面渲染的过程