MVC 是Model-View-Controller的缩写.
主要目的是对代码解耦. 把混合在一起的代码拆分成 3 部分;
让html中不存在任何逻辑代码, 没有JavaScript代码痕迹.
以原生 HTML 为例:
Model: 数据模型层
早期前端: 弱化的Model. 不关注 Model 层, 数据都是从 服务器 请求下来, 直接使用即可.
现在前端: 使用WebStorage, 框架中的Vuex, Redux等管理数据
在TypeScript 语言中, 新增了数据类型声明特征, 才让 Model 在前端变得尤为重要.
View: 视图层
书写普通的html. 不掺杂任何 JS 代码.
例如: Tedu
注意: 此按钮 没有 onclick 的事件写法.
Controller: 控制器层
控制 HTML 的具体行为, 具体为script代码范围, 例如 为id="tedu"的按钮添加事件的写法:
var btn = document.getElementById("tedu");
btn.onclick = function(){ alert(123456) }
MVVM 是Model-View-ViewModel的简写。它本质上就是 MVC 的改进版。MVVM 就是将其中的 View 的状态和行为抽象化,让我们将视图 UI 和业务逻辑分开。
以 vue为例:
Model: 数据模型层
script 部分的 data 属性, 专门管理数据
View: 视图层
即 template 中的代码, 负责 UI 的构建
ViewModel: 视图模型层
new Vue({}) 部分. 自动管理数据和视图.
重点是双向数据绑定功能, 实现了 数据变化视图自动变更. 视图变化,数据自动联动.
采用数据劫持 结合 发布者-订阅者模式,通过 Object.defineProperty() 来劫持各个属性的 setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
具体步骤如下:
observe
的数据对象进行递归遍历,包括子属性对象的属性,都加上setter
getter
。这样的话,给这个对象的某个属性赋值,就会触发setter
,那么就能监听到数据变化。(其实是通过Object.defineProperty()
实现监听数据变化的)compile
解析模板指令,将模板中的变量替换成数据,接着初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者。一旦数据有变动,订阅者收到通知,就会更新视图Watcher订阅者
是Observer
和Compile
之间通信的桥梁,主要负责:update()
方法dep.notice()
通知时,就调用自身的update()
方法,并触发Compile
中绑定的回调viewmodel(vue实例对象)
作为数据绑定的入口,整合Observer
、Compile
、Watcher
三者,通过Observer
来监听自己的model数据变化,通过Compile
来解析编译模板指令,最终利用Watcher
搭起Observer
和Compile
之间的通信桥梁,达到数据变化 (ViewModel)→视图更新(view);视图变化(view)→数据(ViewModel)变更的双向绑定效果。脏值检查(angular.js)
angular.js是通过脏值检测的方式,对比数据是否有变更,从而决定是否更新视图。
最简单的方式就是通过setInterval()定时轮询检测数据变动。
angular.js只有在指定的事件触发时,进入脏值检测,大致如下:
<input type="text" value={this.state.word} onChange={this._onChange} />
_onChange = (event) => {
let val = event.target.value;
this.setState({ word: val });
};
优点:
单向数据流,所有状态变化都可以被记录、跟踪,状态变化通过手动调用通知,源头易追溯
例如: 通过 _onChange 方法可以实时监听输入框数据变更.
缺点:
代码量会相应的上升,数据的流转过程变长,从而出现很多类似的样板代码。
例如: 每个输入框 都要添加对应的 方法监听变更. 大型表单项目会导致代码非常啰嗦.
<input type="text" v-model="uname" />
优点:
在表单交互较多的场景下,会简化大量业务无关的代码。
例如: React中的事件绑定 onChange 都可以省略
缺点:
无法实时掌控数据的状态变化
例如: 数据的更新都是自动化操作, 是无感的. 要想实现 纯数字 纯数字 的输入需求, 就需要更多操作.
Vuex 是一个专为 Vue.js 应用程序开发的 状态管理模式.
它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
使用场景:
5个核心属性:
vuex
实现了一个单项数据流,在全局又有一个state
存放数据,
当组建要更改state
中的数据时,必须通过Mutation
进行,
mutation
同时提供了订阅者模式供外部插件调用获取state
数据的更新。
而当所有异步操作(常见于调用后台接口异步获取更新数据)或批量的同步操作需要走Action
,
但Action
也是无法直接修改state
的,还是需要通过mutation
来修改state
的数据。
最后根据state的变化,渲染到视图上。
vue-router
通过 hash
与history
两种方式实现前端路由
更新视图但不重新请求页面是前端路由原理的核心之一.
目前在浏览器环境中这一功能的实现主要有两种方式:
1. hash
: 利用 URL 中的 hash. 形式上会多个‘#’
http://localhost:8080/#/login
hash("#")
的作用是加载 URL 中指示网页中的位置。
#本身以及它后面的字符称之为 hash,可通过 window.location.hash
获取
hash 虽然出现在 url 中,但不会被包括在 http 请求中,它是用来指导浏览器动作的,对服务器
端完全无用,因此,改变 hash 不会重新加载页面。
每一次改变 hash(window.localtion.hash),都会在浏览器访问历史中增加一个记录。
利用 hash 的以上特点,就可以来实现前端路由"更新视图但不重新请求页面"的功能了。
2. history
:html5 中新增的方法. 形式上比 hash
更好看
http://localhost:8080/login
History interface
是浏览器历史记录栈提供的接口,通过back()
、forward()
、go()
等方法,我们可
以读取浏览器历史记录栈的信息,进行各种跳转操作。
从 HTML5开始,History interface
提供了2个新的方法:pushState()
、replaceState()
使得我们
可以对浏览器历史记录栈进行修改
这2个方法有个共同的特点:
当调用他们修改浏览器历史栈后,虽然当前url改变了,但浏览器不会立即发送请求该url,这就为单页应用前端路由,更新视图但不重新请求页面提供了基础
history模式需要后端服务器进行 路径重写 处理. 否则会出现 404 错误
router-link:
tag
属性修改为其他标签$router.push()
实现跳转$router.push:
mode
确定使用 HTML5History
还是 HashHistory
实现跳转HTML5History
: 调用 window.history.pushState()
跳转HashHistory
: 调用 HashHistory.push()
跳转Promise 对象代表一个异步操作,有三种
状态:
pending
: 初始状态,不是成功或失败状态。fulfilled
: 意味着操作成功完成。rejected
: 意味着操作失败。优点:
回调地狱
缺点:
基础用法:
奇数报错, 偶数正常
function demo() {
return new Promise((resolve, reject) => {
setTimeout(() => {
let num = Math.round(Math.random() * 100);
if (num % 2 == 0) {
resolve("num是偶数" + num);
} else {
reject(new Error("num是奇数" + num));
}
}, 500);
});
}
console.log("运行中...");
demo()
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log(err);
});
封装ajax
function ajax(url) {
return new Promise((resolve, reject) => {
let req = new XMLHttpRequest();
req.open("GET", url, true);
req.onload = function () {
if (req.status == 200) {
let data = JSON.parse(req.responseText);
resolve(data);
} else {
reject(new Error(req.statusText));
}
};
req.onerror = function () {
reject(new Error(req.statusText));
};
req.send();
});
}
ajax("http://101.96.128.94:9999/mfresh/data/news_select.php")
.then((res) => console.log(res))
.catch((res) => console.log(res));
封装Promise:
function myPromise(callback) {
// 用 常量 保存状态值. 使用时有 代码提示, 不容易写错.
const PENDING = "pending";
const REJECTED = "rejected";
const FULFILLED = "fulfilled";
this.status = PENDING; //初始状态值: pending
this.msg; // 存储执行结果
this.thens = []; //存储多个 then(); xxx().then().then().then().
this.func_error;
// 通过 .then(成功回调, 失败回调) 接受用户传入的回调方法
this.then = function (func_success, func_error) {
this.thens.push(func_success);
this.func_error = func_error;
// 返回this, 支持链式写法. 例如: xxx().then().then().then()...
return this;
};
// 接受 失败回调 函数
this.catch = function (func_error) {
this.func_error = func_error;
};
// 成功时触发, 注意必须箭头函数 保持 this 指向
let resolve = (msg) => {
// 只有状态为 pending 时, 才能更改
if (this.status != PENDING) return;
this.status = FULFILLED; //修改状态值
this.msg = msg;
// 触发 终结 方法
this.complete();
};
// 失败时触发, 注意必须箭头函数 保持 this 指向
let reject = (msg) => {
if (this.status != PENDING) return;
this.status = REJECTED; //修改状态值
this.msg = msg;
// 触发 终结 方法
this.complete();
};
// callback 为用户传入的函数, 即异步操作所在位置
callback(resolve, reject);
// 统一出口: 失败和成功 最终都调用此方法; 便于维护
this.complete = () => {
if (this.status == FULFILLED) {
let first_time = true; //首次
let res;
// 考虑多个 then() 的情况
this.thens.forEach((item) => {
if (first_time) {
res = item(this.msg); //首次执行
first_time = false;
} else {
// 每次 then 都是上一次的返回值
res = item(res);
}
});
}
if (this.status == REJECTED && this.func_error) {
this.func_error(this.msg);
}
};
//
/// 测试方式 //
console.log("运行中...");
function demo() {
return new myPromise((resolve, reject) => {
setTimeout(() => {
let num = Math.round(Math.random() * 100);
if (num % 2 == 0) {
resolve(num);
} else {
reject(new Error("奇数" + num));
}
}, 300);
});
}
demo()
.then((res) => {
console.log("1" + res);
return res;
})
.then((res) => {
console.log(res / 2);
return res / 2;
})
.then((res) => {
console.log("3." + res);
return res / 2;
})
.then((res) => {
console.log("4." + res);
return res / 2;
})
.then((res) => {
console.log("5." + res);
return res / 2;
})
.catch((err) => console.log(err));
// demo()
// .then((res) => {
// console.log(res);
// })
// .catch((err) => {
// console.log(err);
// });
区别主要在于按顺序调用多个 异步函数 时的写法 和 报错获取
Promise方式
ajax().then(func1).then(func2).then(func3).then(func4)
await/async方式
async function demo(){
await res = ajax();
await res = func1(res);
await res = func2(res);
await res = func3(res);
await res = func4(res);
}
总结:
链式写法
$('#id').css().append().xxx()
原理原理
每个函数调用后的返回值, 都是当前对象. 主要依赖每个函数结尾的 return this
详细参考
<body>
<div id="d1">
<i>Hello World!</i>
</div>
<script>
function MyQuery(id) {
this.el = document.getElementById(id);
this.css = function (property, value) {
this.el.style[property] = value;
return this; //关键点
};
this.append = function (content) {
this.el.innerHTML += content;
return this; //关键点
};
}
const $ = function (id) {
return new MyQuery(id);
};
$("d1").css("color", "red").append("haha");
// $("d1").css("color", "red") 的返回值是 this
// 所以 append 相当于是 this.append(). 而 this 就代表 MyQuery 对象
</script>
</body>
随着屏幕设备或视口(viewport)尺寸的增加,系统会自动分为最多12列
(也可以自己定制多少列都行
)。
通过一系列的行(row)与列(column)的组合创建页面布局
通过定义容器大小,平分12份,再调整内外边距,最后结合媒体查询,实现强大的响应式网格系统。
let
: 块级作用域
const
: 常量; 块级作用域; 一旦声明, 则运行期间无法修改.
模板字符串
:``
解构赋值
: let {name, age} = {name:‘dongdong’, age:33}
...
: 代替 arguments 变量, 接受函数的多余参数. function name(…args){}
箭头函数
: 匿名函数, 自带 this 保持为定义所在对象.
... 扩展对象
, 取代 Object.assign()
let a = {name:'xiaoxin', age:32};
let b = Object.assign(a, {gender: 1});
//等价于下方. 可以看到 ... 更简单
let c = {...a, {gender:1}}
Promise
: 异步编程的一种方案, 解决 回调地狱
class 面向对象
区别
所以
适用场景
加载时
beforeCreate
: 开始创建created
: 创建完毕beforeMount
: 开始挂载mounted
: 挂载完毕更新
beforeUpdate
: 更新前updated
: 更新完毕keep-alive
相关
activated
: 被 keep-alive 缓存的组件激活时调用。deactivated
: 被 keep-alive 缓存的组件停用时调用。销毁
beforeDestory
: 销毁前destroyed
: 销毁完毕理论上, 应该在created
周期中进行网络请求. 因为这是最早的的 methods 与 data 加载完毕的时机. 在 created
发送请求, 可以比mounted
周期发送请求, 提前几毫秒的时间拿到数据.
而实际开发中, 几毫秒的提前对用户来讲, 没有任何差异. 所以 created
和 mounted
发送请求都可以.
兼容性问题主要分为三大类:
参考网址:https://www.cnblogs.com/zhoudawei/p/7497544.html
参考网址:https://www.jianshu.com/p/9523bb439950
keep-alive是一个抽象组件:它自身不会渲染一个DOM元素,也不会出现在父组件链中;
使用keep-alive包裹动态组件时,会缓存
不活动的组件实例,而不是销毁
它们。
示例场景:
用户在某个列表页面选择筛选条件过滤出一份数据列表,由列表页面进入数据详情页面,再返回该列表页面,我们希望:列表页面可以保留用户的筛选(或选中)状态。
keep-alive就是用来解决这种场景。当然keep-alive不仅仅是能够保存页面/组件的状态
这么简单,它还可以避免组件反复创建和渲染
,有效提升系统性能
。总的来说,keep-alive用于保存组件的渲染状态
。
<keep-alive :include="whiteList" :exclude="blackList" :max="amount">
<component :is="currentComponent"></component>
</keep-alive>
<keep-alive :include="whiteList" :exclude="blackList" :max="amount">
<router-view></router-view>
</keep-alive>
include
定义缓存白名单,keep-alive会缓存命中的组件;
exclude
定义缓存黑名单,被命中的组件将不会被缓存;
max
定义缓存组件上限,超出上限使用LRU的策略置换缓存数据。
父传递参数
<Son name='xiaoxin' :age="18" />
子组件
<script>
export default {
props: ['name', 'age'],
// 或者 规定类型写法
props: { name: {type: String}, age:{type: Number} }
}
</script>
流程:
子组件
<button v-on:click="$emit('show', 'Hi, petter')">我是按钮</button>
父组件
<Son @show="sayHi" />
<script>
export default {
methods: {
sayHi(msg){
console.log(msg)
}
}
}
</script>
流程解析:
兄弟组件间无法直接通信, 通信方式有两种: 子传父 + 父传子
和 事件车 事件车
子传父 + 父传子:此方式效率较低, 不推荐
依赖共同的父组件进行信息的转达.
假设 A 和 B 组件为兄弟组件, A 要向 B 中传值:
○ 父组件 通过 A 的事件方式传递 父的函数给A
○ A组件 通过 $emit() 方式 触发父传入的事件, 并传入参数
○ 父组件 收到A 的参数之后, 再通过修改 传递给 B组件 的属性. 实现B的属性修改
总结
○ 父和A组件, 通过子父传参进行信息交互.
○ 父和B组件, 通过父子传参进行信息交互.
事件车: 此方式效率高, 推荐使用.
参考网址:https://blog.csdn.net/qq_42455145/article/details/106466367
○ 向 Vue 的原型中, 注入一个 专门负责监听事件的 Vue 实例
Vue.prototype.EventBus = new Vue();
○ A 组件中注册 引入 EventBus.js 模块, 并向其中注册 事件
this.EventBus.$emit('change', msg)
○ B 组件中注册 change 事件的监听
this.EventBus.$on('change', changeMsg(msg)) methods:{
changeMsg(msg){
//此处就能收到 msg, A组件传入的
}
}
parentElement{
position:relative;
}
childElement{
position: absolute;
top: 50%;
transform: translateY(-50%);
}
parentElement{
display:flex;/*Flex布局*/
display: -webkit-flex; /* Safari */
align-items:center;/*指定垂直居中*/
}
代码管理工具有早期的 SVN
和 现在的 GIT
.
我目前使用的是 Git 工具管理代码. Git是一个开源的分布式版本控制系统。
Git工具的主要功能有:
码云
和 Github
实现代码的云存储. 快速进行团队合作Git的常用命令有:
git init
git add 文件名
或 git add
.git commit -m '版本描述'
git branch
git merge
git clone 远程仓库地址
git fetch
git pull
git push
单页应用的全称是 Single Page Application
,简称 SPA
通过路由的变更, 局部切换网页内容 取代 整个页面的刷新操作.
三大框架 React
Vue
Angular
均采用单页应用模式.
SEO
不友好, 需要采用 prerender
服务进行完善参考地址:https://www.jb51.net/article/185275.htm
后台管理系统 一般都会有权限模块, 用来控制用户能访问哪些页面 和 哪些数据接口.
整体思路:
后端返回用户权限,前端根据用户权限处理得到左侧菜单;所有路由在前端定义好,根据后端返回的用户权限筛选出需要挂载的路由,然后使用 addRoutes
动态挂载路由。
具体思路:
高度坍塌:在流式布局中十分常见。当父元素没有高度,子元素全部设置float时。
原因:子元素脱离文档流,无法撑开父元素
<div style="clear:both"></div>
.box:after{
clear: both;
content: '';
display: block;
height: 0;
overflow: hidden;
}
.box{ zoom:1; } /** 兼容ie触发hasLayout */
参考地址:https://blog.csdn.net/hguisu/article/details/8680808
URI
: Universal Resource Identifier 统一资源标识符,用来唯一的标识一个资源,是一种语义上的抽象概念。
URL
: Universal Resource Locator 统一资源定位符,它是一种具体的URI,即URL可以用来标识一个资源,而且还指明了如何访问到这个资源
URN
: Universal Resource Name(统一资源名称)是标准格式的URI,指的是资源而不指定其位置或是否存在
举个容易理解的例子:
URI: 国家说, 我们要指定一个规则, 来找到某个人.
URL: 制定地址规则, 实现国家需求: xx省xx市xx区xx小区xxx楼xx单元xxx号房间的张三张三
URN: 制定唯一原则, 实现国家需求: 姓名张三 + 身份证号xxxxx
参考地址:https://www.cnblogs.com/binguo666/p/10928907.htmllocal
闭包函数:声明在一个函数中的函数,叫做闭包函数。
闭包:内部函数总是可以访问其所在的外部函数中声明的参数和变量,即使在其外部函数执行完毕.
function funA(){
var a = 10; // funA的活动对象之中;
return function(){ //匿名函数的活动对象;
console.log(a);
}
}
var b = funA();
b(); //10
闭包的使用场景
优点
缺点
移动端开发时, 由于屏幕是 retina, 即高清屏幕. 当写 1px 时, 实际的线宽为 2px. 会显得很粗.
此时就有了 0.5px 的需求: 主要应对 iPhone
<style>
.parent{
position: relative;
&:after{
/* 绝对定位到父元素最低端,可以通过left/right的值控制边框长度或者定义width:100%;*/
position: absolute; 123456
bottom: 0;
left: 0;
right: 0;
/* 初始化边框 */
content: '';
box-sizing: border-box;
height: 1px;
border-bottom: 1px solid rgba(0, 0, 0, 0.2);
/* 以上代码,实现了一个边框为1px的元素,下面实现0.5px边框*/
transform: scaleY(0.5); // 元素Y方向缩小为原来的0.5
transform-origin: 0 0; // CSS属性让你更改一个元素变形的原点。
}
}
</style>
<div class="parent"></div>
原因: 浏览器的同源策略
浏览器从一个域名的网页去请求另一个域名的资源时,域名、端口、协议任一不同,都是跨域
网址格式: 协议名://域名:端口号/…
例如: http://localhost:8080/ … 协议http 域名localhost 端口号8080
常见的解决方案有3种
cors
○ 由服务器解决, 添加 cors 功能模块.
○ 前端: 无操作
jsonp: 利用 script 脚本的 src 不受同源策略限制的特点
参考地址:https://www.runoob.com/json/json-jsonp.html
○ 服务器: 返回特定的 jsonp 格式数据
<?php header('Content-type: application/json'); //获取回调函数名
$jsoncallback = htmlspecialchars($_REQUEST ['jsoncallback']); //json数据
$json_data = '["customername1","customername2"]'; //输出jsonp格式的数据
echo $jsoncallback . "(" . $json_data . ")";
?>
○ 前端: 发送特定的 jsonp 格式数据到服务器
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JSONP 实例</title>
</head>
<body>
<div id="divCustomers"></div>
<script type="text/javascript">
function callbackFunction(result, methodName) {
var html = ''
;
for(var i = 0; i < result.length; i++) {
html += '- '
+ result[i] + '
代理proxy
○ vue, angualr 都提供固定的方式设定代理
//vue-cli3.0 里面的 vue.config.js做配置
devServer: {
proxy: {
'/rng': { //这里最好有一个 /
target: 'http://45.105.124.130:8081', // 后台接口域名
ws: true, //如果要代理 websockets,配置这个参数
secure: false, // 如果是https接口,需要配置这个参数
changeOrigin: true, //是否跨域
pathRewrite:{
'^/rng':''
}
}
}
}
更多的方式:
关键词:媒体查询
@media
img{ max-width: 100%;}
, 根据不同屏幕分辨率加载不同大小的图片页面的生命周期.
属性 | 说明 |
---|---|
onLoad | 生命周期回调—监听页面加载 |
onShow | 生命周期回调—监听页面显示 |
onReady | 生命周期回调—监听页面初次渲染完成 |
onHide | 生命周期回调—监听页面隐藏 |
onUnload | 生命周期回调—监听页面卸载 |
组件的生命周期
属性 | 说明 |
---|---|
created | 在组件实例刚刚被创建时执行 |
attached | 在组件实例进入页面节点树时执行 |
ready | 在组件在视图层布局完成后执行 |
moved | 在组件实例被移动到节点树另一个位置时执行 |
detached | 在组件实例被从页面节点树移除时执行 |
error | 每当组件方法抛出错误时执行 |
bind
: 允许事件冒泡
catch
: 阻止事件冒泡
例如下图:
文件 | 必需 | 作用 |
---|---|---|
app.js | 是 | 小程序逻辑 |
app.json | 是 | 小程序公共配置 |
app.wxss | 否 | 小程序公共样式表 |
一个小程序页面由四个文件组成,分别是:
文件类型 | 必需 | 作用 |
---|---|---|
js | 是 | 页面逻辑 |
wxml | 是 | 页面结构 |
json | 否 | 页面配置 |
wxss | 否 | 页面样式表 |
参考网址: https://router.vuejs.org/zh/guide/advanced/navigation-guards.html
完整的导航解析流程
beforeRouteLeave
守卫。beforeEach
守卫。beforeRouteUpdate
守卫 (2.2+)。beforeEnter
。beforeRouteEnter
。beforeResolve
守卫 (2.5+)。afterEach
钩子。beforeRouteEnter
守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。全局前置守卫
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
// ...
})
全局解析守卫
在 2.5.0+ 你可以用 router.beforeResolve 注册一个全局守卫。这和 router.beforeEach
类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后 同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调
用。
全局后置钩子
router.afterEach((to, from) => {
// ...
})
路由独享守卫
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
组件内的守卫
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
vue 更新 DOM 是异步操作. $nextTick()
可以监听DOM更新完毕的时机.
<template>
<div>
<h3 id="nn">{{ name }}</h3>
</div>
</template>
<script>
export default {
data() {
return {
name: "东东",
};
},
mounted() {
this.name = "然然";
// 异步渲染机制. 只要 mounted 方法执行完毕后, name 才会更新到DOM
let el = document.getElementById("nn");
console.log(el.innerText); // 东东
this.$nextTick(() => {
// 这里是DOM 渲染完成后的回调函数
let el = document.getElementById("nn");
console.log(el.innerText); // 然然
});
},
};
</script>
<style></style>
浅拷贝理解为: 昵称. 比如 张东 东东 东神 东哥 都是一个人呢
深拷贝裂解为: 克隆体 比如 东哥的 大乔 和 然哥的 大乔 长得一样, 但不是同一个角色.
把一个对象里面的所有的属性值和方法都复制给另一个对象
let a = { boss: {name:'wenhua'} };
let b = Object.assign({}, a);
b.boss.name = 'WenHua';
console.log(a); // WenHua
.直接把一个对象赋给另一个对象,使得两个都指向同一个对象。
let a = {age: 11};
\let b = a;
b.age = 22;
console.log(a); // 22
深拷贝
把一个对象的属性和方法一个个找出来,在另一个对象中开辟对应的空间 在另一个对象中开辟对应的空间,一个个存储到另一个对象中。
var obj1 = {
age: 10,
sex: "男",
car: ["奔驰", "宝马", "特斯拉", "奥拓"],
dog: {
name: "大黄",
age: 5,
color: "黑白色"
}
};
var obj2 = {};//空对象
// 通过函数实现,把对象a中的所有的数据深拷贝到对象b中 // 使用递归函数
function deepCopy(obj,targetObj){
for (let key in obj){
let item = obj[key];
if (item instanceof Array){//if array
targetObj[key] = [];
deepCopy(item,targetObj[key]);
}else if (item instanceof Object){//if object
targetObj[key] = {};
deepCopy(item,targetObj[key]);
}else {//normal attribute
targetObj[key] = obj[key];
}
}
}
deepCopy(obj1,obj2);
console.dir(obj1);
console.dir(obj2);
参考网址: https://echarts.apache.org/examples/zh/editor.html?c=map-usa
使用的数据: https://echarts.apache.org/examples/data/asset/geo/USA.json
ES6之前并没有引入 class 面向对象的概念, JavaScript 通过构造函数来创建实例.
构造函数:
function Person(name, age){
this.name = name;
this.age = age;
this.sayName = function () {
console.log(this.name);
}
}
var person = new Person('xiaoxin', 32);
console.log(Person.prototype);
原型
: 每个函数都有一个prototype
属性,这个属性指向函数的原型对象。
原型链
: __ proto __
这是每个对象(除null外)都会有的属性,叫做__ proto __,这个属性会指向该对象的原型。
箭头函数无法修改this指向.
普通函数 可以通过 apply
, call
,bind
修改 this 指向
场景: 用户登录成功后, 需要反复到服务器获取 敏感数据.
服务器对每次请求都要验证是哪位用户发送的, 且用户是否合法, 需要反复查询数据库, 对数据库造成过大压力.
token的具体流程:
用户登录成功后, 在服务器可以查询到此用户的相关信息. 服务器通过一些加密算法 把 用户信息, token 的有效期等, 加密成一个字符串. 然后发送给用户. 这个字符串就是 token.
具体加密算法只有服务器知道, 服务器可以对 token 进行解密, 还原成原始值.
重点
: 用户每次请求都必须携带 token. 服务器直接解密token 就可以知道用户的相关信息. 省去查询数据库的操作. 减轻数据库压力!
优势 相较于 cookie
:
缺陷
:
劫持
问题.参考文档:https://www.cnblogs.com/jinzhou/p/9072614.html
http协议和https协议的区别:传输信息安全性不同、连接方式不同、端口不同、证书申请方式不同
缓存可以说是性能优化中简单高效的一种优化方式了。一个优秀的缓存策略可以缩短网页请求资源的距离,减少延迟,并且由于缓存文件可以重复利用,还可以减少带宽,降低网络负荷。
对于一个数据请求来说,可以分为发起网络请求
、后端处理
、浏览器响应
三个步骤。浏览器缓存可以帮助我们在第一和第三步骤中优化性能。
跨站请求伪造(英语:Cross-site request forgery), 缩写为 CSRF CSRF, 是一种劫持受信任用户向服务器发送非预期请求的攻击方式.
原理
东东到提款机, 插入银行卡 输入密码取钱. 此时东东离开提款机忘记拔卡.
然然直接用提款机取钱. 提款机是不知道取钱人是否为东东本人的.
防御手段
SameSite
属性用来限制第三方Cookie, 从而减少安全风险.Origin Header
和 Referer Header
属性token
并验证是什么
Webpack 是一个前端资源加载/打包工具。它将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源。
为什么用
主要特性
source map
有很好的支持。set
和 map
都是 ES6新增特性
map映射
也称 dictionary字典
. 一个键值结构. 类似于 js 的对象类型.
let map = new Map();
map.set("name", "东东");
map.set("age", 22);
map.set("gender", 1);
console.log(map); //Map { 'name' => '东东', 'age' => 22, 'gender' => 1 }
console.log(map.get("name")); //东东
set集合
: 特点为内部元素不重复. 会自动去重
let a = new Set([1, 1, 2, 2, 3, 3]);
console.log(a); //Set { 1, 2, 3 }
计算属性就是当其依赖属性的值发生变化时,这个属性的值会自动更新,与之相关的DOM部分也会同步自动更新。
使用场景:
在模板中绑定一些数据, 这些数据需要经过一些复杂处理之后再展示.
但是模板中只能进行简单逻辑处理, 表达式过长 或 逻辑复杂 会变得臃肿, 难以阅读及维护.
此时就把处理数据的逻辑放在计算属性中进行.
具体用法:
<template>
<div>
<h3>总价: {{ total }}</h3>
</div>
</template>
<script>
export default {
data() {
return {
goods: [
{ name: "iPhone12", price: 8999, count: 4 },
{ name: "小米11", price: 3999, count: 2 },
{ name: "Mate40", price: 8000, count: 1 },
],
};
},
computed: {
total() {
let total = 0;
this.goods.forEach((item) => {
total += item.price * item.count;
});
return total;
},
},
};
</script>
<style></style>
能监听
push
和 splice
操作deep:true
配置, 才能监听到export default {
data() {
return {
emps: [
{ name: "lucy", age: 22, skills: ["lucy", "lily"] },
{ name: "lucy", age: 22, skills: ["lucy", "lily"] },
{ name: "lucy", age: 22, skills: ["lucy", "lily"] },
],
};
},
methods: {
change() {
this.emps[0].name = "lala";
this.emps[0].skills.push(333);
},
},
watch: {
emps: {
handler: (xx) => {
console.log(xx);
},
deep: true, //允许监听 内容的变化
},
},
};
不能监听
export default {
data() {
return {
emps: [
{ name: "lucy", age: 22, skills: ["lucy", "lily"] },
{ name: "lucy", age: 22, skills: ["lucy", "lily"] },
{ name: "lucy", age: 22, skills: ["lucy", "lily"] },
],
};
},
methods: {
change() {
// 此操作, 替换 下标0 的值, 不会被 watch 监听
this.emps[0] = { name: "222", age: 333 };
},
},
watch: {
emps(){
console.log(this.emps)
}
},
};
参考文档:https://www.cnblogs.com/fs0196/p/12685422.html
日常开发过程中,滚动事件做复杂计算频繁调用回调函数很可能会造成页面的卡顿,这时候我们更希望把多次计算合并成一次,只操作一个精确点,JS把这种方式称为debounce(防抖)和throttle(节流)
函数防抖
当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定时间到来之前,又触发了事件,就重新开始延时。也就是说当一个用户一直触发这个函数,且每次触发函数的间隔小于既定时间,那么防抖的情况下只会执行一次。
function debounce(fn, wait) {
var timeout = null; //定义一个定时器
return function() {
if(timeout !== null) clearTimeout(timeout); //清除这个定时器
timeout = setTimeout(fn, wait);
}
}// 处理函数
function handle() {
console.log(Math.random());
}// 滚动事件
window.addEventListener('scroll', debounce(handle, 1000));
效果: 页面滚动停止1秒后, 才会打印随机数字.
在滚动过程中并没有持续执行,有效减少了性能的损耗
函数节流
当持续触发事件时,保证在一定时间内只调用一次事件处理函数,意思就是说,假设一个用户一直触发这个函数,且每次触发小于既定值,函数节流会每隔这个时间调用一次
用一句话总结防抖和节流的区别:防抖是将多次执行变为最后一次执行,节流是将多次执行变为每隔一段时间执行
实现函数节流我们主要有两种方法
:
○ 时间戳
var throttle = function(func, delay) {
var prev = Date.now();
return function() {
var context = this; //this指向window
var args = arguments;
var now = Date.now();
if (now - prev >= delay) {
func.apply(context, args);
prev = Date.now();
}
}
}
function handle() {
console.log(Math.random());
}
window.addEventListener('scroll', throttle(handle, 1000));
○ 定时器
var throttle = function(func, delay) {
var timer = null;
return function() {
var context = this;
var args = arguments;
if (!timer) {
timer = setTimeout(function() {
func.apply(context, args);
timer = null;
}, delay);
}
}
}
function handle() {
console.log(Math.random());
}
window.addEventListener('scroll', throttle(handle, 1000));
当进行前后端通信时,如果响应没有设置结束导致请求一直处于被挂起的状态,或者超出了我们设置的时间,就会发生通信超时
。
我们可以通过设置请求的timeout属性
来设置超时时间
:
request.timeout = 2000;
超时时间必须设置在open方法执行以后,send方法执行之前。
当超时发生时, timeout事件
将会被触发。
request.addEventListener(“timeout”,timeoutHandler);
当超时发生以后,我们需要断开通信
连接,这时需要使用abort
方法:
request.abort();
综合运用示例
:
var xhr = new XMLHttpRequest();
xhr.addEventListener("readystatechange", readyStateChangeHandler);
xhr.addEventListener("timeout", timeoutHandler); //侦听超时事件
xhr.open("POST", "http://10.9.72.236:4010");
xhr.timeout = 5000; //设置超时时间
xhr.send("a=1&b=2");
function readyStateChangeHandler(e) {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log("通信完成并且成功");
} else if (xhr.readyState === 4) {
console.log("通信完成,但是通信可能有误");
} else {
console.log("通信的过程");
}
}
function timeoutHandler(e) {
console.log("超时了");
xhr.abort(); //断开连接
}
严格模式 strict mode
使用 use strict
指令开启严格模式
"use strict"; //整个js代码都是以严格模式执行
//... js 代码
常见区别
变量必须先声明 后使用
不能使用 delete 关键词删除变量或对象
函数的参数名不能重复
不允许使用 八进制
对象的属性名不能重复
arguments 差别
"use strict";
function fn(a, obj) {
arguments[0] = 2;
arguments[1].b = 2;
console.log(a); // 严格模式为1;非严格模式为2
console.log(obj.b); // 2,因为js中object是地址传递
}fn(1, { b: 1 });
arguments 不能做变量名 或 函数名
为 非箭头函数 设置函数体中的 this 对象
function demo(wife, phone) {
console.log(`${this.name}的${wife}电话是${phone}`);
}
let obj = { name: "然然" };
demo("小乔", "10086"); // undefined的小乔电话是10086
// 然然的小乔电话是10086
demo.apply(obj, ["小乔", "10086"]);
demo.call(obj, "小乔", "10086");
let a = demo.bind(obj, "小乔", "10086");
a()
总结:
apply
: 函数中的 this 替换成参数1, 其余参数放数组中
. 直接触发函数call
: 函数中的 this 替换成 参数1, 其余参数依次摆放
. 直接触发函数bind
: 替换函数中的 this 指向 并 传入其他参数, 返回新的函数
. 不会直接触发函数!设计思想
总之,react的性能优化需要手动去做,而vue的性能优化是自动的,但是vue的响应式机制也有问题,就是当state特别多的时候,Watcher也会很多,会导致卡顿,所以大型应用(状态特别多的)一般用react,更加可控。
实现方式
代码书写
methods
, data
, filter
, directive
, component
…外援
主要优化方案分类
详细方案:
嵌套循环
懒加载
, 加快页面启动速度preload="none"
,页面加载完时就会开始加载。xml
小z-index
改变层级. 让盒子在不同平面// 计算阶乘 5 * 4 * 3 * 2 * 1
function jie(n) {
if (n > 1) {
return n * jie(n - 1);
}
return 1;
}
console.log(jie(5));
职责分离
多端开发
web
和 app
至少两个端.前端工程化
是使用软件工程
的技术和方法来进行前端项目的开发、维护和管理.
早期的非工程化前端开发方式, 与小作坊相似:
● 按照个人习惯制作 html 页面
● 使用 jQuery 等技术添加一些动态效果与数据
● 随便找个 框架 改一改
总之: 没有一个固定的规矩可以遵循, 没有标准化的操作流程. 很难保证质量.
前端工程化就是形成一套规矩, 把前端网站的制作标准化, 大概分为以下措施:
模块化
把耦合在一起的大文件 拆分成功能独立的小文件. 再进行统一的拼装和加载. 这样才能多人协作.
○ 例如 JS 的模块化操作: commonJS
, AMD
, CMD
○ webpack 工具: 进行模块的打包
组件化
代码的设计层面, 把不同的功能解耦合, 设计成可插拔的组件.
○ 相当于: 台式机与笔记本的差别. 台式机的各个零件都可以随意替换 而 不会影响其他组件
规范化
设定一个规范, 让所有参与人员的代码统一风格, 便于团队协作与维护.
○ 目录结构的制定
○ 代码规范
○ 前后端接口规范
○ 文档规范
○ 组件管理
○ git分支管理
○ commit 描述规范
○ 定期 Code Review
○ 视觉图标规范
○ …
自动化
任何简单机械的重复劳动 都应该让机器自动完成
○ 图标合并:webpack – 雪碧图
○ 自动化构建: 脚手架
○ 自动化部署: 脚手架
○ 自动化测试: 脚手架
…
RESTFUL是一种网络应用程序的设计风格和开发方式
RESTFUL特点包括:
1、每一个URI代表1种资源;
2、客户端使用GET、POST、PUT、DELETE4个表示操作方式的动词对服务端资源进行操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源;
rem(font size of the root element)是指相对于根元素的字体大小的单位。简单的说它就是一个相对单
位。看到rem大家一定会想起em单位,em(font size of the element)是指相对于父元素的字体大小的单位。它们之间其实很相似,只不过一个计算的规则是依赖根元素一个是依赖父元素计算。
rem最适合的场景就是 web app. 即在手机端上浏览的网页.
利用 JS 根据设备自动更改根元素字体大小, 就可以实现全局的自动适配
参考: http://caibaojian.com/web-app-rem.html
参考网址:https://blog.csdn.net/fe_dev/article/details/79600448
var arr = [3, 4, 1, 2];
function bubbleSort (arr) {
var max = arr.length - 1;
for (var j = 0; j < max; j++) {
// 声明一个变量,作为标志位
var done = true;
for (var i = 0; i < max - j; i++) {
if (arr[i] > arr[i + 1]) {
var temp = arr[i];
arr[i] = arr[i + 1];
arr[i + 1] = temp;
done = false;
}
}
if (done) {
break;
}
}
return arr;
}
bubbleSort(arr);