vue及rect面试点(更新中) - 面试篇

文章目录

一、事件的执行顺序

事件的整体顺序是:非目标元素捕获 -> 目标元素代码顺序 -> 非目标元素冒泡。
冒泡:从里到外
捕获:从外到里
DOM 对象.addEventListener(事件,事件处理程序,事件冒泡方式)
事件冒泡方式:默认为 false,表示冒泡阶段完成事件处理,true 为捕获阶段完成事件处理

阻止事件冒泡的方式

常用方法:

(1)事件委托:将元素的绑定事件写起其父元素上,防止事件冒泡

(2)event.stopPropagation():可以阻止事件冒泡,阻止父级元素的绑定事件

son.addEventListener("click", function (e) {
  alert("son");
  e.stopPropagation(); //阻止事件冒泡
});

二、事件循环 EventLoop

所有任务分为宏任务(macrotask )和微任务(microtask ) 两种。
MacroTask(宏任务):* script 全部代码、setTimeout、setInterval、setImmediate(浏览器暂时不支持,只有 IE10 支持,具体可见 MDN)、I/O、UI Rendering。

MicroTask(微任务):* Process.nextTick(Node 独有)、Promise、Object.observe(废弃)、MutationObserver(点击这里查看)
事件循环图
事件循环完整图

三、异步编程的方法

1.回调函数

回调函数的优点是简单容易理解部署,缺点是不利于代码的阅读和维护,各个部分之间高度耦合(Coupling),流程会很混乱,而回调函数有一个致命的弱点,就是容易写出回调地狱

function f1(callback) {
  setTimeout(function () {
    // f1的任务代码

    callback();
  }, 1000);
}

2.事件监听

这种方法的优点是比较容易理解,可以绑定多个事件,每个事件可以指定多个回调函数,而且可以"去耦合"(Decoupling),有利于实现模块化。缺点是整个程序都要变成事件驱动型,运行流程会变得很不清晰。

f1.on('done', f2); 当f1发生done事件,就执行f2。

function f1(){

  setTimeout(function () {

    // f1的任务代码
    f1.trigger('done');

  }, 1000);

}

3.发布/订阅 | 观察者模式

jQuery.subscribe("done", f2);

function f1() {
  setTimeout(function () {
    // f1的任务代码
    jQuery.publish("done"); // f1执行完成后,向"信号中心"jQuery发布"done"信号,从而引发f2的执行。
  }, 1000);
}

jQuery.unsubscribe("done", f2); // f2完成执行后,也可以取消订阅(unsubscribe)

4.Promise 对象

手写 promise 代码,需要复制链接
是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。
Promise 对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称 Fulfilled)和 Rejected(已失败)。

promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});

  • promise.all([]) 一个失败,就整个失败
  1. Promise.all
    Promise.all 可以将多个实例组装成一个新的实例,只有所有的 promise 都 resolve 时才会 resolve 所有的结果,任意一个 promise 被 reject ,就会立即被 reject ,并且 reject 的是第一个抛出的错误信息

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

  3. Promise.any 只要其中的一个 promise 成功,就返回那个已经成功的 promise,只有所有的 promise 都 reject 时才会 reject 所有的失败信息

  4. finally() 方法用于指定不管 Promise 对象最后状态如何,都会执行的操作,不管 promise 最后的状态,在执行完 then 或 catch 指定的回调函数以后,都会执行 finally 方法指定的回调函数。

finally 方法的回调函数不接受任何参数,这意味着没有办法知道,前面的 Promise 状态到底是 fulfilled 还是 rejected。这表明,finally 方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。

promise.finally(() => {
  // 语句
});

// 等同于
promise.then(
  (result) => {
    // 语句
    return result;
  },
  (error) => {
    // 语句
    throw error;
  }
);

5.Generator 函数

Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同
Generator 函数有多种理解角度:
语法上,Generator 函数是一个状态机,封装了多个内部状态。执行 Generator 函数会返回一个遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
形式上,Generator 函数是一个普通函数,但是有两个特征。一是,function 关键字与函数名之间有一个星号;二是,函数体内部使用 yield 表达式,定义不同的内部状态。

yield 语句
Generator 函数返回的遍历器对象,yield 语句暂停,调用 next 方法恢复执行,如果没遇到新的 yeild,一直运行到 return 语句为止,return 后面表达式的值作为返回对象的 value 值,如果没有 return 语句,一直运行到结束,返回对象的 value 为 undefined。

function* helloWorldGenerator() {
  // Generator 函数,该函数有三个状态:hello,world 和 return 语句
  yield "hello";
  yield "world";
  return "ending";
}

var hw = helloWorldGenerator();
//Generator 函数的调用,调用后并不执行,而是返回一个指向内容状态的指针对象(即遍历器对象Iterator Object)

// Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行,

hw.next();
// { value: 'hello', done: false }  done为false表示遍历未结束

hw.next();
// { value: 'world', done: false }

hw.next();
// { value: 'ending', done: true }  done为true表示遍历结束

hw.next();
// { value: undefined, done: true }  Generator 函数已经运行完毕,以后再调用next方法,返回的都是这个结果

6.async 与 await

ES2017 提供了 async 函数,使得异步操作变得更加方便。async 函数就是 Generator 函数的语法糖。
async 函数就是将 Generator 函数的星号(*)替换成 async,将 yield 替换成 await,仅此而已。
进一步说,async 函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而 await 命令就是内部 then 命令的语法糖。
async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。当函数执行的时候,一旦遇到 await 就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。

async function timeout(ms) {
  await new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

async function asyncPrint(value, ms) {
  await timeout(ms);
  console.log(value);
}

asyncPrint("hello world", 50);
// 上面代码指定 50 毫秒以后,输出hello world。

四、call、apply、bind 的区别

call、apply、bind 都是改变 this 指向的方法

call fn.call(obj, 1, 2);

  1. 非严格模式:如果不传参数,或者第一个参数是 null 或 undefined,this 都指向 window
  2. 严格模式 strict:第一个参数是谁,this 就指向谁,包括 null 和 undefined,如果不传参数 this 就是 undefined

apply fn.apply(obj, [1, 2]);

apply 把需要传递给 fn 的参数放到一个数组(或者类数组)中传递进去,虽然写的是一个数组,但是也相当于给 fn 一个个的传递

bind

fn.call(obj, 1, 2); // 改变 fn 中的 this,并且把 fn 立即执行
fn.bind(obj, 1, 2); // 改变 fn 中的 this,fn 并不执行

// bind会把fn中的this预处理为obj,此时fn没有执行,当点击的时候才会把fn执行
document.onclick = fn.bind(obj);

// this改变为obj了,但是绑定的时候立即执行,当触发点击事件的时候执行的是fn的返回值undefined
document.onclick = fn.call(obj);

五、vue 和 react 中不被渲染出来的标签

// vue
import Fragment from 'vue-fragment'
Vue.use(Fragment.Plugin)

<template>
// fragment在此做根节点 页面不渲染此标签
	  <fragment>
			<div>test</div>
	</fragment>
</template>

// react
import React from 'react';
import ReactDOM from 'react-dom';
import {BrowserRouter} from 'react-router-dom'

ReactDom.render(
	<React.Fragment>
		hello
	</React.Fragment>
,document.getElementById('root'))

// 或者使用解构赋值,直接拿到Fragment组件,可以直接用Fragment代替React.Fragment
import React, {Fragment}from 'react';
import ReactDOM from 'react-dom';
import {BrowserRouter} from 'react-router-dom'

ReactDOM.render(
	<BrowserRouter>
		<Fragment>
			hello
		</Fragment>
	</BrowserRouter>
,document.getElementById('root'));

// 或者
<> </>

六、js 里 for…in 和 for…of 的用法:

  1. for in 可以遍历对象,for of 不能遍历对象,只能遍历带有 iterator 接口的;
  2. for in 和 for of 都可以循环数组,for in 输出的是数组的 index 下标,而 for of 输出的是数组的每一项的值
  3. for in 适合遍历对象,for of 适合遍历数组。for in 遍历的是数组的索引,对象的属性,以及原型链上的属性

七、keep-alive 生命周期和普通的生命周期区别

多了一个 activated。 销毁的生命周期 deactivated;
正常生命周期是:创建前后、载入前后、更新前后、销毁前后
beforeCreate(创建前)
created(创建后)
beforeMount(载入前),(挂载)
mounted(载入后)
beforeUpdate(更新前)
updated(更新后)
beforeDestroy(销毁前)
destroyed(销毁后)

八、$nextick

可以参考这里
$nextTick 会在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
nextTick 是 vue 框架里面一个全局 api。它是当前异步更新队列循环结束之后的回调函数。
$nextTick 实现原理
在浏览器支持 Promise.then、MutationObserver 和 setImmediate 方法时使用这些方法,不支持则采用 setTimeout(fn, 0)。

  • 场景:
  1. 重新渲染元素时,想拿到元素的属性
  2. 在希望所有子组件加载完成时进行某些操作时,可以采用
  3. 在 created 和 mounted 阶段,如果需要操作渲染后的视图,可以使用 nextTick 方法。

九、虚拟 dom 的理解

原理,查看这里

为什么使用虚拟 dom

虚拟 DOM 不会进行排版与重绘操作
虚拟 DOM 进行频繁修改,然后一次性比较并修改真实 DOM 中需要改的部分(注意!),最后并在真实 DOM 中进行排版与重绘,减少过多 DOM 节点排版与重绘损耗
真实 DOM 频繁排版与重绘的效率是相当低的
虚拟 DOM 有效降低大面积(真实 DOM 节点)的重绘与排版,因为最终与真实 DOM 比较差异,可以只渲染局部

虚拟 dom 的原理

  1. 利用 JavaScript 创建 DOM 树
  2. 树的 diff,同层对比,输出 patchs(listDiff / diffChildren / diffProps)
    1. 没有新的节点,返回
    2. 新的节点 tagName 与 key 不变,对比 props,继续递归遍历子树
      1. 对比属性(对比新旧属性列表)
      2. 都存在的是否有变化
      3. 是否出现旧列表中没有的新属性
    3. tagName 和 key 值变化了,则直接替换成新节点
  3. 渲染差异
    1. 遍历 patchs,把需要更改的节点取出来
    2. 局部更新 DOM

十、mvc 和 mvvm

  1. mvc 是一种设计模式,模型(model)-视图(view)-控制器(controller),三层架构的设计模式。用于实现前端页面的展现与后端业务数据处理的分离。通过把职责、性质相近的成分归结在一起,不相近的进行隔离,MVC 将系统分解为模型、视图、控制器三部分,每一部分都相对独立,职责单一,在实现过程中可以专注于自身的核心逻辑。MVC 是对系统复杂性的一种合理的梳理与切分,它的思想实质就是“关注点分离”。MVC 要实现的目标是将软件用户界面和业务逻辑分离以使代码可扩展性、可复用性、可维护性、灵活性加强。
  2. mvvm 目标思想与 mvp 类似。数据驱动、低耦合度、团队协作、可复用性、单元测试
    Model 指的是后端传递过来的数据
    View 指的是所看到的页面
    ViewModel 指的是连接 模型和视图的桥梁
    区别总结:
    mvc 和 mvvm 其实区别并不大。都是一种设计思想。主要就是 mvc 中 Controller 演变成 mvvm 中的 viewModel。mvvm 主要解决了 mvc 中大量的 DOM 操作使页面渲染性能降低,加载速度变慢,影响用户体验。和当 Model 频繁发生变化,开发者需要主动更新到 View 。

十一、用 ts 的体验

想了解更多,可以点击这里
TypeScript 是 JavaScript 的超集,扩展了 JavaScript 的语法,因此现有的 JavaScript 代码可与 TypeScript 一起工作无需任何修改,TypeScript 通过类型注解提供编译时的静态类型检查。

TypeScript 可处理已有的 JavaScript 代码,并只对其中的 TypeScript 代码进行编译。
TypeScript 会忽略程序中出现的空格、制表符和换行符。空格、制表符通常用来缩进代码,使代码易于阅读和理解。TypeScript 区分大写和小写字符。每行指令都是一段语句,你可以使用分号或不使用, 分号在 TypeScript 中是可选的,建议使用。注释与 JavaScript 使用一致。

  • js 数据类型

    • 基本数据类 存储在栈上
      number,boolean,string,undefined,null
    • 引用数据类型 存储在堆上
      object,function,array
  • TypeScript 基础类型
    any(任意类型):声明为 any 的变量可以赋予任意类型的值。

number(数字类型):双精度 64 位浮点值。它可以用来表示整数和分数。

string(字符串类型):一个字符系列,使用单引号(')或双引号(")来表示字符串类型。反引号(`)来定义多行文本和内嵌表达式。

boolean(布尔类型):表示逻辑值:true 和 false。

数组类型、元组。

enum(枚举类型):枚举类型用于定义数值集合。

void:用于标识方法返回值的类型,表示该方法没有返回值。

null:表示对象值缺失。

undefined:用于初始化变量为一个未定义的值。

never:never 是其它类型(包括 null 和 undefined)的子类型,代表从不会出现的值。

  • TypeScript 变量的命名规则:

1、变量名称可以包含数字和字母。

2、除了下划线 _ 和美元 $ 符号外,不能包含其他特殊字符,包括空格。

3、变量名不能以数字开头。

十二、递归是什么

运行过程中不断调用自己。
它是指一段程序直接或者间接调用自身的一种方法,通过这种形式执行需要进行一些固定步骤的许多操作,它可以把一个复杂并且庞大的问题简单化,通过专注于解决它分化出来的小问题从而解决大问题,从而大大减少我们的代码量,是提高我们编码效率的很好方法。

function fib(n) {
  if (n === 1 || n === 2) return 1;
  return fib(n - 2) + fib(n - 1);
}
console.log(fib(3));

十三、内存泄漏

1. 常见的四种内存泄漏:

  1. 未声明变量
    解决办法:避免创建全局变量;使用严格模式,在 JavaScript 文件头部或者函数的顶部加上 use strict。
function foo(arg){
        bar =“some text”; // bar将泄漏到全局.
    }
  1. 被遗忘的定时器和回调
    解决方案:手动删除定时器和 dom。removeEventListener 移除事件监听
  2. DOM 引用
    解决方法:手动删除,refA = null。
var refA = document.getElementById("refA");
document.body.removeChild(refA); // dom删除了
console.log(refA, "refA"); // 但是还存在引用能console出整个div 没有被回收
  1. 闭包
    原因:闭包可以读取函数内部的变量,然后让这些变量始终保存在内存中。如果在使用结束后没有将局部变量清除,就可能导致内存泄露。
    解决:将事件处理函数定义在外部,解除闭包,或者在定义事件处理函数的外部函数中。
// 注意: 闭包本身没有错,不会引起内存泄漏.而是使用错误导致.
var theThing = null;
var replaceThing = function () {
  var originalThing = theThing;
  var unused = function () {
    if (originalThing) console.log("hi");
  };
  theThing = {
    longStr: new Array(1000000).join("*"),
    someMethod: function () {
      console.log(someMessage);
    },
  };
};
setInterval(replaceThing, 1000);
// 解决: 去除unuserd函数或者在replaceThing函数最后一行加上 originlThing = null.

2. vue 中容易出现内存泄漏的几种情况

  1. 全局变量造成的内存泄露
    声明的全局变量在切换页面的时候没有清空
    解决方法:在页面卸载的时候顺便处理掉该引用
<template>
  <div id="home">这里是首页</div>
</template>
<script>
  export default {
    mounted() {
      window.test = {
        // 此处在全局window对象中引用了本页面的dom对象
        name: 'home',
        node: document.getElementById('home'),
      }
    },
  }
</script>

// 解决方案
destroyed () {
  window.test = null // 页面卸载的时候解除引用
}
  1. 监听在 window/body 等事件没有解绑
    特别注意 window.addEventListener 之类的时间监听
    解决方法:在页面销毁的时候,顺便解除引用,释放内存
<template>
  <div id="home">这里是首页</div>
</template>

<script>
export default {
  mounted () {
    window.addEventListener('resize', this.func) // window对象引用了home页面的方法
  }
}
</script>

// 解决方案
beforeDestroy () {
  window.removeEventListener('resize', this.func)
}
  1. 绑在 EventBus 的事件没有解绑
    解决方法:在页面卸载的时候也可以考虑解除引用
<template>
  <div id="home">这里是首页</div>
</template>

<script>
export default {
  mounted () {
   this.$EventBus.$on('homeTask', res => this.func(res))
  }
}
</script>

// 解决方案
mounted () {
 this.$EventBus.$on('homeTask', res => this.func(res))
},
destroyed () {
 this.$EventBus.$off()
}
  1. ECharts
    每一个图例在没有数据的时候它会创建一个定时器去渲染气泡,页面切换后,echarts 图例是销毁了,但是这个 echarts 的实例还在内存当中,同时它的气泡渲染定时器还在运行。这就导致 Echarts 占用 CPU 高,导致浏览器卡顿,当数据量比较大时甚至浏览器崩溃。
    解决方法:加一个 beforeDestroy()方法释放该页面的 chart 资源。
beforeDestroy () {
  this.chart.clear()
  this.chart.dispose()
}
  1. v-if
    v-if 绑定到 false 的值,但是实际上 dom 元素在隐藏的时候没有被真实的释放掉。
    比如下面的示例中,我们加载了一个带有非常多选项的选择框,然后我们用到了一个显示/隐藏按钮,通过一个 v-if 指令从虚拟 DOM 中添加或移除它。这个示例的问题在于这个 v-if 指令会从 DOM 中移除父级元素,但是我们并没有清除由 Choices.js 新添加的 DOM 片段,从而导致了内存泄漏。
 // 在我们的 Vue 实例的数据对象中设置一个 `choicesSelect` 的引用
        this.choicesSelect = new Choices("#choices-single-default", {
          searchEnabled: true,
          removeItemButton: true,
          choices: list,
        })
      },

// 现在我们可以让 Choices 使用这个引用,从 DOM 中移除这些元素之前进行清理工作
this.choicesSelect.destroy()

垃圾回收机制:

  1. 引用计数
  2. 标记清除

定位泄漏点,可以用 Chrome 的 memory 面板来进行分析
chrome图片

十四、事件委托

参考这里
也可以参考这里
又名事件代理。事件委托就是利用事件冒泡,就是把子元素的事件都绑定到父元素上。如果子元素阻止了事件冒泡,那么委托也就没法实现了
好处:提高性能,减少了事件绑定,从而减少内存占用
可以大量节省内存占用,减少事件注册,比如在 ul 上代理所有 li 的 click 事件就非常棒
可以实现当新增子对象时无需再次对其绑定(动态绑定事件)
jQuery 事件 delegate()实现事件委托

十五、原型链

创建对象有几种方法

// 第一种(字面量)
var obj1 = { name: "obj1" }; // Object {name:'obj1'}
var obj2 = new Object({ name: "obj2" }); // Object {name:'obj2'}

// 第二种(构造函数)
var M = function () {
  this.name = "obj3";
};
var obj3 = new M(); // M {name:'obj3'}

// 第三种(Object.create)
var P = { name: "obj4" };
var obj4 = Object.create(P); // Object {},看不到属性,但是存在。因为它本身是空对象,但是它的原型指向P这个对象。

原型、构造函数、实例、原型链

原型链

// 字面量的方式
var test = {};

声明了一个空对象 test,在没有对这个对象进行任何操作的时候,我们发现,这个对象已经有了很多的属性和方法
看有没有属性

下面我们通过 console.dir 来打印一下看看这个 test 对象,发现 test 有一个默认的属性proto, 恰好这个proto对象里的属性,和 test 的默认属性是一样的
查找原型的方法
test.valueOf 被读取的时候,是连着proto链子一直走的,而这个由proto组成的链子,就被称作原型链

test 对象首先看对象本身上有没有 valueOf 属性,没有的话找 test.__proto__对象里有没有 valueOf 属性,如果没有,接着找 test.__proto__.__proto__里有没有,直到找到 valueOf 或者__proto__属性 null 的时候结束。

  • 官方的概念:
    JavaScript 是面向对象的,每个实例对象都有一个__proto__属性,该属性指向它的原型对象。当一个对象在查找一个属性的时候,自身没有就会根据__proto__向它的原型进行查找,如果都没有,则向它的原型的原型继续查找,直到查到 Object.prototype.proto__为 null,这样也就形成了原型链。

原型对象和实例之间有什么作用呢?

function aa(name) {
  this.name = name;
}

var bb = new aa("测试 1"); // bb.name = 测试 1
var cc = new aa("测试 2"); // cc.name = 测试 2

从上面可以看出,在实例的原型上添加一个方法,这个原型的所有实例便都有了这个方法。

以下可以通过自己打印 aa、bb、cc 去印证,参考:可点击

bb 和 cc 都是实例,aa 就是构造函数
实例通过 new 一个构造函数生成的
实例的 proto 指向的是原型对象
只有函数有 prototype,对象是没有的
但是函数也是有 proto 的,因为函数也是对象。函数的 proto 指向的是 Function.prototype。
实例的构造函数的 prototype 也是指向的原型对象

instanceof 的原理

  • instanceof 判断是否是实例,在原型链上的都会是 true,而用 constructor 只有在本身上才是 true,更加严谨

new 运算符

  • 创建一个新对象。它继承自 foo.prototype
  • 执行 foo 构造函数里的代码。执行时相应的参数被传入,同时 this 会被指定为这个新实例。new foo 等同于 new foo(),只能用在不传递任何参数的情况
  • 如果构造函数返回了一个对象,那么返回这个对象。否则,返回 this
var new2 = function (func) {
  var o = Object.create(func.prototype);
  var k = func.call(o);
  if (typeof k === "object") {
    return k;
  } else {
    return o;
  }
};

面向对象

类与实例

  • 类的声明
    • es5
function Animal() {
  this.name = "name";
}
  • es6
class Animal {
  constructor() {
    this.name = "name";
  }
}
  • 生成实例
new Animal();

类与继承【非常重要,是整个 js 里最难理解的内容】

  • 如何实现继承(es5js 继承本质:原型链)
// 第一种,借助构造函数实现继承(这种无法继承parent原型对象上的方法)
function Parent1(){
    this.name='parent1';
}
Parent1.prototype.say=function(){}
function Child1(){
    Parent1.call(this);// call,apply改变函数执行上下文,即this
    this.type='child1';
}
// 第二种,借助原型链实现继承(这种继承是继承了同一个parent实例,导致修改的也是同一个)
function Parent2(){
    this.name='parent2';
    this.play=[1,2,3]
}
function Child2(){
    this.type='child2';
}
Child2.prototype=new Parent2();
var s1=new Child2();
var s2=new Child2();
s1.play.push(4);
console.log(s1.play,s2.play)// [1,2,3,4],[1,2,3,4]
// 第三种,组合继承方式(组合以上两种,避免了以上两个的问题)(缺点:继承时父级构造函数执行了两遍)
function Parent3(){
    this.name='parent3'
}
function Child3(){
    Parent3.call(this);
    this.type='child3';
}
Child3.prototype=new Parent3();
// 组合继承方式优化1(问题:constructorwei指向Child4)
function Parent4(){
    this.name='parent4'
}
function Child4(){
    Parent4.call(this);
    this.type='child4';
}
Child4.prototype=Parent4.prototype;
var s4=new Child4();
console.log(s4.constructor);// Parent4
// 组合继承方式优化2(最终方案)
function Parent5{
    this.name='parent5'
}
function Child5(){
    Parent5.call(this);
    this.type='child5';
}
Child5.prototype=Object.create(Parent5.prototype);// 隔离父类和子类的原型对象
Child5.prototype.constructor=Child5;// 覆盖自雷的原型对象
  • 继承的几种方式
    • es5 继承
    • es6 继承
    • 【查】class 继承 和 es5 原型链 继承有什么区别?
      可以查看这里

十六、nginx 前端需要配置什么

需要查看此处

server {
        listen       80;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        #匹配用户端,vue vue-router:history项目
        location / {
       root   /home/frontuser/front_project/commercial_service/dist;
       try_files $uri $uri/ /index.html;
        }

        #匹配static
        // 一个的访问路径是根路径:/
       location ^~ /static/ {
            alias /usr/local/static/;
        }

        #匹配后台管理系统,vue,vue-router:history
        // 一个是二级目录:/admin
       location ^~ /admin/ {
       alias   /home/frontuser/front_project/commercial_service_admin/dist/;
       index index.html;
       try_files $uri $uri/ /admin/index.html;
       }
 }

十七、关于 vue 中的源码部分和框架

查看这里 要复制链接

MVVM 框架类

  • 了解 MVVM 框架吗?

    • 引导模式,不要细节全说出来。
  • 谈谈你对 MVVM 的认识?

    • 先聊 MVC,再聊 MVVM 的定义,
  • 双向绑定是什么原理,可以写出来吗?

    • 数据到页面的更新–>Object.defineProperty 的更新回调(以前需要使用模板引擎)
    • 数据劫持
      • 以前let a={name:1}
      • 现在Object.defineProperty('a','name',{set(),get()}),赋值时更新回调
  • 【重要】:

  1. object.defineProperty 的用法要熟记于心
  2. object.defineProperty 与 reflect.defineProperty 的区别
    • 前一个 es5 用法,返回一个新对象。 后一个 es6 用法,返回一个布尔值
  3. object.defineProperty 要会手写
  • 页面到数据的更新–>内置了 input 事件(以前需要自己写 input 事件)

  • 使用了什么设计模式?

  • 观察者设计模式的原理要了如指掌
    • 模式分为几大主体(Observer,Dep,Watcher),角色,作用
    • 最好能写出设计模式的伪代码
    • 如果没有问到设计模式,也要找时机表现出来
  • 生命周期是什么?
    • 说出八个事件点,八个事件点之间的区别
    • 页面性能监控,在 mounted 更合适,测某个点的性能,updated 更合适
  • 有看过源码吗?
    • observer 的作用:在 vue 实例化时调用,把所有的 data 遍历一遍,调用 object.defineProperty,读取时是否添加观察者,数据变化时是否通知观察者。
    • dep 的作用:有一个列表,可以添加,可以通知。
      -watcher:会动态改变 Dep.target,
// 观察者(发布订阅)
class Dep {
  constructor() {
    // 存放所有的watcher
    this.subs = [];
  }
  // 订阅
  addSub(watcher) {
    this.subs.push(watcher);
  }
  // 发布
  notify() {
    this.subs.forEach((watcher) => watcher.update());
  }
}
class Watcher {
  constructor(vm, expr, cb) {
    this.vm = vm;
    this.expr = expr;
    this.cb = cb;
    // 默认先存放老值
    this.oldValue = this.get();
  }
  get() {
    // 先把自己放在this上
    Dep.target = this;
    // 再把这个观察者和数据关联起来
    let value = CompileUtil.getVal(this.vm, this.expr);
    Dep.target = null;
    return value;
  }
  update() {
    let newVal = CompileUtil.getVal(this.vm, this.expr);
    if (newVal !== this.oldValue) {
      this.cb(newVal);
    }
  }
}

// 数据劫持
class Observer {
  constructor(data) {
    this.Observer(data);
  }
  observer(data) {
    // 是对象才观察
    if (data && typeof data == "object") {
      for (let key in data) {
        this.defineReactive(data, key, data[key]);
      }
    }
  }
  defineReactive(obj, key, value) {
    this.observer(value);
    // 给每一个属性都加上一个具有发布订阅的功能
    let dep = new Dep();
    Object.defineProperty(obj, key, {
      get() {
        // 创建watcher时,会取到对应的内容,并且把watcher放到了全局上
        Dep.target && dep.addSub(Dep.target);
        return value;
      },
      set: (newVal) => {
        if (newVal != value) {
          this.observer(newVal);
          value = newVal;
          dep.notify();
        }
      },
    });
  }
}
// 编译模板
class Compiler {
  constructor(el, vm) {
    // 是否是元素节点
    this.el = this.isElementNode(el) ? el : document.querySelector(el);
    // 把节点移到内存中
    let fragment = this.node2fragment(this.el);

    // 把节点中的内容进行替换

    // 用数据编译模板
    this.compile(fragment);
    // 把编译好的内容塞回页面
  }
  compile(node) {
    let childNodes = node.childNodes;
    [...childNodes].forEach((child) => {
      if (this.isElementNode(child)) {
        // 元素节点
        this.compileElement(child);
        // 继续用数据编译元素节点的子节点
        this.compile(child);
      } else {
        // 文本节点
        this.compileText(child);
      }
    });
  }
  compileElement(node) {
    let attributes = node.attributes;
    // 类数组转数组
    [...attributes].forEach((attr) => {
      let { name, value: expr } = attr;
      // 是否是指令
      if (this.isDirective(name)) {
        let [, directive] = name.split("-");
        let [directiveName, eventName] = directive.split(":");
        CompileUtil[directiveName](node, expr, this.vm, eventName);
      }
    });
  }
  compileText(node) {
    // 是否包含{{}}
    let content = node.textContent;
    // 找到所有文本
    if (/\{\{(.+?)\}\}/.test(content)) {
      CompileUtil["text"](node, expr, vm);
    }
  }
  isDirective(attrName) {
    return attrName.startWith("v-");
  }
  isElementNode(node) {
    return node.nodeType === 1;
  }
  node2fragment(node) {
    let fragment = document.createDocumentFragment();
    let firstChild;
    while ((firstChild = node.firstChild)) {
      fragment.appendChild(firstChild);
    }
    return fragment;
  }
}
CompileUtil = {
  getVal(vm, expr) {
    let arr = expr.split(".");
    if (arr.length === 0) return vm.$data[expr];
    return arr.reduce((data, current) => {
      return data[current];
    }, vm.$data);
  },
  setValue(vm, expr, value) {
    return expr.split(".").reduce((data, current, index, arr) => {
      if (index == arr.length - 1) {
        return (data[current] = value);
      }
      return data[current];
    }, vm.$data);
  },
  model(node, expr, vm) {
    // node节点 expr表达式 vm当前实例
    // v-model的输入框赋值数据 node.value=xxx
    let fn = this.updater["modelUpdater"];
    // 给输入框加一个观察者,如果稍后数据更新了会触发此方法,会拿新值给输入框赋值
    new Watcher(vm, expr, (newVal) => {
      fn(node, newVal);
    });
    // 页面变化更新数据变化
    node.addEventListener("input", (e) => {
      let value = e.target.value;
      this.setValue(vm, expr, value);
    });
    // 根据表达式取数据
    let value = this.getVal(vm, expr);
    fn(node, value);
  },
  html(node, expr, vm) {
    // node节点 expr表达式 vm当前实例
    // v-model的输入框赋值数据 node.value=xxx
    let fn = this.updater["htmlUpdater"];
    // 给输入框加一个观察者,如果稍后数据更新了会触发此方法,会拿新值给输入框赋值
    new Watcher(vm, expr, (newVal) => {
      fn(node, newVal);
    });
    // 根据表达式取数据
    let value = this.getVal(vm, expr);
    fn(node, value);
  },
  getContentValue(vm, expr) {
    // 遍历表达式,将内容重新替换成完整的内容,返回
    return expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
      return this.getVal(vm, args[1]);
    });
  },
  on(node, expr, vm, eventName) {
    node.addEventListener(eventName, (e) => {
      vm[expr].call(vm, e);
    });
  },
  text(node, expr, vm) {
    let fn = this.updater["textUpdater"];
    let content = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
      // 给表达式每个人都加上观察者
      new Watcher(vm, args[1], () => {
        // 返回一个全的字符串
        this.getContentValue(vm, expr);
      });
      return this.getVal(vm, args[1]);
    });
    fn(node, content);
  },
  updater: {
    htmlUpdater(node, value) {
      node.innerHTML = value;
    },
    modelUpdater() {
      node.value = value;
    },
    htmlUpdater() {},
    textUpdater(node, value) {
      node.textContent = value;
    },
  },
};
// 基类 调度
class Vue {
  constructor(options) {
    // vue的$el,$data,$option实例方法
    this.$el = options.el;
    this.$data = options.data;
    let computed = options.computed;
    let methods = options.methods;
    if (this.$el) {
      // 数据劫持(把数据全部转化成用Object.defineProperty来定义)
      new IntersectionObserver(this.$data);
      // computed实现
      for (let key in computed) {
        Object.defineProperty(this.$data, key, {
          get() {
            return computed[key].call(this);
          },
        });
      }
      // methods实现
      for (let key in methods) {
        Object.defineProperty(this, key, {
          get: () => {
            return methods[key];
          },
        });
      }
      // 把数据获取操作 VM上的取值操作都代理到vm.$data
      this.proxyVm(this.$data);

      // 编译模板
      new Compiler(this.$el, this);
    }
  }
  // 实现了可以通过vm取到对应的值,直接拿值
  proxyVm(data) {
    for (let key in data) {
      Object.defineProperty(this, key, {
        get() {
          // 进行了转化操作
          return data[key];
        },
        // 设置代理方法
        set(newVal) {
          data[key] = newVal;
        },
      });
    }
  }
}

十八、flyio 和 axios 的比较

axios 的代码
具体参考这里
flyio 的使用和代码

Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。
可以提供以下服务:
1、从浏览器中创建XMLHttpRequests
2、从node.js创建http请求
3、支持PromiseAPI
4、拦截请求和响应
5、转换请求数据和响应数据
6、取消请求
7、自动转换JSON数据
8、客户端支持防御XSRF

Fly.js 是一个基于 promise 的,轻量且强大的Javascript http 网络库,它有如下特点:

提供统一的 Promise API。
浏览器环境下,轻量且非常轻量 。
支持多种JavaScript 运行环境
支持请求/响应拦截器。
自动转换 JSON 数据。
支持切换底层 Http Engine,可轻松适配各种运行环境。
浏览器端支持全局Ajax拦截 。
H5页面内嵌到原生 APP 中时,支持将 http 请求转发到 Native。支持直接请求图片。

共同点:

1,都支持 Promise API,

2,都同时支持 Node 和 Browser 环境

3,都支持请求/响应拦截器

4,都支持自动转换 JSON

不同点

浏览器环境下两者功能不分伯仲,最大的不同是大小,fly.min.js 只有 4K 左右,而 axios.min.js 12K 左右。Fly 更轻量,集成成本更低

十九、正则的方法

字符串的正则方法
5 个方法:match()、replace()、search()、split()、matchAll()

二十、实例方法

es6 新增的查看这里 要复制链接
== es5 ==

  1. conact 合并
  2. join 把数组中的所有元素转换一个字符串
  3. slice 截取,以新的字符串返回被提取的部分
  4. unshift 可向数组的开头添加一个或更多元素,并返回新的长度
  5. push 新项添加到数组末尾
  6. shift 把数组的第一个元素从其中删除,并返回第一个元素的值
  7. pop 移除数组末尾的元素
  8. splice array.splice(index,‘删除多少元素’,‘要添加到数组的新元素’,item1,…,itemX)
  9. sort 排序 array.sort(‘规定排序顺序。必须是函数’),默认排序顺序为按字母升序
  10. reverse 翻转,用于颠倒数组中元素的顺序。
  11. 对数组操作:map(遍历,创建新数组)、forEach(遍历所有元素)、reducer(升序执行,求和,无初始值会报错)
  12. 筛选:filter(过滤)、every(都满足,返回 true)、some(一个满足,返回 true)、indexOf(从开头开始比较,符合返回-1)、lastIndexOf

es6

1、array.forEach()
循环遍历数组中的每一项

let arr = [1, 2, 3];
array.forEach((item, index) => {
  //数组操作 不能return 值
});

2、array.map()
map 方法和 forEach 每次执行匿名函数都支持 3 个参数,参数分别是 item(当前每一项)、index(索引值)、arr(原数组),但是 map 返回一个新数组,原数组不影响;

let arr = [1, 2, 3];
let arr2 = arr.map((iitem, index) => {
  if (item == 1) {
    return true;
  } else {
    return false; //通过return 返回想要的东西
  }
});

结果 arr2 = [true,false,false] arr = [1,2,3]
3、array.filter
筛选数组中符合条件的项,返回一个新数组

let arr = [1,2,4];
let result = arr.filter((item,index)=>{
  return item>2;
}

结果 result 为 [4]
4、array.some()和 array.every()
想执行一个数组是否满足什么条件,返回一个布尔值,这时 forEach 和 map 就不行了,可以用一般的 for 循环实现,或者用 array.every()或者 array.some();
(1)array.some() 类似于或 some()方法用于检测数组中的元素是否有满足条件的,若满足返回 true,否则返回 false
注意:
1、不会对空数组检测
2、不会改变原始数组

let arr = [1,2,4];
let result = arr.some((item,index)=>{
  return item>2;
}

结果 result 为 true
(2) array.every() 类似于与
用于检测数组中所有元素是否都满足条件,若满足返回 true,否则返回 false

let arr = [1,2,4];
let result = arr.every((item,index)=>{
  return item>2;
}

结果 result 为 false
5、array.find()
find()方法只会找到第一个符合的,找到之后就会直接返回,就算下面还有符合要求的,也不会再找下去

let arr = [1,1,2,4];
let result = arr.find((item,index)=>{
  return item>=2;
}

结果 result 为 2
6、array.reduce()
reduce((sum,item)=>{…},0)要有两个参数,第一个参数一定要初始化

let arr = [{name:‘张三’,index:0},{name:‘李四’,index:1}];
let result = arr.reduce((array,item)=>{
  array.push(item.name)
  return array;;
},[ ]

结果 result 为[‘张三’,‘李四’]

二十一、实现一个深拷贝,是递归的方式

function deepCopy(obj) {
  //递归跳出去的条件,不加的话就相当于死循环
  if (typeof obj != "object") {
    return obj;
  }
  var newObj;
  if (obj instanceof Array) {
    newObj = [];
  } else {
    newObj = {};
  }
  //将obj身上的所有属性复制到newObj身上
  for (var attr in obj) {
    //自己调用自己  (递归)
    newObj[attr] = deepCopy(obj[attr]);
  }
  return newObj;
}

二十二、typeof 和 instanceof、Object.prototype.toString.call()。

typeof

  • Typeof
    • 对于基本类型,除了 null 都可以显示正确的类型;
    • 对于引用类型,除了函数都会显示 Object。
    • 对于 null,会显示 Object,这是一个存在很久的 bug。
typeof ""; // string 有效
typeof 1; // number 有效
typeof Symbol(); // symbol 有效
typeof true; //boolean 有效
typeof undefined; //undefined 有效
typeof null; //object 无效
typeof []; //object 无效
typeof new Function(); // function 有效
typeof new Date(); //object 无效
typeof new RegExp(); //object 无效

typeof 原理:js 在底层存储变量的时候,会在变量的机器码的低位 1-3 位存储其类型信息 👉

000:对象
010:浮点数
100:字符串
110:布尔
1:整数

but, 对于 undefined 和 null 来说,这两个值的信息存储是有点特殊的。
null:所有机器码均为 0
undefined:用 −2^30 整数来表示
所以,typeof 在判断 null 的时候就出现问题了,由于 null 的所有机器码均为 0,因此直接被当做了对象来看待。

Object.prototype.toString.call() 方法,判断某个对象之属于哪种内置类型。

Object.toString.call(Array); //"function Array() { [native code] }"
Object.prototype.toString.call(Array); //"[object Function]"

Object.toString(); //"function Object() { [native code] }"
Object.prototype.toString(); //"[object Object]"

分为 null、string、boolean、number、undefined、array、function、object、date、math。

instanceof

  • instanceOf
    • 可以正确的判读对象的类型,因为内部机制是通过判断对象的原型链中是不是能找到类型的 prototype。
    • instanceof 只能用来判断两个对象是否属于实例关系, 而不能判断一个对象实例具体属于哪种类型。

二十三、对箭头函数的理解;

参考地址

  1. 没有 this
  2. 没有 arguments
  3. 不能通过 new 关键字调用
  4. 没有 new.target
  5. 没有原型
  6. 没有 super

二十四、前端要注意哪些 seo

  1. 合理的 title,description,keywords;
  2. 语义化的 html 代码;
  3. 重要的内容放在前面;
  4. 少用 ifram,搜索引擎不会抓取 ifram 的内容;
  5. 非装饰性图片必须加 alt;
  6. 提高网站速度;

二十五、new 操作符干了什么?

创建一个空对象,并在 this 变量中引用这个对象,同时还继承了该对象的原型;属性和方法被加入到 this 引用的对象中;新创建的对象由 this 所引用,并且最后隐式的返回 this。

New 操作符的步骤:创建一个对象;链接到原型;绑定 this;返回一个新对象。

可以说 new 是用来做继承的,而创建对象的其实是 Object.create(null)。 在 new 操作符的作用下,我们使用新创建的对象去继承了他的构造函数上的属性和方法、以及他的原型链上的属性和方法

function create(Con, ...args) {
  // 创建一个空的对象
  let obj = Object.create(null);
  // 将空对象指向构造函数的原型链
  Object.setPrototypeOf(obj, Con.prototype);
  // obj绑定到构造函数上,便可以访问构造函数中的属性,即obj.Con(args)
  let result = Con.apply(obj, args);
  // 如果返回的result是一个对象则返回
  // new方法失效,否则返回obj
  return result instanceof Object ? result : obj;
}

// 测试
function company(name, address) {
  this.name = name;
  this.address = address;
}

var company1 = create(company, "yideng", "beijing");
console.log("company1: ", company1);

二十六、项目的性能优化

可以点击这里查看
较全的,可以看这里

1. 加载性能

  1. 资源内容实用 cdn 加载,请求开启 Gzip 压缩(可以通过向 HTTP 请求头中的 Accept-Encoding 头添加 gzip 标识来开启这一功能)
  2. 减少 http 请求次数
  3. 对静态资源进行优化,缩小请求体积,减少响应时间
  4. 按需加载资源

2. 项目性能优化

  1. 页面渲染
  2. 媒体资源进行压缩
  3. 渲染完成后的页面交互优化
  4. 主流程页面添加骨架屏加载
  5. 图片尽量用 svg 或字体图标代替

3. 配置方面

  1. webpack 模块打包和 js、css 压缩

检查加载性能

一个网站加载性能如何主要看白屏时间和首屏时间。

白屏时间:指从输入网址,到页面开始显示内容的时间。
首屏时间:指从输入网址,到页面完全渲染的时间。
将以下脚本放在 </head> 前面就能获取白屏时间。

<script>
  new Date() - performance.timing.navigationStart // 通过 domLoading 和
  navigationStart 也可以 performance.timing.domLoading -
  performance.timing.navigationStart
</script>

window.onload 事件里执行 new Date() - performance.timing.navigationStart 即可获取首屏时间。

二十七、为什么将 CSS 放在文件头部,JavaScript 文件放在底部

CSS 执行会阻塞渲染,阻止 JS 执行
JS 加载和执行会阻塞 HTML 解析,阻止 CSSOM 构建
如果这些 CSS、JS 标签放在 HEAD 标签里,并且需要加载和解析很久的话,那么页面就空白了。所以 JS 文件要放在底部(不阻止 DOM 解析,但会阻塞渲染),等 HTML 解析完了再加载 JS 文件,尽早向用户呈现页面的内容。

那为什么 CSS 文件还要放在头部呢?

因为先加载 HTML 再加载 CSS,会让用户第一时间看到的页面是没有样式的、“丑陋”的,为了避免这种情况发生,就要将 CSS 文件放在头部了。

另外,JS 文件也不是不可以放在头部,只要给 script 标签加上 defer 属性就可以了,异步下载,延迟执行。

二十八、浏览器渲染过程

解析 HTML 生成 DOM 树。
解析 CSS 生成 CSSOM 规则树。
将 DOM 树与 CSSOM 规则树合并在一起生成渲染树。
遍历渲染树开始布局,计算每个节点的位置大小信息。
将渲染树每个节点绘制到屏幕。
图片地址

二十九、 web workers

Web Worker 使用其他工作线程从而独立于主线程之外,它可以执行任务而不干扰用户界面。一个 worker 可以将消息发送到创建它的 JavaScript 代码, 通过将消息发送到该代码指定的事件处理程序(反之亦然)。

Web Worker 适用于那些处理纯数据,或者与浏览器 UI 无关的长时间运行脚本。

创建一个新的 worker 很简单,指定一个脚本的 URI 来执行 worker 线程(main.js):

var myWorker = new Worker("worker.js");
// 你可以通过postMessage() 方法和onmessage事件向worker发送消息。
first.onchange = function () {
  myWorker.postMessage([first.value, second.value]);
  console.log("Message posted to worker");
};

second.onchange = function () {
  myWorker.postMessage([first.value, second.value]);
  console.log("Message posted to worker");
};

在 worker 中接收到消息后,我们可以写一个事件处理函数代码作为响应(worker.js):

onmessage = function (e) {
  console.log("Message received from main script");
  var workerResult = "Result: " + e.data[0] * e.data[1];
  console.log("Posting message back to main script");
  postMessage(workerResult);
};

onmessage 处理函数在接收到消息后马上执行,代码中消息本身作为事件的 data 属性进行使用。这里我们简单的对这 2 个数字作乘法处理并再次使用 postMessage()方法,将结果回传给主线程。

回到主线程,我们再次使用 onmessage 以响应 worker 回传的消息:

myWorker.onmessage = function (e) {
  result.textContent = e.data;
  console.log("Message received from worker");
};

在这里我们获取消息事件的 data,并且将它设置为 result 的 textContent,所以用户可以直接看到运算的结果。

不过在 worker 内,不能直接操作 DOM 节点,也不能使用 window 对象的默认方法和属性。然而你可以使用大量 window 对象之下的东西,包括 WebSockets,IndexedDB 以及 FireFox OS 专用的 Data Store API 等数据存储机制。

参考资料

三十、react 的 update 更新原理

  • class Counter extends React.Component中的 Component 主要提供了一个setState方法,this._currentUnit.update 调用的是当前类的 update 函数进行更新
class Component {
  constructor(props) {
    this.props = props;
  }
  setState(partialState) {
    // 第一个参数是新的元素 第二个参数是新的状态
    this._currentUnit.update(null, partialState);
  }
}
  • setState&&组件更新 更新过程
    • setState 实际是触发 CompositeUnit 类的 update 方法,传入 setState 要变更的状态,通过 CompositeUnit 类的 update 函数,把之前的状态和传递的状态进行合并this._componentInstance.state = Object.assign(this._componentInstance.state,partialState),在新老元素进行比较的时候更新 state 的变化
    • shouldDeepCompare(preRenderedElement,nextRenderElement)新老元素进行比较,这里比较的主要是标签类型,当类型相同的时候,调用上一个渲染单元的 update(传入当前更新的组件),若组件的标签不相同的时候 直接砍掉 重新创建 unit
    • 文本/类/原生 dom 都有 update 更新
      • TextUnit 比较简单 直接通过 reactid 找到对应的文本 替换文本内容
      • CompositeUnit 进来就是递归,标签名相同就再次 update,若不同就砍掉重新创建
      • NativeUnit 则就是核心,分两步比较 第一步是标签属性的比较,第二步是元素之间的比较(也就是 DOMDiff)

多次调用 setState,会执行几次

点击完按钮后,页面显示 count 值是 1,同时也只打印了 1 个 render,说明在这过程中 React 只执行了一次 setState,只执行了一次 render()渲染操作。
React 内部将同一事件响应函数中的多个 setState 进行合并,减少 setState 的调用次数,也就能减少渲染的次数,提高性能。

会执行哪一次

React 在合并多个 setState 时,若出现同名属性,会将后面的同名属性覆盖掉前面的同名属性。可以这么理解,对于同名属性,最终执行的的是最后的 setState 中的属性。

两个 setState 放在 setTimeout 中?

点击按钮后,count 的值最终变成了 3,也就+1 和+2 的操作都执行了,render()也执行了 2 次。
React 的合成事件和生命周期函数中直接调用 setState,会交由 React 的性能优化机制管理,合并多个 setState。而在原生事件、setTimeout 中调用 setState,是不受 React 管理的,故并不会合并多个 setState,写了几次 setState,就会调用几次 setState。

总结

在 React 中直接使用的事件,如 onChange、onClick 等,都是由 React 封装后的事件,是合成事件,由 React 管理。

React 对于合成事件和生命周期函数,有一套性能优化机制,会合并多个 setState,若出现同名属性,会将后面的同名属性覆盖掉前面的同名属性。

若越过 React 的性能优化机制,在原生事件、setTimeout 中使用 setState,就不归 React 管理了,写了几次 setState,就会调用几次 setState。

class Counter extends React.Component{
  constructor(props){
    super(props);
    this.state = {number:0}
  }
  componentWillMount(){
    console.log('Counter componentWillMount')
  }
  componentDidMount(){
    // console.log('Counter componentDidMount')
  }
  shouldComponentUpdate(nextProps,nextState){
    return true
  }
  componentDidUpdate(){
    // console.log('Counter componentDidUpdate')
  }
  handleClick = ()=>{
    this.setState({number:this.state.number+1})
  }
  render(){
    let p = React.createElement('P',{},this.state.number)
    let button = React.createElement('button',{onClick:this.handleClick},'+')
    return React.createElement('div',{style:{color:this.state.number%2===0?"red":"green",background:this.state.number%2===0?"green":"red"}},p,button)
  }
}
let element1 = React.createElement(Counter,{name:'计数器'})
React.render(element1,document.getElementById('root'))

// CompositeUnit 的 update
 update(nextElement,partialState){
    // 先获取到新元素
    this._currentElement = nextElement
    // 获取新的状态,不管要不要更新组件,组件的状态一定要修改
    // 前面操作的setState 在这个地方进行了处理 下面调用render的时候 就是最新的state
    let nextState = this._componentInstance.state = Object.assign(this._componentInstance.state,partialState);
    // 新的属性对象
    let nextProps = this._currentElement
    if(this._componentInstance.shouldComponentUpdate && !this._componentInstance.shouldComponentUpdate(nextProps,nextState)){
      return;
    }
    // 下面要进行比较更新  先得到上次渲染的单元
    let preRenderedUnitInstance = this._renderedUnitInstance

    // 得到上次渲染的元素
    let preRenderedElement  = preRenderedUnitInstance._currentElement
    // 更新后的元素
    let nextRenderElement = this._componentInstance.render();
    // 如果新旧两个元素类型一样,则可以进行深度比较,如果不一样,直接干掉老的元素,新建新的
    if(shouldDeepCompare(preRenderedElement,nextRenderElement)){
      // 如果可以进行深比较,则把更新的工作交给上次渲染出来的那个element元素对应的unit来处理
      // preRenderedUnitInstance 这是老的unit
      preRenderedUnitInstance.update(nextRenderElement)
      // 更新完成
      this._componentInstance.componentDidUpdate&&this._componentInstance.componentDidUpdate()
    }else{
      this._renderedUnitInstance = createUnit(nextRenderElement);
      let nextMarkUp = this._renderedUnitInstance.getMarkUp(this._reactid)
      $(`[data-reactid]="${this._reactid}"`).replaceWith(nextMarkUp)
    }
  }

// TextUnit 的 update
  update(nextElement){
    if(this._currentElement !== nextElement){
      this._currentElement = nextElement
      $(`[data-reactid="${this._reactid}"]`).html(this._currentElement)
    }
  }
// NativeUnit 的 update
  update(nextElement){
    // console.log(nextElement)
    let oldProps = this._currentElement.props;
    let newProps = nextElement.props
    // 比较新老属性  在原来的基础上对属性进行增删
    this.updateDOMProperties(oldProps,newProps)
    // this.updateDOMChildren(nextElement.props.children);
  }

三十一、组件通信 react 及 vue

react

方法实践,可以查看这里

  1. ⽗组件向⼦组件通讯
    ⽗组件可以通过向⼦组件传 props 的⽅式来实现父到子的通讯。

  2. ⼦组件向⽗组件通讯
    可以采用 props + 回调 的⽅式。
    当⽗组件向⼦组件传递 props 进⾏通讯时,可在该 props 中传递一个回调函数,当⼦组件调⽤该函数时,可将⼦组件中想要传递给父组件的信息作为参数传递给该函数。由于 props 中的函数作⽤域为⽗组件⾃身,因此可以通过该函数内的 setState 更新到⽗组件上。

  3. 兄弟组件通信
    可以通过兄弟节点的共同⽗节点,再结合以上 2 种⽅式,由⽗节点转发信息,实现兄弟间通信。

  4. 跨层级通信
    可以采用 React 中的 Context 来实现跨越多层的全局数据通信。
    Context 设计的⽬的是为在⼀个组件树中共享 “全局” 数据,如:当前已登录的⽤户、界面主题、界面语⾔等信息。

  5. 发布订阅模式
    发布者发布事件,订阅者监听到事件后做出反应。
    我们可以通过引⼊ event 模块进⾏此种方式的通信。

  6. 全局状态管理⼯具
    可以借助 Redux 或 Mobx 等全局状态管理⼯具进⾏通信,它们会维护⼀个全局状态中⼼(Store),并可以根据不同的事件产⽣新的状态。

vue

代码例子,查看这里
props emit | $attrs $listeners | $parent $children $ref | $provider $inject | eventBus | vuex

  1. prop 和$emit 父组件向子组件传递属性和方法通过 prop,子组件触发父组件方法,通过$emit调用
  2. 同步数据 v-model .sync
  3. $attrs和$listeners,如果 props 里用了,attrs 就会减少。未使用的会添加在组件的属性中显示出来。如果不希望没有用的属性增加到 dom 元素上,加上 inheritAttrs:false Vue2.4 开始提供$attrs所有属性和$listeners所有事件方法
  4. $parent,$children, 衍生出来封装$dispatch(elementUI简化$parent.$parent.$parent这样的操作,只要父级有该方法就执行)$broadcast($children.$children.$children这样的操作,只要子级有该方法就行)这两个方法
  5. $refs
    父组件中给子组件添加 ref 属性,获取子组件实例
  6. provide 和 inject 父组件提供数据,子组件获取数据,但不知道数据是哪里的来源
  7. eventBus 兄弟组件数据传递,在任何组件中发布,在其他组件中调用。可以任意组件间通信,只适合小规模(大规模不好维护 一呼百应)
  8. vuex 状态管理 大规模
// 4
Vue.prototype.$diapatch = function (eventName, value) {
  let parent = this.$parent;
  while (parent) {
    parent.$emit(eventName, value);
    parent = parent.$parent;
  }
};
Vue.prototype.$broadcast = function (eventName, componentName, value) {
  let children = this.$children;
  function broadcast(children) {
    for (let i = 0; i < children.length; i++) {
      let child = children[i];
      if (componentName === child.$options.name) {
        child.$emit(eventName, value);
        return;
      } else {
        if (child.$children) {
          broadcast(child.$children);
        }
      }
    }
  }
  broadcast(children);
};
// eventBus
Vue.prototype.$bus = new Vue({});
this.$bus.$on();
this.$bus.$emit();

子组件触发父级的方法

this.$attrs 获取当前组件所有的属性
this.$listeners 获取当前组件所有的绑定事件
v-bind=$attrs 绑定所有的属性
v-on=$listeners 绑定所有的方法

组件的某个子元素触发父级方法

(调用自己属性上的父级的方法),有三种方法。
@click="$listeners.click()"
@click="this.$emit('click')"
v-on="$listeners"

三十二、小程序

小程序的生命周期:

onLoad - onShow - onReady - onHide - onUnload

先调 onready 还是 onshow?

onload 先,然后到 onshow,最后 onready

A 到 B 再返回,onload 还会调么?

不会。onLoad: 页面加载。一个页面只会调用一次

点击方法

wx.navigateTo():保留当前页面,跳转到应用内的某个页面。但是不能跳到 tabbar 页面
wx.redirectTo():关闭当前页面,跳转到应用内的某个页面。但是不允许跳转到 tabbar 页面
wx.switchTab():跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面 或用户切换 Tab
wx.navigateBack():关闭当前页面,返回上一页面或多级页面。可通过 getCurrentPages() 获取当前的页面栈,决定需要返回几层 或用户按左上角返回按钮
wx.reLaunch():关闭所有页面,打开到应用内的某个页面
replace、push、go

bindtap 和 catchtap 的区别是什么

事件绑定的写法同组件的属性,以 key、value 的形式。
相同点:首先他们都是作为点击事件函数,就是点击时触发。在这个作用上他们是一样的,可以不做区分
不同点:他们的不同点主要是 bindtap 是不会阻止冒泡事件的,catchtap 是阻止冒泡的

uniapp

uniapp 的配置文件、入口文件、主组件、页面管理部分

pages.json 配置文件

main.js 入口文件

App.vue 主组件

pages 页面管理部分

如何让图片宽度不变,高度自动变化,保持原图宽高比不变?

给 image 标签添加 mode=‘widthFix’

uni-app 的优缺点

优点:
a. 一套代码可以生成多端
b. 学习成本低,语法是 vue 的,组件是小程序的
c. 拓展能力强
d. 使用 HBuilderX 开发,支持 vue 语法
e. 突破了系统对 H5 调用原生能力的限制
缺点:
a. 问世时间短,很多地方不完善
b. 社区不大
c. 官方对问题的反馈不及时
d. 在 Android 平台上比微信小程序和 iOS 差
e. 文件命名受限

分别写出 jQuery、vue、小程序、uni-app 中的本地存储数据和接受数据是什么?

jQuery:
存:.cookie(‘key’, ‘value’) 取:.cookie( ‘key’)
vue:
存储:localstorage.setItem(‘key’,‘value’)
接收:localstorage.getItem(‘key’)

微信小程序:
存储:通过 wx.setStorage/wx.setStorageSync 写数据到缓存
接收:通过 wx.getStorage/wx.getStorageSync 读取本地缓存,

uni-app:
存储:uni.setStorage({key:“属性名”,data:“值”})
接收:uni.getStorage({key:“属性名”,success(e){e.data//这就是你想要取的 token}})

vue,小程序,uni-app 的生命周期

vue:
beforeCreate(创建前)
created(创建后)
beforeMount(载入前),(挂载)
mounted(载入后)
beforeUpdate(更新前)
updated(更新后)
beforeDestroy(销毁前)
destroyed(销毁后)
小程序,uni-app:

  1. onLoad:首次进入页面加载时触发,可以在 onLoad 的参数中获取打开当前页面路径中的参数。
  2. onShow:加载完成后、后台切到前台或重新进入页面时触发
  3. onReady:页面首次渲染完成时触发
  4. onHide:从前台切到后台或进入其他页面触发
  5. onUnload:页面卸载时触发
  6. onPullDownRefresh:监听用户下拉动作
  7. onReachBottom:页面上拉触底事件的处理函数
  8. onShareAppMessage:用户点击右上角转发

git 是什么?git 的五个命令,git 和 svn 的区别

git 是什么
git 是目前世界上最先进的分布式管理系统。

git 的常用命令
1、 git init 把这个目录变成 git 可以管理的仓库
2、 git add README.md 文件添加到仓库
3、 git add 不但可以跟单一文件,也可以跟通配符,更可以跟目录。一个点就把当前目录下所有未追踪的文件全部 add 了
4、 git commit -m ‘first commit’把文件提交到仓库
5、 git remote add origin git@github.com:wangjiax9/practice.git //关联远程仓库
6、 git push -u origin master //把本地库的所有内容推送到远程库上

Git 和 SVN 的区别

  1. Git 是分布式版本控制工具 , SVN 是集中式版本控制工具
  2. Git 没有一个全局的版本号,而 SVN 有。
  3. Git 和 SVN 的分支不同
  4. git 吧内容按元数据方式存储,而 SVN 是按文件
  5. Git 内容的完整性要优于 SVN
  6. Git 无需联网就可使用(无需下载服务端),而 SVN 必须要联网(须下载服务端)因为 git 的版本区就在自己电脑上,而 svn 在远程服务器上。

三十四、vue3 和 vue2

简述:体积更小、速度更快,支持 tree-shaking,支持 fragment 和 portal,支持自定义 render。

体积更小。新的代码库在设计的时候就考虑了 tree-shaking。内置的组件(如 )和内置的指令(v-model)是按需引入的,支持 tree-shaking。新的运行时最小体积将低于 10kb(gzip 之后)。除此之外,由于很多特性是支持 tree-shaking 的,所以我们可以提供更多的内置特性,如果你不想用这些特性,你的代码体积完全不会增加。另外在这里说一嘴,Vue 2.0 体积是 Vue 3.0 的一倍

速度更快,包括虚拟 DOM 的挂载和更新、组件实例的初始化和观察者的创建。3.0 版本将让你的应用启动时间减少一半。

支持 fragment 和 portal。虽然体积变小了,但是 3.0 版本还是带来了新功能,那就是支持 Fragment(一个组件包含多个根节点)和 Portal(在 DOM 中渲染一个 subtree,而不需要在一个组件中)。

插槽机制增强。所有由编译产生的插槽现在都是函数,这些函数会在子组件的 render 调用时被调用。这样一来,插槽中的依赖会被认为是子组件的依赖而不是父组件的依赖。这意味着:1、当插槽内容变化时,只有子组件重新渲染;2、当父组件重新渲染时,如果插槽内容没有变化,子组件就不需要重新渲染。这个特性提供了更精确的组件树层面上的变更检测,所以会减少很多无用的渲染。

自定义 render。我们会提供一个 API 用来创建自定义的 render,因此你不需要为了自定义一些功能而 fork Vue 的代码。这个特性给 Weex 和 NativeScript Vue 这样的项目提供了很多便利。

项目目录

vue-cli2.0 与 3.0 在目录结构方面,有明显的不同
vue-cli3.0 移除了配置文件目录,config 和 build 文件夹
同时移除了 static 静态文件夹,新增了 public 文件夹,打开层级目录还会发现, index.html 移动到 public 中
3.0 config 文件已经被移除,但是多了.env.production 和 env.development 文件,除了文件位置,实际配置起来和 2.0 没什么不同
没了 config 文件,跨域需要配置域名时,从 config/index.js 挪到了 vue.config.js 中,配置方法不变
移除了 static 文件夹,新增 public 文件夹,并且 index.html 移动到 public 中
在 src 文件夹中新增了 views 文件夹,用于分类 视图组件 和 公共组件

2.0 与 3.0 生命周期函数比较:

2.0 周期名称3.0 周期名称说明
beforeCreatesetup组件创建之前
createdsetup组件创建完成
beforeMountonBeforeMount组件挂载之前
mountedonMounted组件挂载完成
beforeUpdateonBeforeUpdate数据更新,虚拟 DOM 打补丁之前
updatedonUpdated数据更新,虚拟 DOM 渲染完成
beforeDestroyonBeforeUnmount组件销毁之前
destroyedonUnmounted组件销毁后

不能再使用 Vue.nextTick/this.$nextTick,Vue3 中你可以用:

import { nextTick } from "vue";
nextTick(() => {
  // something
});

setup

import { setup, reactive, ref, computed, toRefs, watch } from "vue";

在 setup 函数中定义的变量和方法最后都是需要 return 出去的 不然无法再模板中使用
生命周期的函数只能写在 setUp 中
provide/inject 只能写在 setUp
1.setup 函数 入口

2.reactive 函数 ,让复杂数据类型变成响应式

3.ref 函数,将基本数据类型变成响应式

4.toRefs-解构保留响应式状态

5.readonly 让数据变成只读状态

//setup 函数是当前组件入口函数,compositionAPI 的起点(入口)

//生命周期比所有的钩子函数都要早,比之前的 beforeCreate 这个钩子都要早

//setup 函数没有 this,结果是 undefined

除事件 API,$on,$once,$off 不再使用。EventBus 方法也不再使用

新加入了 TypeScript 以及 PWA 的支持

更精准的变更通知

2.x 版本中,使用 Vue.set 来给对象新增一个属性时,这个对象的所有 watcher 都会重新运行
3.x 版本中,只有依赖那个属性的 watcher 才会重新运行。

三十五、react 生命周期

最新的生命周期地址
pureComponent 通过 prop 和 state 的浅比较(shallowEqual)来实现 shouldComponentUpdate,component 是需要开发者在 shouldComponentUpdate 钩子函数中自己写 render 逻辑的,在某些情况下可以使用 pureComponent 来提升性能。
浅比较(shallowEqual):是 react 源码中的一个函数,它代替了 shouldComponentUpdate 的工作, 只比较外层数据结构,只要外层相同,则认为没有变化,不会深层次比较数据

在 react 中,生命周期函数指的是组件在加载前,加载后,以及组件更新数据和组件销毁时触发的一系列方法。通常分为以下几类:

组件加载的时候触发的函数:constructor 、componentWillMount、 render 、componentDidMount
组件数据更新的时候触发的函数:shouldComponentUpdate、componentWillUpdate、render、componentDidUpdate
在父组件里面改变 props 传值的时候触发的函数:componentWillReceiveProps
组件销毁的时候触发的函数:componentWillUnmount

三十六、webpack 打包

打包的优化建议
1、读取⼊⼝⽂件;
2、分析⼊⼝⽂件,得到其所依赖的⽂件信息;
3、递归读取每个⽂件的依赖信息,⽣成 AST 树,得到关系依赖图;
4、代码转化,⽣成 ES5 代码

入口(entry)
出口(output)
加载器(loader)
插件(plugin)
模式(mode)
本地服务器(dev Server)

三十七、webpack 性能优化总结

1.如何优化构建速度 2.如何优化打包后的文件

文件构建速度

  1. 减少不必要的依赖
  2. 减少不必要的 loader 处理
  3. 通过配置区分生产环境和开发环境的 plugin,不要混用,导致影响构建速度。
  4. 使用 cache-loader 进行缓存文件
  5. 开启多线程构建
module.exports = {
  // ...
  module: {
    noParse: /jquery|lodash/, // 1.减少不必要的依赖 module.noParse字段可用于配置不需要解析的模块  正则表达式

    // 或者使用 function
    noParse(content) {
      return /jquery|lodash/.test(content)
    },
    plugins: [ //5. 可以使用happypack是一个开启多线程解析和构建文件的plugin
        new HappyPack({
            id: 'js',
            loaders: ['babel-loader'],
            // 用于检索工作线程的预定义线程池
            threadPool: happyThreadPool,
            // 启用此选项可将状态消息从HappyPack记录到STDOUT
            verbose: true
        })
    ],
    rules: [  //2. 减少不必要的loader处理  module.rule.exclude或module.rule.include排除或仅包含需要应用loader的场景
        {
            test: /\.js$/,
            exclude: /node_modules/, //不匹配
            loader: "eslint-loader", // 代码规范检测的loader,所以没必要也不需要去匹配/node_modules下的模块
        },
        {
            test: /\.js$/,
            include: /src/, //匹配
            loader: "babel-loader", // 为了吧es6和更高版本的语法转换为es3或者es5的语法
        },
        { // 4.对于一些比较消耗性能的模块,并且打包后文件内容没有改动可以使用cache-loader进行缓存
            test: /\.js$/,
            use: [
            {
                loader: 'cache-loader',
                options: {
                cacheDirectory: path.resolve('.cache') //还能进行配置
                }
            },
            'babel-loader'
            ],
            include: path.resolve('src')
        }
        { // 5.thread-loader会开启一个线程池,线程池中包含适量的线程,利用多线程同时开启多个loader的处理
            test: /\.js$/,
            use: [
            "thread-loader",
            "babel-loader"
            ]
        }
    ],

  }
}

开发阶段的页面影响速度

  1. 热更新
    热更新可以理解为当文件有修改,页面会实时刷新。原理是,webpack-dev-server 使用的是 express 框架作为文件资源的 http 服务器,通过 websoket 协议和浏览器端进行通讯。当文件修改的时候,http 服务器会监听到文件变化,而触发 webpakc 编译文件,但是文件内容不会输出到 output 的目录,而是把编译的内容存放到内存中,然后浏览器就会实时更新内存中的内容,实现页面实时更新。
  2. 热替换
    热更新是把整个页面刷新,而热替换是只刷新修改的那部分。css 样式修改可以立马看到热替换效果,因为 css 只是样式。而如 js 这些逻辑性的代码,无法页面热替换,除了 css 文件都需要额外的手动处理才能热替换。
// webpack.config.js
const webpack = require("webpack");

module.exports = {
  // ...
  devServer: {
    // 开启 HMR 特性,如果资源不支持 HMR 会 fallback 到 live reloading
    hot: true,
    // 只使用 HMR,不会 fallback 到 live reloading
    // hotOnly: true
  },
  plugins: [
    // ...
    // HMR 特性所需要的插件
    new webpack.HotModuleReplacementPlugin(),
  ],
};

如何优化打包后的文件

打包后的文件最优的结果就是文件尽可能少、文件尽可能小,所以只需要根据这两个方向去优化。

  1. 模块分离 手动和自动

    1. 手动分离
      手动分包就是开发人员通过根据分析模块的依赖关系,把公共模块通过打包或手动放到一个公共文件夹里,被打包后的 index.html 文件引入到页面。
      大致步骤:
      (1)提取公共模块到公共文件夹,并生成资源清单列表(可以手动创建,也可以使用 webpack 内置插件 DllPlugin 生成)
      (2)在 html 页面引入公共模块
      (3)使用 DllReferencePlugin 控制 chunk 打包结果,资源清单里面有的模块,不在打包进入 bundle 里面。

    2. 自动分离
      自动分离相对于手动分离会方便一点,只需要配置要分离策略。
      webpack 提供了 optimization 配置项,用于配置一些优化信息
      其中 splitChunks 是分离模块策略的配置

  2. 代码压缩
    webpack 自动集成了 Terser 插件进行打包时代码压缩,只需要配置 optimization 选项即可

  3. 删除无用的代码和模块
    webpack2 开始内置 Tree-Shaking 功能来删除用不到或者对程序没有影响的代码,极大的减少了打包后的体积。tree-shaking 是基于 es6 的,因此对于一些使用 commonJs 导出的第三方库如 lodash,应该换成 es6 方式导出的版本,这样才能使用 tree-shaking 优化。

  4. 模块懒加载
    对于一些不会立即使用的模块,可以通过懒加载的形式导入 import(‘xxx’),webpack 打包的时候会把这些需要懒加载的模块打包成独立的文件,只有当使用到的时候才会去加载这个文件,并把模块添加到 webpackJsonp 对象中。

// 模块分离
module.exports = {
  optimization: {
    splitChunks: {
      // 分包策略
      chunks: "all",
    },
  },
};

// 使用compression-webpack-plugin进行预压缩成gzip
// 使用webpack Bundle Analyzer作为模块打包分析
// 2. 代码压缩
//压缩js
const TerserPlugin = require("terser-webpack-plugin");
//压缩css
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        loader: ExtractTextPlugin.extract("style-loader", "css-loader"),
      },
    ],
  },
  optimization: {
    // 是否要启用压缩,默认情况下,生产环境会自动开启
    minimize: true,
    minimizer: [
      // 压缩时使用的插件,可以有多个
      new TerserPlugin(),
      new OptimizeCSSAssetsPlugin(),
    ],
  },
};

三十八、express 和 koa

Koa 比 Express 更加新。
Koa 是一个新的 web 框架。通过利用 async 函数,Koa 帮你丢弃回调函数,并有力地增强错误处理。
Koa 不依赖任何中间件。他比 Express 更加轻便
优缺点:
express 优:功能全面,拥有路由、模板等框架常见功能;学习成本低
express 缺:回调地狱问题,很难处理错误异常
koa 优:没有回调地狱问题

三十九、jwt 和 token 的原理及区别

点击这里了查看

四十、es6 数组处理、css 宽高的样式

对象的新增方法

  1. Object.is() 用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致
    相等运算符(==)和严格相等运算符(===)。它们都有缺点,前者会自动转换数据类型,后者的 NaN 不等于自身,以及+0 等于-0
  2. Object.assign()实行的是浅拷贝;方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
    方法的第一个参数是目标对象,后面的参数都是源对象。
    注意,如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。
    如果该参数不是对象,则会先转成对象,然后返回。由于 undefined 和 null 无法转成对象,所以如果它们作为参数,就会报错。
  • 可以用来处理数组,但是会把数组视为对象。
  1. Object.keys() 返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名
  2. Object.values() 方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值
  3. Object.entries() 方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组
  4. Object.fromEntries() 方法是 Object.entries()的逆操作,用于将一个键值对数组转为对象。因此特别适合将 Map 结构转为对象

链判断运算符

新的判断的运算符
三元运算符?. 直接在链式调用的时候判断,左侧的对象是否为 null 或 undefined。如果是的,就不再往下运算,而是返回 undefined

  1. 本质上,?.运算符相当于一种短路机制,只要不满足条件,就不再往下执行
  2. 如果属性链有圆括号,链判断运算符对圆括号外部没有影响,只对圆括号内部有影响

如何实现居中

::: 水平居中的方法 :::

  • 元素为行内元素,设置父元素 text-align:center
  • 如果元素宽度固定,可以设置左右 margin 为 auto;
  • 如果元素为绝对定位,设置父元素 position 为 relative,元素设 left:0;right:0;margin:auto;
  • 使用 flex-box 布局,指定 justify-content 属性为 center
  • display 设置为 tabel-ceil

::: 垂直居中的方法 :::

  • 将显示方式设置为表格,display:table-cell,同时设置 vertial-align:middle
  • 使用 flex 布局,设置为 align-item:center
  • 绝对定位中设置 bottom:0,top:0,并设置 margin:auto
  • 绝对定位中固定高度时设置 top:50%,margin-top 值为高度一半的负值
  • 文本垂直居中设置 line-height 为 height 值
  • left: 50%;transform: translateX(-50%); 配合使用也可以

::: 左边定宽,右边自适应 :::
1.float
2.右边 absolute+right:0
3.左侧 absolute,右侧 margin-left:200px;
4.flex

四十一、rpx,rem,em,px,%,vm,vh;

rpx 相当于把屏幕宽度分为 750 份,1 份就是 1rpx
rem 相对于根元素,rem 配合动态设置 font-size,做布局响应式
em 相对于父元素,
px 相对于屏幕分辨率,
%是相对于父元素的百分比值,
vm 和 vh 是相对于视口的宽度和高度。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值