通過引入一個新的對象(如小圖片和遠程代理對象)來實現對真實對象的操作或者將新的對 象作為真實對象的一個替身,這種實現機制即為代理模式,通過引入代理對象來間接訪問一 個對象,這就是代理模式的模式動機。
代理模式(Proxy Pattern) :給某一個對象提供一個代理,并由代理對象控制對原對象的引用。代理模式的英 文叫做Proxy或Surrogate,它是一種對象結構型模式。
火車票代購、房產中介、律師、海外代購、明星經紀人
代理模式包含如下角色:
時序圖
一個例子-明星經紀人
abstract class Star {
abstract answerPhone(): void;
}
class Angelababy extends Star {
public available: boolean=true;
answerPhone(): void {
console.log('你好,我是Angelababy.');
}
}
class AngelababyAgent extends Star {
constructor(private angelababy: Angelababy) {
super();
}
answerPhone(): void {
console.log('你好,我是Angelababy的經紀人.');
if (this.angelababy.available) {
this.angelababy.answerPhone();
}
}
}
let angelababyAgent=new AngelababyAgent(new Angelababy());
angelababyAgent.answerPhone();
<body>
<ul id="list">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script>
let list=document.querySelector('#list');
list.addEventListener('click',event=>{
alert(event.target.innerHTML);
});
</script>
</body>
let express=require('express');
let path=require('path')
let app=express();
app.get('/images/loading.gif',function (req,res) {
res.sendFile(path.join(__dirname,req.path));
});
app.get('/images/:name',function (req,res) {
setTimeout(()=> {
res.sendFile(path.join(__dirname,req.path));
}, 2000);
});
app.get('/',function (req,res) {
res.sendFile(path.resolve('index.html'));
});
app.listen(8080);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.bg-container {
width: 600px;
height: 400px;
margin: 100px auto;
}
.bg-container #bg-image {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="background">
<button data-src="/images/bg1.jpg">背景1</button>
<button data-src="/images/bg2.jpg">背景2</button>
</div>
<div class="bg-container">
<img id="bg-image" src="/images/bg1.jpg" />
</div>
<script>
let container=document.querySelector('#background');
class BackgroundImage {
constructor() {
this.bgImage=document.querySelector('#bg-image');
}
setSrc(src) {
this.bgImage.src=src;
}
}
class LoadingBackgroundImage {
static LOADING_URL=`/images/loading.gif`;
constructor() {
this.backgroundImage=new BackgroundImage();
}
setSrc(src) {
this.backgroundImage.setSrc(LoadingBackgroundImage.LOADING_URL);
let img=new Image();
img.onload=()=> {
this.backgroundImage.setSrc(src);
}
img.src=src;
}
}
let loadingBackgroundImage=new LoadingBackgroundImage();
container.addEventListener('click', function (event) {
let src=event.target.dataset.src;
loadingBackgroundImage.setSrc(src + '?ts=' + Date.now());
});
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Lazy-Load</title>
<style>
.image {
width: 300px;
height: 200px;
background-color: #CCC;
}
.image img {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div class="image-container">
<div class="image">
<img data-src="/images/bg1.jpg">
</div>
<div class="image">
<img data-src="/images/bg2.jpg">
</div>
<div class="image">
<img data-src="/images/bg1.jpg">
</div>
<div class="image">
<img data-src="/images/bg2.jpg">
</div>
<div class="image">
<img data-src="/images/bg1.jpg">
</div>
<div class="image">
<img data-src="/images/bg2.jpg">
</div>
<div class="image">
<img data-src="/images/bg1.jpg">
</div>
<div class="image">
<img data-src="/images/bg2.jpg">
</div>
<div class="image">
<img data-src="/images/bg1.jpg">
</div>
<div class="image">
<img data-src="/images/bg2.jpg">
</div>
</div>
</body>
<script>
const imgs=document.getElementsByTagName('img');
const clientHeight=window.innerHeight || document.documentElement.clientHeight;
let loadedIndex=0;
function lazyload() {
for (let i=loadedIndex; i < imgs.length; i++) {
if (clientHeight - imgs[i].getBoundingClientRect().top > 0) {
imgs[i].src=imgs[i].dataset.src;
loadedIndex=i + 1;
}
}
}
lazyload();
window.addEventListener('scroll', lazyload, false);
</script>
</html>
有些時候可以用空間換時間
一個正整數的階乘(factorial)是所有小于及等于該數的正整數的積,并且0的階乘為1
const factorial=function f(num) {
if (num===1) {
return 1;
} else {
return (num * f(num - 1));
}
}
const proxy=function (fn) {
const cache={}; // 緩存對象
return function (num) {
if (num in cache) {
return cache[num]; // 使用緩存代理
}
return cache[num]=fn.call(this, num);
}
}
const proxyFactorial=proxy(factorial);
console.log(proxyFactorial(5));
console.log(proxyFactorial(5));
console.log(proxyFactorial(5));
指的是這樣一個數列:1、1、2、3、5、8、13、21、34。在數學上,斐波那契數列以如下被以遞推的方法定義:F(1)=1,F(2)=1,F(n)=F(n-1)+F(n-2)(n>=3,n∈N*)
let count=0;
function fib(n) {
count++;
return n <=2 ? 1 : fib(n - 1) + fib(n - 2);
}
var result=fib(10);
console.log(result, count);//55 110
let count=0;
const fibWithCache=(function () {
let cache={};
function fib(n) {
count++;
if (cache[n]) {
return cache[n];
}
let result=n <=2 ? 1 : fib(n - 1) + fib(n - 2);
cache[n]=result;
return result;
}
return fib;
})();
var result=fibWithCache(10);
console.log(result, count);//55 17
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
#container {
width: 200px;
height: 400px;
border: 1px solid red;
overflow: auto;
}
#container .content {
height: 4000px;
}
</style>
</head>
<body>
<div id="container">
<div class="content"></div>
</div>
<script>
function throttle(callback, interval) {
let last;
return function () {
let context=this;
let args=arguments;
let now=Date.now();
if (last) {
if (now - last >=interval) {
last=now;
callback.apply(context, args);
}
} else {
callback.apply(context, args);
last=now;
}
}
}
let lastTime=Date.now();
const throttle_scroll=throttle(()=> {
console.log('觸發了滾動事件', (Date.now() - lastTime) / 1000);
}, 1000);
document.getElementById('container').addEventListener('scroll', throttle_scroll);
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
#container {
width: 200px;
height: 400px;
border: 1px solid red;
overflow: auto;
}
#container .content {
height: 4000px;
}
</style>
</head>
<body>
<div id="container">
<div class="content"></div>
</div>
<script>
function debounce(callback, delay) {
let timer;
return function () {
let context=this;
let args=arguments;
if (timer)
clearTimeout(timer);
timer=setTimeout(()=> {
callback.apply(context, args);
}, delay);
}
}
let lastTime=Date.now();
const throttle_scroll=debounce(()=> {
console.log('觸發了滾動事件', (Date.now() - lastTime) / 1000);
}, 1000);
document.getElementById('container').addEventListener('scroll', throttle_scroll);
</script>
</body>
</html>
<body>
<ul id="todos">
</ul>
<script>
let todos=document.querySelector('#todos');
window.onload=function(){
fetch('/todos').then(res=>res.json()).then(response=>{
todos.innerHTML=response.map(item=>`<li "><input value="${item.id}" type="checkbox" ${item.completed?"checked":""}/>${item.text}</li>`).join('');
});
}
function toggle(id){
fetch(`/toggle/${id}`).then(res=>res.json()).then(response=>{
console.log('response',response);
});
}
todos.addEventListener('click',function(event){
let checkbox=event.target;
let id=checkbox.value;
toggle(id);
});
</script>
</body>
app.js
let express=require('express');
let app=express();
app.use(express.static(__dirname));
let todos=[
{id: 1,text: 'a',completed: false},
{id: 2,text: 'b',completed: false},
{id: 3,text: 'c',completed: false},
];
app.get('/todos',function (req,res) {
res.json(todos);
});
app.get('/toggle/:id',function (req,res) {
let id=req.params.id;
todos=todos.map(item=> {
if (item.id==id) {
item.completed=!item.completed;
}
return item;
});
res.json({code:0});
});
app.listen(8080);
todos.html
<body>
<ul id="todos">
</ul>
<script>
let todos=document.querySelector('#todos');
window.onload=function(){
fetch('/todos').then(res=>res.json()).then(response=>{
todos.innerHTML=response.map(item=>`<li "><input value="${item.id}" type="checkbox" ${item.completed?"checked":""}/>${item.text}</li>`).join('');
});
}
function toggle(id){
fetch(`/toggle/${id}`).then(res=>res.json()).then(response=>{
console.log('response',response);
});
}
let LazyToggle=(function(id){
let ids=[];
let timer;
return function(id){
ids.push(id);
if(timer){
clearTimeout(timer);
}
timer=setTimeout(function(){
toggle(ids.join(','));
ids=null;
clearTimeout(timer);
timer=null;
},2000);
}
})();
todos.addEventListener('click',function(event){
let checkbox=event.target;
let id=checkbox.value;
LazyToggle(id);
});
</script>
app.js
app.get('/toggle/:ids',function (req,res) {
let ids=req.params.ids;
ids=ids.split(',').map(item=>parseInt(item));
todos=todos.map(item=> {
if (ids.includes(item.id)) {
item.completed=!item.completed;
}
return item;
});
res.json({code:0});
});
proxy-server.js
const http=require('http');
const httpProxy=require('http-proxy');
//創建一個代理服務
const proxy=httpProxy.createProxyServer();
//創建http服務器并監聽8888端口
let server=http.createServer(function (req, res) {
//將用戶的請求轉發到本地9999端口上
proxy.web(req, res, {
target: 'http://127.0.0.1:9999'
});
//監聽代理服務錯誤
proxy.on('error', function (err) {
console.log(err);
});
});
server.listen(8888, '0.0.0.0');
real-server.js
const http=require('http');
let server=http.createServer(function (req, res) {
res.end('9999');
});
server.listen(9999, '0.0.0.0');
otherWindow.postMessage(message, targetOrigin, [transfer]);
window.addEventListener("message", receiveMessage, false);
origin.js
let express=require('express');
let app=express();
app.use(express.static(__dirname));
app.listen(3000);
target.js
let express=require('express');
let app=express();
let bodyParser=require('body-parser');
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static(__dirname));
let users=[];
app.post('/register', function (req, res) {
let body=req.body;
let target=body.target;
let callback=body.callback;
let username=body.username;
let password=body.password;
let user={ username, password };
let id=users.length==0 ? 1 : users[users.length - 1].id + 1;
user.id=id;
users.push(user);
res.status(302);
res.header('Location', `${target}?callback=${callback}&args=${id}`);
res.end();
});
app.listen(4000);
reg.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script type="text/javascript">
window.addEventListener('message', function (event) {
console.log(event.data);
if (event.data.receiveId) {
alert('用戶ID=' + event.data.receiveId);
}
})
</script>
<iframe name="proxyIframe" id="proxyIframe" frameborder="0"></iframe>
<form action="http://localhost:4000/register" method="POST" target="proxyIframe">
<input type="hidden" name="callback" value="receiveId">
<input type="hidden" name="target" value="http://localhost:3000/target.html">
用戶名<input type="text" name="username" />
密碼<input type="text" name="password" />
<input type="submit" value="提交">
</form>
</body>
</html>
target.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script>
window.onload=function () {
var query=location.search.substr(1).split('&');
let callback, args;
for (let i=0, len=query.length; i < len; i++) {
let item=query[i].split('=');
if (item[0]=='callback') {
callback=item[1];
} else if (item[0]=='args') {
args=item[1];
}
}
try {
window.parent.postMessage({ [callback]: args }, '*');
} catch (error) {
console.log(error);
}
}
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>jquery proxy</title>
</head>
<body>
<button id="btn">點我變紅</button>
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
<script>
let btn=document.getElementById('btn');
btn.addEventListener('click', function () {
setTimeout($.proxy((function () {
$(this).css('color', 'red');
}), this), 1000);
});
</script>
</body>
</html>
function proxy(fn, context) {
return function () {
return fn.call(context, arguments);
}
}
let wang={
name: 'wanglaoshi',
age: 29,
height:165
}
let wangMama=new Proxy(wang,{
get(target,key) {
if (key=='age') {
return wang.age-1;
} else if (key=='height') {
return wang.height-5;
}
return target[key];
},
set(target,key,val) {
if (key=='boyfriend') {
let boyfriend=val;
if (boyfriend.age>40) {
throw new Error('太老');
} else if (boyfriend.salary<20000) {
throw new Error('太窮');
} else {
target[key]=val;
return true;
}
}
}
});
console.log(wangMama.age);
console.log(wangMama.height);
wangMama.boyfriend={
age: 41,
salary:3000
}
Vue2 中的變化偵測實現對 Object 及 Array 分別進行了不同的處理,Object 使用了 Object.defineProperty API,Array使用了攔截器對 Array 原型上的能夠改變數據的方法進行攔截。雖然也實現了數據的變化偵測,但存在很多局限 ,比如對象新增屬性無法被偵測,以及通過數組下邊修改數組內容,也因此在 Vue2 中經常會使用到 $set 這個方法對數據修改,以保證依賴更新。
Vue3 中使用了 es6 的 Proxy API對數據代理,沒有像 Vue2 中對原數據進行修改,只是加了代理包裝,因此首先性能上會有所改善。其次解決了 Vue2 中變化偵測的局限性,可以不使用 $set 新增的對象屬性及通過下標修改數組都能被偵測到。
適配器提供不同接口,代理模式提供一模一樣的接口
裝飾器模式原來的功能不變還可以使用,代理模式改變原來的功能
是解決方案實際上更像是webpack的插件索引。
寫這一篇的目的是為了形成一個所以,將來要用時直接來查找即可。
解決方案:使用插件 html-webpack-plugin
webpack.config.js如下:
module.exports={
entry: './src/app.js',
output: {
path: __dirname + '/dist',
filename: 'app.bundle.js'
},
plugins: [new HtmlWebpackPlugin({
template: './src/模板文件.html',
filename: '構建的.html',
minify: {
collapseWhitespace: true,
},
hash: true,
})]
};
注意要有path,因為這個輸出的html需要知道輸出目錄
loader用于對模塊的源代碼進行預處理轉換。
解決方案:使用css-loader,style-loader
看一下項目結構:
此時運行webpack命令會拋出錯誤:
接下來安裝 css-loader 和 style-loader
npm install --save-dev css-loader style-loader
再修改webpack.config.js為:
這其中rules數組就是loader用來的匹配和轉換資源的規則數組。
test代表匹配需轉換文件的正則表達式,而圖中表示匹配所有以css結尾的文件。
而use數組代表用哪些loader去處理這些匹配到的文件。
此時再運行webpack,打包后的文件bundle.js就包含了css代碼。
其中css-loader負責加載css,打包css到js中。
而style-loader負責生成:在js運行時,將css代碼通過style標簽注入到dom中。
解決方案:使用less-loader
但是用less-loader只是將LESS代碼轉換為css代碼。如果要打包文件到js中,還是需要用到上面提到的css-loader和style-loader。
看一下項目結構:
然后app.js的代碼為:
import styles from './app.less';
console.info('我是一個js文件123')
為了解決這種情況,首先要安裝 less-loader,而less-loader是基于less的,所以也要安裝less。
npm i --save-dev less less-loader
修改webpack.config.js為:
module: {
rules: [
{
test: /\.less$/,
use: [ 'style-loader', 'css-loader', 'less-loader' ]
}
]
}
很多時候我們想要的效果并不是想要把幾個LESS或者CSS處理好后,打包到一個js中,而是想要把它打包到一個css文件中。
此時就有了插件 extract-text-webpack-plugin。
首先進行安裝
npm i --save-dev extract-text-webpack-plugin
然后修改webpack.config.js為:
紅框中為新加或修改的配置
與原配置對比可以發現,比html-webpack-plugin這個插件多做了一步,就是在匹配和轉換規則里面的use中使用了ExtractTextPlugin.extract。
注意這里的fallback表示,在提取文件失敗后,將繼續使用style-loader去打包到js中。
此時運行webpack
可以發現輸出目錄build下生成了一個style.css文件,也就是我們在webpack.config.js中期望生成的文件,并且在生成的demo.html中被引用了。
webpack-dev-server可以在本地搭建一個簡單的開發環境用的服務器,自動打開瀏覽器,而且還可以達到webpack -watch的效果。
首先安裝一下:
npm i -g webpack-dev-server
npm i --save-dev webpack-dev-server
這里不需要改動webpack.config.js,直接運行命令
webpack-dev-server
查看控制臺輸出:
控制臺輸出
顯示項目運行在http://localhost:8080/
webpack的輸出目錄的路徑在/下面
并且這個服務器會自動識別輸出目錄下名為index的HTML文件,而我們之前輸出的文件名為demo.html。
所以還需要將之前html-webpack-plugin中配置的filename改為index.html,或者直接用http://localhost:8080/demo.html也行。
當我們修改了源代碼后,打開的網頁還會自動更新。
為了更靈活的應用開發環境的服務器,也可以在webpack.config.js中加入如下代碼:
devServer配置功能
port修改端口為8787,而不是默認的8080。
open為true表示會自動打開瀏覽器,而不是需要我們再手動打開瀏覽器并在里面輸入http://localhost:8080。
compress對本地server返回的文件提供gzip壓縮
index指定網站首頁映射的文件,默認為index.html
這里說是ES6,實際上可以認為是ECMAScript的高版本代碼,只是代指而已。
babel的作用是將瀏覽器還未支持的這些高版本js代碼轉換成可以被指定瀏覽器支持的js代碼。
這里列出可以轉換的大致語法:
babel-preset-env支持的轉換
那么首先就需要安裝babel
npm install babel-core babel-preset-env --save-dev
然后,為了和webpack結合起來,要用到babel-loader
npm install babel-loader --save-dev
然后在webpack.config.js的rules數組中增加以下代碼:
{
test: /\.js$/,
exclude: /(node_modules)/,
use: {
loader: 'babel-loader',
options: {
presets: ['env']
}
}
}
這行代碼的意思是用babel-loader解析除了node_modules文件下的所有js文件。
而babel-loader就是用babel去解析js代碼。
options的內容類似于.babelrc文件的配置,有了這個就不需要.babelrc文件了。
presets表示預處理器,現在的babel不像以前需要很多預處理器了,只需要env這一個就夠了。
修改之前的app.js中的代碼為:
console.info('我是一個js文件123')
const doSomething=()=> {
console.info('do do do')
}
使用webpack命令后,可以看到我們最后的打包js文件中代碼變成了這樣:
以下為這些新增函數:
安裝:
npm install --save-dev babel-polyfill
為了確保babel-polyfill被最先加載和解析,所以一般都是講babel-polyfill在最開始的腳本中引入。
而在webpack中,就是在放到entry中,所以需要修改webpack.config.js中的配置為:
安裝
npm install --save-dev babel-preset-react
配置:
這里是匹配所有以js或者jsx結尾的文件,并用 babel-preset-env和babel-preset-react進行解析
這里首先介紹一下nodejs的path模塊的一個功能:resolve。
將相對路徑轉換為絕對路徑。
在最開始引用path模塊
var path=require('path');
然后可以在輸出設置那里修改代碼為:
output: {
path: path.resolve(__dirname, 'build'),
filename: 'bundle.js'
},
和我們原來的代碼沒有任何區別。
hash和chunkhash有區別,hash的話輸出的文件用的都是同一個hash值,而chunkhash的話是根據模塊來計算的,每個輸出文件的hash值都不一樣。
直接將輸出文件改為
output: {
path: path.resolve(__dirname, 'build'),
filename: 'bundle.[chunkhash].js'
},
[chunkhash]就代表一串隨機的hash值
當我們像上面一樣不斷改變輸出文件時,之前的輸出文件并沒有去掉。
為了解決這個問題就需要clean-webpack-plugin。
首先安裝
npm i clean-webpack-plugin --save-dev
然后引用插件,并聲明每次生成輸出需要清空的文件夾
var CleanWebpackPlugin=require('clean-webpack-plugin');
var pathsToClean=[
'build',
]
再在插件配置中加入:
new CleanWebpackPlugin(pathsToClean)
之前的webpack-dev-server提供了監聽功能,只要代碼改變,瀏覽器就會刷新。
但是模塊熱替換是不會刷新瀏覽器,只刷新修改到的那部分模塊。
模塊熱替換無需安裝。
首先需要引入模塊
var webpack=require('webpack')
其實插件中加入:
new webpack.NamedModulesPlugin(),
new webpack.HotModuleReplacementPlugin()
此時運行webpack可能會報錯,我們需要把之前在輸出環境中寫的[chunkhash]改為[hash]
可以在腳本中這么寫:
"scripts": {
"dev": "webpack-dev-server",
"prod": "set NODE_ENV=production && webpack -p"
},
這樣在webpack.config.js中這樣修改上面的東西:
if (isProduction) {
config.output.filename='bundle.[chunkhash].js'
} else {
config.plugins.push(new webpack.NamedModulesPlugin())
config.plugins.push(new webpack.HotModuleReplacementPlugin())
}
這樣就可以根據環境的不同來運行不同的配置
上述設置環境變量的腳本中只有在window下才有效,在linux和mac上需要使用
"prod": "NODE_ENV=production webpack -p"
為了解決這個問題,使得不同平臺的人能公用一套代碼,我們可以使用cross-env。
首先進行安裝:
npm i --save-dev cross-env
然后命令直接使用類似于mac上的用法即可
"prod": "cross-env NODE_ENV=production webpack -p"
file-loader可以用來處理圖片和字體文件在css文件中的路徑問題,輸出的css文件中會引用輸出的文件地址。
html-loader可以用來處理html中,比如img元素的圖片路徑問題。
首先安裝
npm i --save-dev file-loader html-loader
配置:
{
test: /\.(gif|png|jpe?g|svg)$/i,
use: {
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'src/images/'
}
}
},
{
test: /\.html$/,
use: [{
loader: 'html-loader',
options: {
minimize: true
}
}],
}
安裝:
npm i --save-dev image-webpack-loader
配置:
{
test: /\.(gif|png|jpe?g|svg)$/i,
use: [{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'images/'
}
},
{
loader: 'image-webpack-loader',
options: {
bypassOnDebug: true,
}
}
]
},
這里的options中也可以具體配置各個圖片類型的壓縮質量
如果我們用web-dev-server運行我們的輸出文件,發現其中有些BUG,然后打開開發者工具取定位文件的時候,只會定位到我們的輸出文件。
而這些輸出文件是經過處理的,我們只有找到我們的源文件代碼,然后進行相應的修改才能解決問題。
于是這里我們需要用到source-map。
很簡單,在webpack.config.js中加入如下配置即可:
devtool: 'source-map',
就這么簡單,還不需要安裝什么插件。
但是這只對js有效,如果我們的css出現錯誤了呢,答案就是如下配置:
在這些loader后面加上?sourceMap即可
之前我們通過在命令中設置環境變量,并且通過環境變量來判斷環境來進行不同的配置。
現在我們用官方推薦的方法來分離生產環境和開發環境的配置文件。
我們將webpack.config.js分為三個文件
webpack.common.js
webpack.dev.js
webpack.prod.js
其中webpack.common.config.js為生產環境和開發環境共有的配置,dev為開發環境獨有的配置,prod為生成環境獨有的配置。
而想要合成真正的配置文件,還需要一個工具:webpack-merge。
npm install --save-dev webpack-merge
以下是我們之前的webpack.config.js代碼:
var ExtractTextPlugin=require('extract-text-webpack-plugin')
var HtmlWebpackPlugin=require('html-webpack-plugin')
var CleanWebpackPlugin=require('clean-webpack-plugin')
var path=require('path')
var webpack=require('webpack')
var pathsToClean=[
'build',
]
var isProduction=process.env.NODE_ENV==='production'
var config={
entry: ['babel-polyfill', './src/app.js'],
output: {
path: path.resolve(__dirname, 'build'),
filename: '[name].[hash].js'
},
devtool: 'source-map',
devServer: {
port: 8787,
open: true,
compress: true,
index: 'demo.html'
},
plugins: [
new HtmlWebpackPlugin({
template: './template/index.html',
filename: 'demo.html',
minify: {
collapseWhitespace: true,
},
hash: true
}),
new ExtractTextPlugin({ filename: 'style.css', allChunks: false }),
new CleanWebpackPlugin(pathsToClean)
],
module: {
rules: [{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: ['css-loader?sourceMap']
})
},
{
test: /\.less$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: ['css-loader?sourceMap', 'less-loader?sourceMap']
})
},
{
test: /\.jsx?$/,
exclude: /(node_modules)/,
use: {
loader: 'babel-loader',
options: {
presets: ['env', 'react']
}
}
},
{
test: /\.(gif|png|jpe?g|svg)$/i,
use: [{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'images/'
}
},
{
loader: 'image-webpack-loader',
options: {
bypassOnDebug: true,
}
}
]
},
{
test: /\.html$/,
use: [{
loader: 'html-loader',
options: {
minimize: true
}
}],
}
]
}
};
if (isProduction) {
config.output.filename='[name].[chunkhash].js'
} else {
config.plugins.push(new webpack.NamedModulesPlugin())
config.plugins.push(new webpack.HotModuleReplacementPlugin())
}
module.exports=config
接下來分為三個文件,webpack.common.js:
var ExtractTextPlugin=require('extract-text-webpack-plugin')
var HtmlWebpackPlugin=require('html-webpack-plugin')
var CleanWebpackPlugin=require('clean-webpack-plugin')
var path=require('path')
var webpack=require('webpack')
var pathsToClean=[
'build',
]
var isProduction=process.env.NODE_ENV==='production'
module.exports={
entry: ['babel-polyfill', './src/app.js'],
output: {
path: path.resolve(__dirname, 'build'),
filename: '[name].[chunkhash].js'
},
plugins: [
new HtmlWebpackPlugin({
template: './template/index.html',
filename: 'demo.html',
minify: {
collapseWhitespace: true,
},
hash: isProduction
}),
new ExtractTextPlugin({ filename: '[name].[contenthash].css', allChunks: false }),
new CleanWebpackPlugin(pathsToClean)
],
module: {
rules: [{
test: /\.jsx?$/,
exclude: /(node_modules)/,
use: {
loader: 'babel-loader',
options: {
presets: ['env', 'react']
}
}
},
{
test: /\.(gif|png|jpe?g|svg)$/i,
use: [{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'images/'
}
},
{
loader: 'image-webpack-loader',
options: {
bypassOnDebug: true,
}
}
]
},
{
test: /\.html$/,
use: [{
loader: 'html-loader',
options: {
minimize: true
}
}],
}
]
}
};
然后是webpack.dev.js:
const merge=require('webpack-merge');
const common=require('./webpack.common.js');
const webpack=require('webpack');
const ExtractTextPlugin=require('extract-text-webpack-plugin')
module.exports=merge(common, {
output: {
filename: '[name].[hash].js'
},
devtool: 'source-map',
devServer: {
port: 8787,
open: true,
compress: true,
index: 'demo.html'
},
plugins: [
new webpack.NamedModulesPlugin(),
new webpack.HotModuleReplacementPlugin()
],
module: {
rules: [{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: ['css-loader?sourceMap']
})
},
{
test: /\.less$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: ['css-loader?sourceMap', 'less-loader?sourceMap']
})
}
]
}
});
最后是webpack.prod.js:
const merge=require('webpack-merge');
const common=require('./webpack.common.js');
const ExtractTextPlugin=require('extract-text-webpack-plugin')
module.exports=merge(common, {
module: {
rules: [{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: ['css-loader']
})
},
{
test: /\.less$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: ['css-loader', 'less-loader']
})
}
]
}
});
然后修改一下package.json中的腳本即可。
"scripts": {
"dev": "webpack-dev-server --config webpack.dev.js",
"prod": "cross-env NODE_ENV=production webpack -p --config webpack.prod.js"
},
各個插件以及loader的玩法還有很多,這里不具體介紹。
對于MVVM的理解
MVVM是Model-View-ViewModel縮寫,也就是把MVC中的Controller演變成ViewModel。Model層代表數據模型,View代表UI組件,ViewModel是View和Model層的橋梁,數據會綁定到viewModel層并自動將數據渲染到頁面中,視圖變化的時候會通知viewModel層更新數據。
答:總共分為8個階段創建前/后,載入前/后,更新前/后,銷毀前/后
生命周期是什么
Vue 實例有一個完整的生命周期,也就是從開始創建、初始化數據、編譯模版、掛載Dom -> 渲染、更新 -> 渲染、卸載等一系列過程,我們稱這是Vue的生命周期
各個生命周期的作用
生命周期描述beforeCreate組件實例被創建之初,組件的屬性生效之前created組件實例已經完全創建,屬性也綁定,但真實dom還沒有生成,$el還不可用beforeMount在掛載開始之前被調用:相關的 render 函數首次被調用mountedel 被新創建的 vm.$el 替換,并掛載到實例上去之后調用該鉤子beforeUpdate組件數據更新之前調用,發生在虛擬 DOM 打補丁之前update組件數據更新之后activitedkeep-alive專屬,組件被激活時調用deadctivatedkeep-alive專屬,組件被銷毀時調用beforeDestory組件銷毀前調用destoryed組件銷毀后調用
image
由于Vue會在初始化實例時對屬性執行getter/setter轉化,所以屬性必須在data對象上存在才能讓Vue將它轉換為響應式的。Vue提供了$set方法用來觸發視圖更新
export default {
data(){
return {
obj: {
name: 'fei'
}
}
},
mounted(){
this.$set(this.obj, 'sex', 'man')
}
}
什么是vue生命周期?
vue生命周期的作用是什么?
vue生命周期總共有幾個階段?
第一次頁面加載會觸發哪幾個鉤子?
DOM 渲染在哪個周期中就已經完成?
父組件與子組件傳值
父組件傳給子組件:子組件通過props方法接受數據;
非父子組件間的數據傳遞,兄弟組件傳值
eventBus,就是創建一個事件中心,相當于中轉站,可以用它來傳遞事件和接收事件。項目比較小時,用這個比較合適(雖然也有不少人推薦直接用VUEX,具體來說看需求)
首頁可以控制導航跳轉,beforeEach,afterEach等,一般用于頁面title的修改。一些需要登錄才能調整頁面的重定向功能。
image
image
modules:項目特別復雜的時候,可以讓每一個模塊擁有自己的state、mutation、action、getters,使得結構非常清晰,方便管理
image
將當前組件的<style>修改為<style scoped>
keep-alive可以實現組件緩存,當組件切換時不會對當前組件進行卸載
比如有一個列表和一個詳情,那么用戶就會經常執行打開詳情=>返回列表=>打開詳情…這樣的話列表和詳情都是一個頻率很高的頁面,那么就可以對列表組件使用<keep-alive></keep-alive>進行緩存,這樣用戶每次返回列表的時候,都能從緩存中快速渲染,而不是重新渲染
提供一個在頁面上已存在的 DOM元素作為 Vue實例的掛載目標.可以是 CSS 選擇器,也可以是一個 HTMLElement 實例,
問題一:構建的 vue-cli 工程都到了哪些技術,它們的作用分別是什么?
問題二:vue-cli 工程常用的 npm 命令有哪些?
npm install
npm run dev
npm run build
npm run build --report
在瀏覽器上自動彈出一個 展示 vue-cli 工程打包后 app.js、manifest.js、vendor.js 文件里面所包含代碼的頁面。可以具此優化 vue-cli 生產環境部署的靜態資源,提升 頁面 的加載速度
nextTick可以讓我們在下次 DOM 更新循環結束之后執行延遲回調,用于獲得更新后的 DOM
聲明式(標簽跳轉)
<router-link :to="index">
編程式( js跳轉)
router.push('index')
其基本實現原理
Vue SSR 的實現,主要就是把 Vue 的組件輸出成一個完整 HTML, vue-server-renderer 就是干這事的
實現時,主要如下
可以簡單理解成以下步驟:
用 timeline 工具。大意是通過 timeline 來查看每個函數的調用時常,定位出哪個函數的問題,從而能判斷哪個組件出了問題
使用了v-if的時候,如果值為false,那么頁面將不會有這個html標簽生成。v-show則是不管值為true還是false,html元素都會存在,只是CSS中的display顯示或隱藏
語法:v-bind:title="msg"簡寫::title="msg"
Object.defineProperty() 的問題主要有三個:
Proxy 在 ES2015 規范中被正式加入,它有以下幾個特點
除了上述兩點之外,Proxy 還擁有以下優勢:
全局守衛
vue-router全局有三個守衛
// main.js 入口文件
import router from './router'; // 引入路由
router.beforeEach((to, from, next)=> {
next();
});
router.beforeResolve((to, from, next)=> {
next();
});
router.afterEach((to, from)=> {
console.log('afterEach 全局后置鉤子');
});
路由獨享守衛
如果你不想全局配置守衛的話,你可以為某些路由單獨配置守衛
const router=new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next)=> {
// 參數用法什么的都一樣,調用順序在全局前置守衛后面,所以不會被全局守衛覆蓋
// ...
}
}
]
})
路由組件內的守衛
組件之間通訊分為三種: 父傳子、子傳父、兄弟組件之間的通訊
1. 父組件給子組件傳值
<template>
<child :msg="message"></child>
</template>
<script>
import child from './child.vue';
export default {
components: {
child
},
data () {
return {
message: 'father message';
}
}
}
</script>
子組件vue模板child.vue:
<template>
<div>{{msg}}</div>
</template>
<script>
export default {
props: {
msg: {
type: String,
required: true
}
}
}
</script>
2. 子組件向父組件通信
父組件向子組件傳遞事件方法,子組件通過$emit觸發事件,回調給父組件
父組件vue模板father.vue:
<template>
<child @msgFunc="func"></child>
</template>
<script>
import child from './child.vue';
export default {
components: {
child
},
methods: {
func (msg) {
console.log(msg);
}
}
}
</script>
子組件vue模板child.vue:
<template>
<button @click="handleClick">點我</button>
</template>
<script>
export default {
props: {
msg: {
type: String,
required: true
}
},
methods () {
handleClick () {
//........
this.$emit('msgFunc');
}
}
}
</script>
3. 非父子, 兄弟組件之間通信
vue2中廢棄了broadcast廣播和分發事件的方法。父子組件中可以用props和$emit()。如何實現非父子組件間的通信,可以通過實例一個vue實例Bus作為媒介,要相互通信的兄弟組件之中,都引入Bus,然后通過分別調用Bus事件觸發和監聽來實現通信和參數傳遞。Bus.js可以是這樣:
import Vue from 'vue'
export default new Vue()
在需要通信的組件都引入Bus.js:
<template>
<button @click="toBus">子組件傳給兄弟組件</button>
</template>
<script>
import Bus from '../common/js/bus.js'
export default{
methods: {
toBus () {
Bus.$emit('on', '來自兄弟組件')
}
}
}
</script>
另一個組件也import Bus.js 在鉤子函數中監聽on事件
import Bus from '../common/js/bus.js'
export default {
data() {
return {
message: ''
}
},
mounted() {
Bus.$on('on', (msg)=> {
this.message=msg
})
}
}
Vue與AngularJS的區別
Vue與React的區別
vuex的使用借助官方提供的一張圖來說明:
image
Vuex有5種屬性: 分別是 state、getter、mutation、action、module;
state
Vuex 使用單一狀態樹,即每個應用將僅僅包含一個store 實例,但單一狀態樹和模塊化并不沖突。存放的數據狀態,不可以直接修改里面的數據
mutations
mutations定義的方法動態修改Vuex 的 store 中的狀態或數據。
getters
類似vue的計算屬性,主要用來過濾一些數據
action
一般面試官問到這里vue基本知識就差不多了, 如果更深入的研究就是和你探討關于vue的底層源碼;或者是具體在項目中遇到的問題,下面列舉幾個項目中可能遇到的問題:
computed:
watch:
小結:
利用Object.defineProperty劫持對象的訪問器,在屬性值發生變化時我們可以獲取變化,然后根據變化進行后續響應,在vue3.0中通過Proxy代理對象進行類似的操作。
// 這是將要被劫持的對象
const data={
name: '',
};
function say(name) {
if (name==='古天樂') {
console.log('給大家推薦一款超好玩的游戲');
} else if (name==='渣渣輝') {
console.log('戲我演過很多,可游戲我只玩貪玩懶月');
} else {
console.log('來做我的兄弟');
}
}
// 遍歷對象,對其屬性值進行劫持
Object.keys(data).forEach(function(key) {
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function() {
console.log('get');
},
set: function(newVal) {
// 當屬性值發生變化時我們可以進行額外操作
console.log(`大家好,我系${newVal}`);
say(newVal);
},
});
});
data.name='渣渣輝';
//大家好,我系渣渣輝
//戲我演過很多,可游戲我只玩貪玩懶月
Vue 采用數據劫持結合發布—訂閱模式的方法,通過 Object.defineProperty() 來劫持各個屬性的 setter,getter,在數據變動時發布消息給訂閱者,觸發相應的監聽回調。
image
Watcher 訂閱者是 Observer 和 Compile 之間通信的橋梁,主要做的事情
Vue3.x響應式數據原理
Vue3.x改用Proxy替代Object.defineProperty。因為Proxy可以直接監聽對象和數組的變化,并且有多達13種攔截方法。并且作為新標準將受到瀏覽器廠商重點持續的性能優化。
Proxy只會代理對象的第一層,那么Vue3又是怎樣處理這個問題的呢?
判斷當前Reflect.get的返回值是否為Object,如果是則再通過reactive方法做代理, 這樣就實現了深度觀測。
監測數組的時候可能觸發多次get/set,那么如何防止觸發多次呢?
我們可以判斷key是否為當前被代理對象target自身屬性,也可以判斷舊值與新值是否相等,只有滿足以上兩個條件之一時,才有可能執行trigger
v-model本質上是語法糖,v-model在內部為不同的輸入元素使用不同的屬性并拋出不同的事件
所以我們可以v-model進行如下改寫:
<input v-model="sth" />
// 等同于
<input :value="sth" @input="sth=$event.target.value" />
//Parent
<template>
{{num}}
<Child v-model="num">
</template>
export default {
data(){
return {
num: 0
}
}
}
//Child
<template>
<div @click="add">Add</div>
</template>
export default {
props: ['value'],
methods:{
add(){
this.$emit('input', this.value + 1)
}
}
}
scoped雖然避免了組件間樣式污染,但是很多時候我們需要修改組件中的某個樣式,但是又不想去除scoped屬性
//Parent
<template>
<div class="wrap">
<Child />
</div>
</template>
<style lang="scss" scoped>
.wrap /deep/ .box{
background: red;
}
</style>
//Child
<template>
<div class="box"></div>
</template>
//Parent
<template>
<div class="wrap">
<Child />
</div>
</template>
<style lang="scss" scoped>
//其他樣式
</style>
<style lang="scss">
.wrap .box{
background: red;
}
</style>
//Child
<template>
<div class="box"></div>
</template>
Computed本質是一個具備緩存的watcher,依賴的屬性發生變化就會更新視圖。適用于計算比較消耗性能的計算場景。當表達式過于復雜時,在模板中放入過多邏輯會讓模板難以維護,可以將復雜的邏輯放入計算屬性中處理
image
<template>{{fullName}}</template>
export default {
data(){
return {
firstName: 'xie',
lastName: 'yu fei',
}
},
computed:{
fullName: function(){
return this.firstName + ' ' + this.lastName
}
}
}
Watch沒有緩存性,更多的是觀察的作用,可以監聽某些數據執行回調。當我們需要深度監聽對象中的屬性時,可以打開deep:true選項,這樣便會對對象中的每一項進行監聽。這樣會帶來性能問題,優化的話可以使用字符串形式監聽,如果沒有寫到組件中,不要忘記使用unWatch手動注銷
image
<template>{{fullName}}</template>
export default {
data(){
return {
firstName: 'xie',
lastName: 'xiao fei',
fullName: 'xie xiao fei'
}
},
watch:{
firstName(val) {
this.fullName=val + ' ' + this.lastName
},
lastName(val) {
this.fullName=this.firstName + ' ' + val
}
}
}
導航守衛 router.beforeEach 全局前置守衛
// main.js 入口文件
import router from './router'; // 引入路由
router.beforeEach((to, from, next)=> {
next();
});
router.beforeResolve((to, from, next)=> {
next();
});
router.afterEach((to, from)=> {
console.log('afterEach 全局后置鉤子');
});
路由獨享的守衛 你可以在路由配置上直接定義 beforeEnter 守衛
const router=new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next)=> {
// ...
}
}
]
})
組件內的守衛你可以在路由組件內直接定義以下路由導航守衛
const Foo={
template: `...`,
beforeRouteEnter (to, from, next) {
// 在渲染該組件的對應路由被 confirm 前調用
// 不!能!獲取組件實例 `this`
// 因為當守衛執行前,組件實例還沒被創建
},
beforeRouteUpdate (to, from, next) {
// 在當前路由改變,但是該組件被復用時調用
// 舉例來說,對于一個帶有動態參數的路徑 /foo/:id,在 /foo/1 和 /foo/2 之間跳轉的時候,
// 由于會渲染同樣的 Foo 組件,因此組件實例會被復用。而這個鉤子就會在這個情況下被調用。
// 可以訪問組件實例 `this`
},
beforeRouteLeave (to, from, next) {
// 導航離開該組件的對應路由時調用,我們用它來禁止用戶離開
// 可以訪問組件實例 `this`
// 比如還未保存草稿,或者在用戶離開前,
將setInterval銷毀,防止離開之后,定時器還在調用。
}
}
當時的思路是頭部(Header)一般分為左、中、右三個部分,分為三個區域來設計,中間為主標題,每個頁面的標題肯定不同,所以可以通過vue props的方式做成可配置對外進行暴露,左側大部分頁面可能都是回退按鈕,但是樣式和內容不盡相同,右側一般都是具有功能性的操作按鈕,所以左右兩側可以通過vue slot插槽的方式對外暴露以實現多樣化,同時也可以提供default slot默認插槽來統一頁面風格
Proxy的優勢如下:
Object.defineProperty的優勢如下:
兼容性好,支持IE9
image
響應式系統簡述:
現代前端框架有兩種方式偵測變化,一種是pull一種是push
考點: Vue的變化偵測原理
前置知識: 依賴收集、虛擬DOM、響應式系統
根本原因是Vue與React的變化偵測方式有所不同
diff程可以概括為:oldCh和newCh各有兩個頭尾的變量StartIdx和EndIdx,它們的2個變量相互比較,一共有4種比較方式。如果4種比較都沒匹配,如果設置了key,就會用key進行比較,在比較的過程中,變量會往中間靠,一旦StartIdx>EndIdx表明oldCh和newCh至少有一個已經遍歷完了,就會結束比較,這四種比較方式就是首、尾、舊尾新頭、舊頭新尾.
準確: 如果不加key,那么vue會選擇復用節點(Vue的就地更新策略),導致之前節點的狀態被保留下來,會產生一系列的bug. 快速: key的唯一性可以被Map數據結構充分利用,相比于遍歷查找的時間復雜度O(n),Map的時間復雜度僅僅為O(1).
image
代碼層面:
Webpack 層面優化:
nextTick 可以讓我們在下次 DOM 更新循環結束之后執行延遲回調,用于獲得更新后的 DOM
nextTick主要使用了宏任務和微任務。根據執行環境分別嘗試采用
定義了一個異步方法,多次調用nextTick會將方法存入隊列中,通過這個異步方法清空當前隊列
使用了函數劫持的方式,重寫了數組的方法,Vue將data中的數組進行了原型鏈重寫,指向了自己定義的數組原型方法。這樣當調用數組api時,可以通知依賴更新。如果數組中包含著引用類型,會對數組中的引用類型再次遞歸遍歷進行監控。這樣就實現了監測數組變化。
接口請求一般放在mounted中,但需要注意的是服務端渲染時不支持mounted,需要放到created中
一個組件被復用多次的話,也就會創建多個實例。本質上,這些實例用的都是同一個構造函數。如果data是對象的話,對象屬于引用類型,會影響到所有的實例。所以為了保證組件不同的實例之間data不沖突,data必須是一個函數
v-model本質就是一個語法糖,可以看成是value + input方法的語法糖。可以通過model屬性的prop和event屬性來進行自定義。原生的v-model,會根據標簽的不同生成不同的事件和屬性
原生事件綁定是通過addEventListener綁定給真實元素的,組件事件綁定是通過Vue自定義的$on實現的
簡單說,Vue的編譯過程就是將template轉化為render函數的過程。會經歷以下階段:
簡單來說,diff算法有以下過程
key的作用是盡可能的復用 DOM 元素
加載渲染過程
父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount- >子mounted->父mounted
子組件更新過程
父beforeUpdate->子beforeUpdate->子updated->父updated
父組件更新過程
父 beforeUpdate -> 父 updated
銷毀過程
父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
SSR也就是服務端渲染,也就是將Vue在客戶端把標簽渲染成HTML的工作放在服務端完成,然后再把html直接返回給客戶端
SSR有著更好的SEO、并且首屏加載速度更快等優點。不過它也有一些缺點,比如我們的開發條件會受到限制,服務器端渲染只支持beforeCreate和created兩個鉤子,當我們需要一些外部擴展庫時需要特殊處理,服務端渲染應用程序也需要處于Node.js的運行環境。還有就是服務器會有更大的負載需求
編碼階段
SEO優化
打包優化
用戶體驗
還可以使用緩存(客戶端緩存、服務端緩存)優化、服務端開啟gzip壓縮等。
Vuex 是一個專為 Vue.js 應用程序開發的狀態管理模式。
有 5 種,分別是 state、getter、mutation、action、module
vuex 的 getter 是什么?
vuex 的 mutation 是什么?
vuex 的 action 是什么?
面對復雜的應用程序,當管理的狀態比較多時;我們需要將vuex的store對象分割成模塊(modules)。
如果請求來的數據不是要被其他組件公用,僅僅在請求的組件內使用,就不需要放入 vuex 的 state 里如果被其他地方復用,請將請求放入 action 里,方便復用,并包裝成 promise 返回
將當前組件的<style>修改為<style scoped>
var a=[1,2,3,4]
var b=[1,2,3,4]
delete a[0]
console.log(a) //[empty,2,3,4]
this.$delete(b,0)
console.log(b) //[2,3,4]
可以
<input type="text" :value="name" @input="onInput" @focus="onFocus" @blur="onBlur" />
v-on 常用修飾符
babel-polyfill插件
以下方法調用會改變原始數組:push(), pop(), shift(), unshift(), splice(), sort(), reverse(),Vue.set( target, key, value )
在mounted
注意 mounted 不會承諾所有的子組件也都一起被掛載。如果你希望等到整個視圖都渲染完畢,可以用 vm.$nextTick 替換掉 mounted
mounted: function () {
this.$nextTick(function () {
// Code that will run only after the
// entire view has been rendered
})
}
第一次加載會觸發哪幾個鉤子
會觸發beforeCreate , created ,beforeMount ,mounted
active classname, isActive 變量
<div :class="{ active: isActive }"></div>
*請認真填寫需求信息,我們會在24小時內與您取得聯系。