vue全家桶学习笔记02

625 阅读17分钟

webpack打包后文件的运行方式

  1. 直接运行build.js文件

    图片:pic.images.ac.cn/image/5e86e…

  2. 配置build文件,然后通过run命令来运行打包后的build文件

    图片:pic.images.ac.cn/image/5e86e…

    图片:pic.images.ac.cn/image/5e86e…

    3.上面那种方法也可以通过webpack直接运行

    图片:pic.images.ac.cn/image/5e86e…

webpack 编译之后的build.js文件解读

打包后的每个文件都会有一个解析函数

// 模块化文件
/******/
(function(modules) { // webpackBootstrap
	/******/ // The module cache
	/******/
	var installedModules = {};
	/******/
	/******/ // The require function
	/******/
	function __webpack_require__(moduleId) {
		/******/
		/******/ // Check if module is in cache
		/******/
		if (installedModules[moduleId]) {
			/******/
			return installedModules[moduleId].exports;
			/******/
		}
		/******/ // Create a new module (and put it into the cache)
		/******/
		var module = installedModules[moduleId] = {
			/******/
			i: moduleId,
			/******/
			l: false,
			/******/
			exports: {}
			/******/
		};
		/******/
		/******/ // Execute the module function
		/******/
		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
		/******/
		/******/ // Flag the module as loaded
		/******/
		module.l = true;
		/******/
		/******/ // Return the exports of the module
		/******/
		return module.exports;
		/******/
	}
	/******/
	/******/
	/******/ // expose the modules object (__webpack_modules__)
	/******/
	__webpack_require__.m = modules;
	/******/
	/******/ // expose the module cache
	/******/
	__webpack_require__.c = installedModules;
	/******/
	/******/ // define getter function for harmony exports
	/******/
	__webpack_require__.d = function(exports, name, getter) {
		/******/
		if (!__webpack_require__.o(exports, name)) {
			/******/
			Object.defineProperty(exports, name, {
				enumerable: true,
				get: getter
			});
			/******/
		}
		/******/
	};
	/******/
	/******/ // define __esModule on exports
	/******/
	__webpack_require__.r = function(exports) {
		/******/
		if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
			/******/
			Object.defineProperty(exports, Symbol.toStringTag, {
				value: 'Module'
			});
			/******/
		}
		/******/
		Object.defineProperty(exports, '__esModule', {
			value: true
		});
		/******/
	};
	/******/
	/******/ // create a fake namespace object
	/******/ // mode & 1: value is a module id, require it
	/******/ // mode & 2: merge all properties of value into the ns
	/******/ // mode & 4: return value when already ns object
	/******/ // mode & 8|1: behave like require
	/******/
	__webpack_require__.t = function(value, mode) {
		/******/
		if (mode & 1) value = __webpack_require__(value);
		/******/
		if (mode & 8) return value;
		/******/
		if ((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
		/******/
		var ns = Object.create(null);
		/******/
		__webpack_require__.r(ns);
		/******/
		Object.defineProperty(ns, 'default', {
			enumerable: true,
			value: value
		});
		/******/
		if (mode & 2 && typeof value != 'string')
			for (var key in value) __webpack_require__.d(ns, key, function(key) {
				return value[key];
			}.bind(null, key));
		/******/
		return ns;
		/******/
	};
	/******/
	/******/ // getDefaultExport function for compatibility with non-harmony modules
	/******/
	__webpack_require__.n = function(module) {
		/******/
		var getter = module && module.__esModule ?
			/******/
			function getDefault() {
				return module['default'];
			} :
			/******/
			function getModuleExports() {
				return module;
			};
		/******/
		__webpack_require__.d(getter, 'a', getter);
		/******/
		return getter;
		/******/
	};
	/******/
	/******/ // Object.prototype.hasOwnProperty.call
	/******/
	__webpack_require__.o = function(object, property) {
		return Object.prototype.hasOwnProperty.call(object, property);
	};
	/******/
	/******/ // __webpack_public_path__
	/******/
	__webpack_require__.p = "";
	/******/
	/******/
	/******/ // Load entry module and return exports
	/******/
	return __webpack_require__(__webpack_require__.s = "./main.js");
	/******/
})
/************************************************************************/
/******/
({

	/***/
	"./App.js":
		/*!****************!*\
		  !*** ./App.js ***!
		  \****************/
		/*! exports provided: num1, num2, add, default */
		/***/
		(function(module, __webpack_exports__, __webpack_require__) {

			"use strict";
			eval(
				"__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"num1\", function() { return num1; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"num2\", function() { return num2; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"add\", function() { return add; });\n// 声明组件\r\nvar App = {\r\n\ttemplate: \r\n\t`\r\n\t\t<div>我是一个入口组件</div>\r\n\t`\r\n};\r\n\r\n// 声明并导出\r\nvar num1 = 4;\r\n// 声明再导出\r\nvar num2 = 1;\r\n\r\n// 抛出函数\r\nfunction add(x, y){\r\n\treturn x + y;\r\n\tconsole.log(x + y);\r\n}\r\n\r\n// 抛出对象\r\n/* harmony default export */ __webpack_exports__[\"default\"] = (App);\n\n//# sourceURL=webpack:///./App.js?"
			);

			/***/
		}),

	/***/
	"./main.js":
		/*!*****************!*\
		  !*** ./main.js ***!
		  \*****************/
		/*! no exports provided */
		/***/
		(function(module, __webpack_exports__, __webpack_require__) {

			"use strict";
			eval(
			);

			/***/
		}),

	/***/
	"./node_modules/_vue@2.6.11@vue/dist/vue.js":
		/*!**************************************************!*\
		  !*** ./node_modules/_vue@2.6.11@vue/dist/vue.js ***!
		  \**************************************************/
		/*! no static exports found */
		/***/
		(function(module, exports, __webpack_require__) {

			eval(
			);

			/***/
		}),

	/***/
	"E:\\node安装\\node_modules\\webpack\\buildin\\global.js":
		/*!***********************************!*\
		  !*** (webpack)/buildin/global.js ***!
		  \***********************************/
		/*! no static exports found */
		/***/
		(function(module, exports) {

			eval(
				
			);

			/***/
		}),

	/***/
	"E:\\node安装\\node_modules\\webpack\\node_modules\\_process@0.11.10@process\\browser.js":
		/*!******************************************************************!*\
		  !*** (webpack)/node_modules/_process@0.11.10@process/browser.js ***!
		  \******************************************************************/
		/*! no static exports found */
		/***/
		(function(module, exports) {

			eval(
			);

			/***/
		}),

	/***/
	"E:\\node安装\\node_modules\\webpack\\node_modules\\_setimmediate@1.0.5@setimmediate\\setImmediate.js":
		/*!*******************************************************************************!*\
		  !*** (webpack)/node_modules/_setimmediate@1.0.5@setimmediate/setImmediate.js ***!
		  \*******************************************************************************/
		/*! no static exports found */
		/***/
		(function(module, exports, __webpack_require__) {

			eval(
			);

			/***/
		}),

	/***/
	"E:\\node安装\\node_modules\\webpack\\node_modules\\_timers-browserify@2.0.11@timers-browserify\\main.js":
		/*!**********************************************************************************!*\
		  !*** (webpack)/node_modules/_timers-browserify@2.0.11@timers-browserify/main.js ***!
		  \**********************************************************************************/
		/*! no static exports found */
		/***/
		(function(module, exports, __webpack_require__) {

			eval(
			);

			/***/
		})

	/******/
});

关于setimmediate介绍链接:

www.ruanyifeng.com/blog/2014/1…

webpack打包模块的源码 执行顺序

  • 1:把所有模块的代码放入到函数中,用一个数组保存起来
  • 2:根据require时传入的数组索引,能知道需要哪一段代码
  • 3:从数组中,根据索引取出包含我们代码的函数
  • 4:执行该函数,传入一个对象 module.exports
  • 5:我们的代码,按照约定,正好是用module.exports = 'xxxx' 进行赋值
  • 6:调用函数结束后,module.exports从原来的空对象,就有值了
  • 7:最终return module.exports; 作为require函数的返回值

webpack.config.js文件配置

  • entry 是一个对象,程序的入口
    • key:随意写
    • value: 入口文件
  • output 是一个对象,产出的资源
    • key: filename
    • value : 生成的build.js
  • module 模块(对象)
    • loaders:[]
      • 存在一些loader `{ test:正则,loader:'style-loader!css-loader' }

图片:pic.images.ac.cn/image/5e86e…

图片:pic.images.ac.cn/image/5e86e…

配置文件webpack.config.js的修改

修改配置文件名为:webpack.dev.config.js和webpack.prod.config.js

在package.json文件中修改

"scripts": {
     "dev": "webpack --config ./webpack.dev.config.js",
     "prod": "webpack --config ./webpack.prod.config.js"

}

css文件处理

es6模块导入

import './main.css'

编译之后报错

图片:pic.images.ac.cn/image/5e86e…

对于webpack来说,css文件也是一个模块,但是像这样的文件,webpack得需要loaders去处理这些文件

下载并配置

npm i css-loader style-loader -D

module:{
		loaders:[
			{	
				// 遇到后缀为.css的文件,webpack先用css-loader加载器去解析这个文件
				// 最后计算完的css,将会使用style-loader生成一个内容为最终解析完的css代码的style标签,放到head标签里。
				// webpack在打包过程中,遇到后缀为css的文件,就会使用style-loader和css-loader去加载这个文件。
				test:/\.css$/,
				loader:'style-loader!css-loader'
			}
            ]
}

图片文件的处理

App.js 导入图片资源

import imgSrc from './myGirl.jpg'

export default{
	template:`
		<div>
			<img :src="imgSrc" alt="" />
		</div>
	`,
	data(){
		return {
			imgSrc:imgSrc
		}
	}
};

对文件的处理,webpack得需要url-loaderfile-loader

下载处理图片的loader模块

npm i url-loader file-loader -D

添加loader的配置

module:{
		loaders:[
			{
				test:/\.css$/,
				loader:'style-loader!css-loader'
			},
			{
				test:/\.(jpg|png|jpeg|gif|svg)$/,
				loader:'url-loader?limit=4000'	// limit如果小于4000就会生成base64编码的图片
			}
		]
	},
webpack最终会将各个模块打包成一个文件,因此我们样式中的url路径是相对入口html页面的,而不是相对于原始css文件所在的路径的。这就会导致图片引入失败。这个问题是用file-loader解决的,file-loader可以解析项目中的url引入(不仅限于css),根据我们的配置,将图片拷贝到相应的路径,再根据我们的配置,修改打包后文件引用路径,使之指向正确的文件。
简易,对于图片的大小小于limit设置的大小,使用base64编码,可以减少一次图片的网络请求;那么对于比较大的图片,使用base64就不适合了,编码会和html混在一起,一方面可读性差,另一方面加大了html页面的大小,反而加大了下载页面的大小,得不偿失了呢,因此设置一个合理的limit是非常有必要的。

html-webpack-plugin插件的使用

下载模块

npm i html-webpack-plugin --save-dev

webpack.config.js文件配置

const path = require('path');
//2.导入模块
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
	// 入口
	entry:{
		// 可以有多个入口,也可以有一个,如果一个,就默认从这一个入口开始分析
		 "main" : './src/main.js'
	},
	output:{
		// 指定产出的目录
		path: path.resolve('./dist'), //相对转绝对
		filename:'build.js'
	},
	// 声明模块
	// 包含各个loader
	module:{
		loaders:[
			{	
				// 遇到后缀为.css的文件,webpack先用css-loader加载器去解析这个文件
				// 最后计算完的css,将会使用style-loader生成一个内容为最终解析完的css代码的style标签,放到head标签里。
				// webpack在打包过程中,遇到后缀为css的文件,就会使用style-loader和css-loader去加载这个文件。
				test:/\.css$/,
				loader:'style-loader!css-loader'
			},
			{
				test:/\.(jpg|png|jpeg|gif|svg)$/,
				loader:'url-loader?limit=100000'
			},
			{
				test:/\.less$/,
				loader:'style-loader!css-loader!less-loader'
			}
		]
	},
	watch:true, //文件监视改动 自动产生build.js
    //添加插件 
	plugins:[
		new HtmlWebpackPlugin({
			//插件的执行运行与元素索引有关
			templat:'./src/index.html', //参照物
		})
	]
}

CommonsChunkPlugin的使用

CommonsChunkPlugin主要是用来提取第三方库和公共模块,避免首屏加载的bundle文件或者按需加载的bundle文件体积过大,从而导致加载时间过长,着实是优化的一把利器.

chunk的分类

  • webpack当中配置的入口文件(entry)是chunk,可以理解为entry chunk
  • 入口文件以及它的依赖文件通过code splite(代码分割) 出来的也是chunk,可以理解为children chunk
  • 通过CommonsChunkPlugin创建出来的文件也是chunk,可以理解为commons chunk

CommonsChunkPlugin可配置的属性

  • name:可以是已经存在的chunk(一般指入口文件)对应的name,那么就会把公共模块代码合并到这个chunk上;否则,会创建名字为name的commons chunk进行合并
  • filename:指定commons chunk的文件名
  • chunks:指定source chunk,即指定从哪些chunk当中去找公共模块,省略该选项的时候,默认就是entry chunks
  • minChunks:既可以是数字,也可以是函数,还可以是Infinity,具体用法和区别下面会说

验证三种情况

  • 不分离出第三方库和自定义公共模块
  • 分离出第三方库、自定义公共模块、webpack运行文件,但它们在同一个文件中
  • 单独分离第三方库、自定义公共模块、webpack运行文件,各自在不同文件

不分离出第三方库和自定义公共模块

不分离出第三方库和自定义公共模板:

图片:p1-jj.byteimg.com/tos-cn-i-t2…

src目录下各个文件内容,尽量保持简单:

--common.js
	export const common = 'common file';
--main1.js
	import {common} from './common.js'
    import Vue from 'vue'
    console.log(Vue,`main1 ${common}`);
--main2.js
	import {common} from './common.js'
    import Vue from 'vue'
    console.log(Vue,`main1 ${common}`)

webpack.config.js

const path  = require('path');
module.exports = {
	// 入口
	entry:{
		// 可以有多个入口,也可以有一个,如果有一个就默认从这一个入口开始分析
		"main1":'./src/main1.js',
		"main2":'./src/main2.js'
	},
	output:{
		path:path.resolve('./dist'),//相对转绝对
		filename:'[name].js'
	},
	watch:true,//文件监视改动 自动产出build.js
}

执行命令npm run build

问题:查看main1.js和main2.js,会发现共同引用的common.js文件和vue都被打包进去了,这肯定不合理,公共模块重复打包,体积过大。

分离出第三方库、自定义公共模块、webpack运行文件

修改webpack.config.js新增一个入口文件vendor和CommonsChunkPlugin插件进行公共模块的提取:

const path = require('path');
const webpack = require('webpack');
const packagejson = require('./package.json');
module.exports = {
    // 入口
    entry: {
        "main1": './src/main1.js',
        "main2": './src/main2.js',
        "vendor": Object.keys(packagejson.dependencies) //获取生产环境依赖的库
    },
    //出口
    output: {
        path: path.resolve('./dist'), //相对转绝对
        filename: '[name].js'
    },
    watch: true, //文件监视改动 自动产出build.js
    plugins: [
        new webpack.optimize.CommonsChunkPlugin({
            name: ['vendor'],
            filename: '[name].js'
        })
    ]
}

查看dist目录,新增了一个vendor.js的文件

打包信息:

通过查看vendor.js文件,发现main1.js和main2.js文件中依赖的vue和common.js都被打包进vendor.js中,同时还有webpack的运行文件。总的来说,我们初步的目的达到,提取公共模块,但是它们都在同一个文件中。

到这里,肯定有人希望自家的vendor.js纯白无瑕,只包含第三方库,不包含自定义的公共模块和webpack运行文件,又或者希望包含第三方库和公共模块,不包含webpack运行文件。

其实,这种想法是对,特别是分离出webpack运行文件,因为每次打包webpack运行文件都会变,如果你不分离出webpack运行文件,每次打包生成vendor.js对应的哈希值都会变化,导致vendor.js改变,但实际上你的第三方库其实是没有变,然而浏览器会认为你原来缓存的vendor.js就失效,要重新去服务器中获取,其实只是webpack运行文件变化而已,就要人家重新加载,好冤啊~

单独分离出第三方库、自定义公共模块、webpack运行文件

这里我们分两步走:

  1. 先单独抽离出webpack运行文件
  2. 接着单独抽离第三方库和自定义公共模块

(1)抽离webpack的运行文件

修改webpack配置文件

 plugins: [
        new webpack.optimize.CommonsChunkPlugin({
            name: ['vendor', 'runtime'],
            filename: '[name].js',
        })

其实上面这段代码,等价于下面这段:

 plugins: [
        new webpack.optimize.CommonsChunkPlugin({
            name: 'vendor',
            filename: '[name].js'
        }),
        new webpack.optimize.CommonsChunkPlugin({
            name: 'runtime',
            filename: '[name].js',
            chunks: ['vendor']
        }),
    ]

上面两段抽离webpack运行文件代码的意思是创建一个名为runtime的commons chunk进行webpack运行文件的抽离,其中source chunks是vendor.js。

查看dist目录下,新增了一个runtime.js的文件,其实就是webpack的运行文件

再来查看一下命令行中webpack的打包信息,你会发现vendor.js的体积已经减小,说明已经把webpack运行文件提取出来了:

可是,vendor.js中还有自定义的公共模块common.js,人家只想vendor.js拥有项目依赖的第三方库而已(这里是jquery),这个时候把minChunks这个属性引进来。

minChunks可以设置为数字、函数和Infinity,默认值是2,并不是官方文档说的入口文件的数量,下面解释下minChunks含义:

  • 数字:模块被多少个chunk公共引用才被抽取出来成为commons chunk
  • 函数:接受 (module, count) 两个参数,返回一个布尔值,你可以在函数内进行你规定好的逻辑来决定某个模块是否提取成为commons chunk
  • Infinity:只有当入口文件(entry chunks) >= 3 才生效,用来在第三方库中分离自定义的公共模块

(2) 抽离第三方库和自定义公共模块

minChunks设为Infinity,修改webpack配置文件如下

plugins: [
        new webpack.optimize.CommonsChunkPlugin({
            name: ['vendor','runtime'],
            filename: '[name].js',
            minChunks: Infinity
        }),
        new webpack.optimize.CommonsChunkPlugin({
            name: 'common',
            filename: '[name].js',
            chunks: ['main1','main2']//从first.js和second.js中抽取commons chunk
        }),
    ]

查看dist目录下,新增了一个common.js的文件: 再来查看一下命令行中webpack的打包信息,自定义的公共模块分离出来

这时候的vendor.js就纯白无瑕,只包含第三方库文件,common.js就是自定义的公共模块,runtime.js就是webpack的运行文件。

webpack异步加载的原理

webpack ensure 有人称它为异步加载,也有人说做代码切割。其实说白了,它就是把js模块给独立导出一个.js文件的,然后使用这个 模块的时候,webpack会构造script dom元素,由浏览器发起异步请求这个js文件。

webpack.ensure的原理:

它就是 把一些js模块给独立出一个个js文件,然后需要用到的时候,在创建一个script对象,加入 到document.head对象中即可,浏览器会自动帮我们发起请求,去请求这个js文件,在写个回调,去 定义得到这个js文件后,需要做什么业务逻辑操作。

main.js依赖三个js

  • A.js是封装aBtn按钮点击后,才执行的业务逻辑
  • B.js是封装bBtn按钮点击后,才执行的业务逻辑
  • vue.js是封装了main.js需要利用的包

针对上面的需求,优化方案

假设 A.js B.js vue.js都是非常大的文件 因为 A.js B.js 都不是main.js必须有的,即未来可能发生的操作,那么我们把 他们利用异步加载,当发生的时候再去加载就行了

vue.js.js是main.js立即马上依赖的工具箱。但是它又非常的大,所以将其配置打包成一个公共模块, 利用浏览器的并发加载,加快下载速度。ok,构思完成,开始实现。

index.html

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title></title>
</head>
<body>
	<div id="app"></div>
	<button id="aBtn">Abtn</button>
	<br>
	<button id="bBtn">Bbtn</button>
	
</body>
</html>

定义了两个button

然后看看 main.js

import Vue from 'vue'
console.log(Vue);
document.getElementById('aBtn').onclick = function(){
	require.ensure(['./B.js'],function(){
		var A = require('./A.js');
		alert(A.data);
		//异步里面再导入同步模块--实际是使用同步中的模块
		var Vue1 = require('vue');
	})
}
document.getElementById('bBtn').onclick = function(){
	require.ensure([],function(){

		var B = require('./B.js');
		alert(B.data);

	})
}

可以看到,A.js, B.js 都是点击后才ensure进来的。什么时候加载完成呢? 就是 require.ensure() 第二个函数参数,即回调函数,它表示当下载js完成后,发生的。

webpack-dev-server

下载模块

1.npm install webpack-dev-server --save-dev

常用配置参数

--open 自动打开浏览器

--hot 热更新 ,不在刷新的情况下替换 css样式

--inline 自动刷新

--port 9999 指定端口

--process 显示编译进度

在package.json文件中配置

图片链接:pic.images.ac.cn/image/5e86e…

直接执行 npm run dev

es6的解析

模块介绍

babel-core:

javascript babel-core的作用是把js代码分析成ast(抽象语法树),方便各个插件分析语法进行相应的处理。有些新语法在低版本 js 中是不存在的,如箭头函数,rest 参数,函数默认值等,这种语言层面的不兼容只能通过将代码转为 ast,分析其语法后再转为低版本 js

abel转译器本身,提供了babel的转译API,如babel.transform等,用于对代码进行转译。像webpack的babel-loader就是调用这些API来完成转译过程的。

babel-loader:

在将es6的代码transform进行转义,就是通过babel-loader来完成

babel-preset-env

如果要自行配置转译过程中使用的各类插件,那太痛苦了,所以babel官方帮我们做了一些预设的插件集,称之为preset,这样我们只需要使用对应的preset就可以了。以JS标准为例,babel提供了如下的一些preset:

  • es2015
  • es2016
  • es2017
  • env es20xx的preset只转译该年份批准的标准,而env则代指最新的标准,包括了latest和es20xx各年份 另外,还有 stage-0到stage-4的标准成形之前的各个阶段,这些都是实验版的preset,建议不要使用。

babel-plugin-transform-runtime

Babel 默认只转换新的 JavaScript 语法,而不转换新的 API。例如,Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise 等全局对象,以及一些定义在全局对象上的方法(比如 Object.assign)都不会转译。如果想使用这些新的对象和方法,必须使用 babel-polyfill,为当前环境提供一个垫片。

参考链接:segmentfault.com/q/101000000… 看这个回答,说的非常详细。

安装模块

npm install babel-core babel-loader babel-preset-env babel-plugin-transform-runtime -D

在webpack-dev-config.js配置

loader:[
    {
		// 处理es6,7,8
		test:/\.js$/,
		loader:'babel-loader',
		options:{
			presets:['env'],//处理关键字
			plugins:['transform-runtime'],//处理函数
		}
    }
]

配置完成之后执行 npm run dev

发现!!!!!

图片链接:pic.images.ac.cn/image/5e86e…

解决:

排除不包含node_modules的路径,然后再配置文件中修改

loader:[
    {
		// 处理es6,7,8
		test:/\.js$/,
		loader:'babel-loader',
        exclude:/node_modules/,	//排除不包含node_modules的路径
		options:{
			presets:['env'],//处理关键字
			plugins:['transform-runtime'],//处理函数
		}
    }
]

也会发现,当排除掉node_modules文件中的es6代码编译后,编译的时间也快了 以前出错的,3601毫秒的时候就开始出错了。。。。

图片链接:pic.images.ac.cn/image/5e86e…

排除掉node_modules之后

图片链接:pic.images.ac.cn/image/5e86e…

单文件引入

下载包

注:-D代表下载的文件存在dev里面(-D开发依赖)(-S项目依赖)

npm install vue-loader@14.1.1 vue-template-compiler@2.5.17 -D

创建App.vue文件

//组件的模板结构
<template>
	<div>
		{{ text }}
	</div>
</template>

//组件的业务逻辑
<script>
export default {
	data(){
		return {
			text:'hello Single file'				
		}
	}
}
</script>
//组件的样式
<style>
	body{
		background-color: green;
	}
</style>

创建入口文件main.js

import Vue from 'vue';
import App from './App'
new Vue({
	el:'#app',
	//Render函数是Vue2.x版本新增的一个函数;
	// 使用虚拟dom来渲染节点提升性能,因为它是基于JavaScript计算。
	// 通过使用createElement(h)来创建dom节点。createElement是render的核心方法。其Vue编译的时候会把template里面的节点解析成虚拟dom;
	render:c=>c(App)
	// components:{
	//  	App
	// },
	// template:`<App />`
});

webpack.dev.config.js文件配置

// 处理Vue文件
{
	test:/\.vue$/,
	loader:'vue-loader'
}

vue-cli脚手架

v-for中key的作用

1.当数据发生变化时,vue是怎样更新节点的?

要知道渲染真实DOM的开销是很大的,比如有时候我们修改了某个数据,如果直接渲染到真实dom上会引起整个dom树的重绘和重排,有没有可能我们只更新我们修改的那一小块dom而不要更新整个dom呢?diff算法能够帮助我们。

我们先根据真实DOM生成一颗virtual DOM,当virtual DOM某个节点的数据改变后会生成一个新的Vnode,然后VnodeoldVnode作对比,发现有不一样的地方就直接修改在真实的DOM上,然后使oldVnode的值为Vnode

diff的过程就是调用名为patch的函数,比较新旧节点,一边比较一边给真实的DOM打补丁。

2.virtual DOM和真实DOM的区别

virtual DOM是将真实的DOM的数据抽取出来,以对象的形式模拟树形结构。比如dom是这样的:

<div>
    <p>123</p>
</div>

对应的virtual DOM(伪代码):

var Vnode = {
    tag: 'div',
    children: [
        { tag: 'p', text: '123' }
    ]
};

3.diff的比较方式

在采取diff算法比较新旧节点的时候,比较只会在同层级进行, 不会跨层级比较。

<div>
    <p>123</p>
</div>

<div>
    <span>456</span>
</div>

大家要知道,不仅只是vue中,react中在执行列表渲染时也会要求给每个组件添加key这个属性

如果想知道key的作用,不得我们得聊一下虚拟DOM的Diff算法

所谓虚拟DOM的诞生,使我们可以不直接操作DOM元素,只操作数据便可以重新渲染页面。而隐藏在背后的原理便是其高效的Diff算法,它的核心是基于两个简单的假设:

  1. 两个相同的组件产生类似的DOM结构,不同的组件产生不同的DOM结构
  2. 同一个层级的一组节点,他们可以通过唯一的id进行区分

下面这张图是Diff示意图:

图片链接:pic.images.ac.cn/image/5e86e…

由此图我们可以看出:

当页面的数据发生变化时,Diff算法只会比较同一层级的节点:

如果节点类型不同,直接干掉前面的节点,再创建并插入新的节点,不会再比较这个节点以后的子节点了
如果节点类型相同,则会重新设置该节点的属性,从而实现节点的更新

当某一层有很多相同的界定时,也就是列表节点,Diff算法的更新过程默认情况下也是遵循以上原则

比如下面这个情况

图片链接:pic.images.ac.cn/image/5e86e…

我们希望可以在B和C之间加一个F,Diff算法默认 执行起来是这样的:

图片链接:pic.images.ac.cn/image/5e86e…

既把C更新成F,D更新成C,E更新成D,最后再插入E,是不是很没有效率?

所有我们***需要使用key来给每个节点做一个唯一的标识,Diff算法就可以正确的识别此节点,找到正确的位置区插入新的节点***

图片链接:pic.images.ac.cn/image/5e86e…

所以一句话,key的作用主要是为了高效的更新虚拟DOM。另外vue的在使用相同标签名元素的过渡切换时,也会使用到key属性,其目的也是为了让vue可以区分他们,否则vue只会替换其内部属性而不会触发过渡效果。

深度剖析:如何实现一个Virtual DOM算法

所谓的 Virtual DOM 算法。包括几个步骤:

  1. 用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中
  2. 当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异
  3. 把2所记录的差异应用到步骤1所构建的真正的DOM树上,视图就更新了

参考链接:github.com/livoras/blo…

总结:当给组件使用v-for遍历的时候,一定要加上:key属性,避免让vue去计算DOM

emit和on进行组件之间的传值(bus)

注意:emit和on的事件必须在一个公共的实例上,才能够触发

需求:

1.有A,B,C三个组件,同时挂载到入口组件中

2.将A组件中的数据传递到C组件,再将B组件中的数据传递到C组件
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>Vue2-单一事件管理组件通信</title>

  
</head>
<body>
  <div id="app">
    <dom-a></dom-a>
    <dom-b></dom-b>
    <dom-c></dom-c>   
  </div>
  <script src="vue.js"></script>
  <script>
    

  //准备一个空的实例对象
  var Event = new Vue();
  console.log(Event);
 
  //组件A
  var A = {
    template: `
      <div>
        <span>我是A组件的数据->{{a}}</span>
        <input type="button" value="把A数据传给C" @click = "send">
      </div>
    `,
    methods: {
      send () {
        alert(1);
        console.log(this);
        Event.$emit("a-msg", this.a);
      }
    },
    data () {
      return {
        a: "我是a组件中数据"
      }
    }
  };
  //组件B
  var B = {
    template: `
      <div>
        <span>我是B组件的数据->{{a}}</span>
        <input type="button" value="把B数据传给C" @click = "send">
      </div>
    `,
    methods: {
      send () {
        Event.$emit("b-msg", this.a);
      }
    },
    data () {
      return {
        a: "我是b组件中数据"
      }
    }
  };
  //组件C
  var C = {
    template: `
      <div>
        <h3>我是C组件</h3>
        <span>接收过来A的数据为: {{a}}</span>
        <br>
        <span>接收过来B的数据为: {{b}}</span>
      </div>
    `,
    mounted () {
      alert(2);
      //接收A组件的数据
      Event.$on("a-msg", (a)=> {
        this.a = a;
      });
 
      //接收B组件的数据
      Event.$on("b-msg",  (b)=> {
        this.b = b;
      });
    },
    data () {
      return {
        a: "",
        b: ""
      }
    }
  };

    new Vue({
      el: "#app",
      components: {
         'dom-a':A,
         'dom-b':B,
         'dom-c':C
      }
    });
  </script>
 
</body>
</html>

vue-router的导航守卫之在导航完成后获取数据

需求:在导航完成之后加载数据。渲染DOM

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title></title>
</head>
<body>
	<div id="app"></div>
	<script type="text/javascript" src="vue.js"></script>
	<script type="text/javascript" src="vue-router.js"></script>
	<script type="text/javascript" src="axios.js"></script>
	<script type="text/javascript">

		// 导航完成后获取数据,这让我们有机会在数据获取期间展示一个 loading 状态,还可以在不同视图间展示不同的 loading 状态。
		var Index = {
			template:`
				<div>我是首页</div>
			`
		};

		var Post = {
			data(){
				return {
					loading:false,
					error:null,
					post:null
				}
			},
			template:`
				<div>
					<div class = 'loading' v-if = 'loading'>
						loading.....
					</div>
					<div v-if="error" class = 'error'>
						{{error}}
					</div>
					<div class = 'content' v-if = 'post'>
						<h2>{{post.title}}</h2>
						<p>{{post.body}}</p>
					</div>
				</div>
			`,
			created(){
				// 组件创建完成后获取数据
				// 此时data已经被监听了
				this.fetchData();

			},
			watch:{
				'$route':'fetchData'
			},
			methods:{
				fetchData(){
					this.error = null;
					this.post = null;
					this.loading = true;
					this.$axios.get('http://127.0.0.1:8888/post')
					.then(res=>{
						this.loading = false;
						console.log(res.data);
						this.post = res.data;
					})
					.catch(err=>{
						this.err = err.toString();
					})

				}
			}
		}

		var router = new VueRouter({
			routes:[
				{
					path:'/index',
					name:'index',
					component:Index
				},
				{
					path:'/post',
					name:'post',
					component:Post
				}
			]
		});

		var App = {
			template:`
				<div>
					<router-link :to = "{name:'index'}">首页</router-link>
					<router-link :to = "{name:'post'}">我的博客</router-link>

						<router-view></router-view>


				</div>
			`
		};
		Vue.prototype.$axios  = axios;
		new Vue({
			el:"#app",
			data:{

			},

			components:{
				App
			},
			template:`<App />`,
			router
		});
	</script>
</body>
</html>


vue-router的导航守卫之导航完成之前获取数据

需求:在导航完成之前获取数据,之后再渲染DOM

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title></title>
</head>

<body>
    <div id="app"></div>
    <script type="text/javascript" src="vue.js"></script>
    <script type="text/javascript" src="vue-router.js"></script>
    <script type="text/javascript" src="axios.js"></script>
    <script type="text/javascript">
    // 导航完成后获取数据,这让我们有机会在数据获取期间展示一个 loading 状态,还可以在不同视图间展示不同的 loading 状态。

    var vm = null;
    var User = {
        data() {
            return {
                error: null,
                user: ''
            }
        },
        template: `
				<div>
					<div v-if="error" class = 'error'>
						{{error}}
					</div>
					<div class = 'user' v-if = 'user'>
						<h2>{{user}}</h2>
					</div>
				</div>
			`,
        beforeRouteEnter(to, from, next) {
            // 在渲染该组件的对应路由被 confirm 前调用
            // 不!能!获取组件实例 `this`
            // 因为当守卫执行前,组件实例还没被创建


            console.log(to);
            axios.get(`http://127.0.0.1:8888/user/${to.params.id}`)
                .then(res => {

                    next(vm => vm.setData(res.data))

                })
                .catch(err => {
                    console.log(err);
                    next(vm => vm.setError(err));
                })
        },
        beforeRouteUpdate(to, from, next) {
        	 // 在当前路由改变,但是该组件被复用时调用
	    // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
	    // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
	    // 可以访问组件实例 `this`

            this.user = null;
            this.$axios.get(`http://127.0.0.1:8888/user/${to.params.id}`)
                .then(res => {
                    this.setData(res.data);
                    next();
                })
                .catch(err => {
                 	   this.setError(err);
                    next();
                })



        },
        methods: {
            setData(user) {
                this.$nextTick(() => {
                    this.user = user;
                })
            },
            setError(err) {
                this.err = err.toString();

            }

        }

    }

    var router = new VueRouter({
        routes: [{
            path: '/user/:id',
            name: 'user',
            component: User,

        }]
    });

    var App = {
        template: `
				<div>
					
					<router-link :to = "{name:'user',params:{id:1}}">我的用户1</router-link>
					<router-link :to = "{name:'user',params:{id:2}}">我的用户2</router-link>

						<router-view></router-view>


				</div>
			`
    };
    Vue.prototype.$axios = axios;
    vm = new Vue({
        el: "#app",
        data: {

        },

        components: {
            App
        },
        template: `<App />`,
        router
    });
    </script>
</body>

</html>

vue-cli2.x脚手架的使用

参考链接:github.com/vuejs/vue-c…

安装:

npm install -g vue-cli

用法:

$ vue init < template-name >  < project-name >

例:

$ vue init webpack my-project

目前可用的模块包括:

  • webpack - 一个功能齐全的Webpack + vue-loader设置,具有热重载,linting,测试和css提取功能。
  • webpack-simple - 一个简单的Webpack + vue-loader设置,用于快速原型设计。
  • browserify -全功能Browserify + vueify设置用热重装载,linting&单元测试。
  • browserify -simple - 一个简单的Browserify + vueify设置,用于快速原型设计。
  • pwa - 基于webpack模板的vue-cli的PWA模板
  • simple - 单个HTML文件中最简单的Vue设置

相关文件和文件夹的含义:

图片链接:pic.images.ac.cn/image/5e86e…

vue-cli3x脚手架的使用

vue-cli3x的官方文档:cli.vuejs.org/

Vue-cli3 中vue.config.js文件配置参考文档:cli.vuejs.org/zh/config/#…

// vue.config.js 配置说明
//官方vue.config.js 参考文档 https://cli.vuejs.org/zh/config/#css-loaderoptions
// 这里只列一部分,具体配置参考文档
module.exports = {
  // 部署生产环境和开发环境下的URL。
  // 默认情况下,Vue CLI 会假设你的应用是被部署在一个域名的根路径上
  //例如 https://www.my-app.com/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.my-app.com/my-app/,则设置 baseUrl 为 /my-app/。
  baseUrl: process.env.NODE_ENV === "production" ? "./" : "/",
 
  // outputDir: 在npm run build 或 yarn build 时 ,生成文件的目录名称(要和baseUrl的生产环境路径一致)
  outputDir: "dist",
  //用于放置生成的静态资源 (js、css、img、fonts) 的;(项目打包之后,静态资源会放在这个文件夹下)
  assetsDir: "assets",
  //指定生成的 index.html 的输出路径  (打包之后,改变系统默认的index.html的文件名)
  // indexPath: "myIndex.html",
  //默认情况下,生成的静态资源在它们的文件名中包含了 hash 以便更好的控制缓存。你可以通过将这个选项设为 false 来关闭文件名哈希。(false的时候就是让原来的文件名不改变)
  filenameHashing: false,
 
  //   lintOnSave:{ type:Boolean default:true } 问你是否使用eslint
  lintOnSave: true,
  //如果你想要在生产构建时禁用 eslint-loader,你可以用如下配置
  // lintOnSave: process.env.NODE_ENV !== 'production',
 
  //是否使用包含运行时编译器的 Vue 构建版本。设置为 true 后你就可以在 Vue 组件中使用 template 选项了,但是这会让你的应用额外增加 10kb 左右。(默认false)
  // runtimeCompiler: false,
 
  /**
   * 如果你不需要生产环境的 source map,可以将其设置为 false 以加速生产环境构建。
   *  打包之后发现map文件过大,项目文件体积很大,设置为false就可以不输出map文件
   *  map文件的作用在于:项目打包后,代码都是经过压缩加密的,如果运行时报错,输出的错误信息无法准确得知是哪里的代码报错。
   *  有了map就可以像未加密的代码一样,准确的输出是哪一行哪一列有错。
   * */
  productionSourceMap: false,
 
  // 它支持webPack-dev-server的所有选项
  devServer: {
    host: "localhost",
    port: 1111, // 端口号
    https: false, // https:{type:Boolean}
    open: true, //配置自动启动浏览器
    // proxy: 'http://localhost:4000' // 配置跨域处理,只有一个代理
 
    // 配置多个代理
    proxy: {
      "/api": {
        target: "<url>",
        ws: true,
        changeOrigin: true
      },
      "/foo": {
        target: "<other_url>"
      }
    }
  }
};

图片链接:pic.images.ac.cn/image/5e86e…

参考网址:mint-ui.github.io/#!/zh-cn