前端MVC Vue2学习总结(七)——ES6与Module模块化、Vue-cli脚手架搭建、开发、发布项目与综合示例

使用vue-cli可以规范项目,提高开发效率,但是使用vue-cli时需要一些ECMAScript6的知识,特别是ES6中的模块管理内容,本章先介绍ES6中的基础与模块化的内容再使用vue-cli开发vue项目。

一、ECMAScript6概要

ECMAScript是一种由Ecma国际(前身为欧洲计算机制造商协会,英文名称是European Computer Manufacturers Association)通过ECMA-262标准化的脚本程序设计语言。这种语言在万维网上应用广泛,它往往被称为JavaScript或JScript,但实际上后两者是ECMA-262标准的实现和扩展。

2015年6月17日,ECMAScript 6发布正式版本,即ECMAScript 2015。

ES6是继ES5之后的一次重大改进,语言规范由ES5.1时代的245页扩充至600页。ES6增添了许多必要的特性,例如:模块和类,以及一些实用特性,例如Maps、Sets、Promises、生成器(Generators)等。尽管ES6做了大量的更新,但是它依旧完全向后兼容以前的版本,标准化委员会决定避免由不兼容版本语言导致的“web体验破碎”。结果是,所有老代码都可以正常运行,整个过渡也显得更为平滑,但随之而来的问题是,开发者们抱怨了多年的老问题依然存在。

简单说ES就是JavaScript语言的实现标准,ES6是一较新的版本,使JavaScript更加规范与强大。

1.0.学习资源

我推荐的ES6学习资料是开源书籍《ECMAScript 6 入门》,全面介绍 ECMAScript 6 新引入的语法特性。

URL:http://es6.ruanyifeng.com/

源码:https://github.com/ruanyf/es6tutorial/

1.1. let、const 和块级作用域

let 允许创建块级作用域,ES6 推荐在函数中使用 let 定义变量,而非 var:

var a = 2;
{
  let a = 3;
  console.log(a); // 3
}
console.log(a); // 2

同样在块级作用域有效的另一个变量声明方式是 const,它可以声明一个常量。ES6 中,const 声明的常量类似于指针,它指向某个引用,也就是说这个「常量」并非一成不变的,如:

{
  const ARR = [5,6];
  ARR.push(7);
  console.log(ARR); // [5,6,7]
  ARR = 10; // TypeError
}

有几个点需要注意:

  • let 关键词声明的变量不具备变量提升(hoisting)特性
  • let 和 const 声明只在最靠近的一个块中(花括号内)有效
  • 当使用常量 const 声明时,请使用大写变量,如:CAPITAL_CASING
  • const 在声明时必须被赋值

默认情况下javascript中并没有块级(block)作用域:

            var i = 100;
            if(true) {
                var i=200;
                console.log(i);
            }
            console.log(i);

结果:

使用let定义:

            let i = 100;
            if(true) {
                //var i=200;  //Identifier 'i' has already been declared  变量定义
                let i=200;
                console.log(i);
            }
            console.log(i);

结果:

 默认情况javascript中定义的变量都是弱类型的,可以动态变化:

            let i = 100;
            if(true) {
                i=true;
                console.log(i);
            }
            console.log(i);

结果:

如果使用const定义,则必须赋值且不允许修改。

1.2. 箭头函数(Arrow Functions)

ES6 中,箭头函数就是函数的一种简写形式,使用括号包裹参数,跟随一个 =>,紧接着是函数体:

var getPrice = function() {
  return 4.55;
};

// Implementation with Arrow Function
var getPrice = () => 4.55;

需要注意的是,上面例子中的 getPrice 箭头函数采用了简洁函数体,它不需要 reture 语句,下面这个例子使用的是正常函数体:

let arr = ['apple', 'banana', 'orange'];

let breakfast = arr.map(fruit => {
  return fruit + 's';
});
console.log(breakfast); // apples bananas oranges

当然,箭头函数不仅仅是让代码变得简洁,函数中 this 总是绑定总是指向对象自身。具体可以看看下面几个例子:

function Person() {
  this.age = 0;

  setInterval(function growUp() {
    // 在非严格模式下,growUp() 函数的 this 指向 window 对象
    this.age++;
  }, 1000);
}
var person = new Person();

我们经常需要使用一个变量来保存 this,然后在 growUp 函数中引用:

function Person() {
  var self = this;
  self.age = 0;

  setInterval(function growUp() {
    self.age++;
  }, 1000);
}

而使用箭头函数可以省却这个麻烦:

function Person(){
  this.age = 0;

  setInterval(() => {
    // |this| 指向 person 对象
    this.age++;
  }, 1000);
}

var person = new Person();

示例:

<!DOCTYPE html>
<html>

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

    <body>
        <script type="text/javascript">
            var add1 = function(a, b) {
                return a + b;
            }
            var add2 = (a, b) => a + b;

            var f1 = function(n) {
                return n + 1;
            }
            var f2 = n => n + 1;

            function counter1() { //普通情况
                this.n = 100;
                var self = this;
                (function(i) {
                    self.n += i;
                    console.log(self.n);
                })(50);
            }
            var c1 = new counter1();

            function counter2() {
                this.n = 100;
                setTimeout(function() { //错误,this指向window
                    console.log(this);
                    this.n += 50;
                    console.log(this.n);
                }, 1000);
            }
            var c2 = new counter2();

            function counter3() {
                this.n = 100;
                setTimeout(() => { //箭头函数,this指向当前对象
                    console.log(this);
                    this.n += 50;
                    console.log(this.n);
                }, 1000);
            }
            var c3 = new counter3();
        </script>
    </body>

</html>

结果:

1.3. 函数参数默认值

ES6 中允许你对函数参数设置默认值:

let getFinalPrice = (price, tax=0.7) => price + price * tax;
getFinalPrice(500); // 850

示例:

            function add(n1=100,n2=200){
                return n1+n2;
            }
            
            console.log(add());
            console.log(add(1));
            console.log(add(1,1));

结果:

1.4. Spread / Rest 操作符

Spread / Rest 操作符指的是 ...,具体是 Spread 还是 Rest 需要看上下文语境。

当被用于迭代器中时,它是一个 Spread 操作符:

function foo(x,y,z) {
  console.log(x,y,z);
}

let arr = [1,2,3];
foo(...arr); // 1 2 3

示例:

        <script type="text/javascript">
            function show(n1,n2,n3){
                console.log(n1,n2,n3);
            }
            
            var arr=[5,7,9,1,6,7];
            show(1,2,3);
            show(arr);
            
            show(...arr);  //将数组中的前3位取出作为3个参数的值
        </script>

结果:

当被用于函数传参时,是一个 Rest 操作符:

function foo(...args) {
  console.log(args);
}
foo( 1, 2, 3, 4, 5); // [1, 2, 3, 4, 5]

示例:

        <script type="text/javascript">
            function show(...args){  //可变参数,args是一个数组
                console.log(args);
            }
            
            show(1,2,3,true,false,'Hello'); 
        </script>

结果:

1.5. 对象词法扩展

ES6 允许声明在对象字面量时使用简写语法,来初始化属性变量和函数的定义方法,并且允许在对象属性中进行计算操作:

function getCar(make, model, value) {
  return {
    // 简写变量
    make,  // 等同于 make: make
    model, // 等同于 model: model
    value, // 等同于 value: value

    // 属性可以使用表达式计算值
    ['make' + make]: true,

    // 忽略 `function` 关键词简写对象函数
    depreciate() {
      this.value -= 2500;
    }
  };
}

let car = getCar('Barret', 'Lee', 40000);

// output: {
//     make: 'Barret',
//     model:'Lee',
//     value: 40000,
//     makeBarret: true,
//     depreciate: function()
// }

示例:

<script type="text/javascript">
            function createProduct(name,price){
                return {
                    "name":name,
                    "price":price,
                    "iphone8Desc":name+"_"+price,
                    "show":function(){
                        console.log("名称:"+this.name+" 价格:"+this.price);
                    }
                }
            }
            
            var phone1=createProduct("iphone8",5898.5);
            phone1.show();
            console.log(JSON.stringify(phone1));
            console.log(phone1);
            
            
            //ES6中定义对象的语法糖
            function createPdt(name,price){
                return {
                    name,
                    price,
                    [name+"Desc"]:name+"_"+price,
                    show(){
                        console.log("名称:"+this.name+" 价格:"+this.price);
                    }
                }
            }
            var phone2=createPdt("iphone8",5898.5);
            phone2.show();
            console.log(JSON.stringify(phone2));
            console.log(phone2);
        </script>

结果:

1.6. 二进制和八进制字面量

ES6 支持二进制和八进制的字面量,通过在数字前面添加 0o 或者 0O 即可将其转换为二进制值:

let oValue = 0o10;
console.log(oValue); // 8

let bValue = 0b10; // 二进制使用 `0b` 或者 `0B`
console.log(bValue); // 2

1.7. 对象和数组解构

解构可以避免在对象赋值时产生中间变量:

function foo() {
  return [1,2,3];
}
let arr = foo(); // [1,2,3]

let [a, b, c] = foo();
console.log(a, b, c); // 1 2 3

function bar() {
  return {
    x: 4,
    y: 5,
    z: 6
  };
}
let {x: x, y: y, z: z} = bar();
console.log(x, y, z); // 4 5 6

示例:

            function getArray(){
                return [1,3,5];
            }
            let [n1,n2,n3,n4]=getArray();   //调用函数,将数组值分别给n1,n2,n3
            console.log(n1,n2,n3,n4);
            
            function getObj(){
                return {name:"tom",age:18};
            }
            let {name:nickname,age:myage}=getObj();  //将对象的name与age给定义的变量赋值
            console.log(nickname,myage);

结果:

1.8. 对象超类

ES6 允许在对象中使用 super 方法:

var parent = {
  foo() {
    console.log("Hello from the Parent");
  }
}

var child = {
  foo() {
    super.foo();
    console.log("Hello from the Child");
  }
}

Object.setPrototypeOf(child, parent);
child.foo(); // Hello from the Parent
             // Hello from the Child

示例:

            var car={
                voice(){
                    console.log("车在叫...");
                }
            };
            
            var bus={
                voice(){
                    super.voice();
                    console.log("大巴车在叫...");
                }
            }
            Object.setPrototypeOf(bus,car);  //将bus的原型指向car
            bus.voice();

结果:

1.8.1、Object.setPrototypeOf 方法的使用

将一个指定的对象的原型设置为另一个对象或者null(既对象的[[Prototype]]内部属性).

示例:

        <script type="text/javascript">
            var Car=function(){
                this.name="车";
            }
            
            var benz=new Car();
            console.log(benz.name);
            
            Object.setPrototypeOf(benz,{name:"小轿车",price:"23456"});
            console.log(benz.name);  //未重写
            console.log(benz.price);
        </script>

结果:

语法

Object.setPrototypeOf(obj, prototype)

参数

obj将被设置原型的对象.prototype该对象新的原型(可以是一个对象或者null).

1.8.2、Object.defineProperty

Object.defineProperty(被扩展的对象名,属性名,{属性的值}) 可以用于扩展对象的属性

不过可以指定只读属性

示例:

<!DOCTYPE html>
<html>

    <head>
        <meta charset="UTF-8">
        <title>Object.defineProperty()</title>
    </head>

    <body>
        <script>
            var tom = {
                name: "tom"
            };
            tom.age = 90;
            tom["age"] = 88;
            console.log("tom.age=" + tom.age);

            var rose = {
                name: "rose"
            };
            Object.defineProperty(rose, "age", {
                value: 98,
                writable:false  //只读
            });
            rose.age=18;  //修改无效
            console.log("rose.age=" + rose.age);
        </script>
    </body>

</html>

结果:

1.9. 模板语法和分隔符

ES6 中有一种十分简洁的方法组装一堆字符串和变量。

${ ... } 用来渲染一个变量 ` 作为分隔符

let user = 'Barret';
console.log(`Hi ${user}!`); // Hi Barret!

1.10. for...of VS for...in

for...of 用于遍历一个迭代器,如数组:

let nicknames = ['di', 'boo', 'punkeye'];
nicknames.size = 3;
for (let nickname of nicknames) {
  console.log(nickname);
}
Result: di, boo, punkeye
for...in 用来遍历对象中的属性:

let nicknames = ['di', 'boo', 'punkeye'];
nicknames.size = 3;
for (let nickname in nicknames) {
  console.log(nickname);
}
Result: 0, 1, 2, size

1.11. Map 和 WeakMap

ES6 中两种新的数据结构集:Map 和 WeakMap。事实上每个对象都可以看作是一个 Map。

一个对象由多个 key-val 对构成,在 Map 中,任何类型都可以作为对象的 key,如:

var myMap = new Map();

var keyString = "a string",
    keyObj = {},
    keyFunc = function () {};

// 设置值
myMap.set(keyString, "value 与 'a string' 关联");
myMap.set(keyObj, "value 与 keyObj 关联");
myMap.set(keyFunc, "value 与 keyFunc 关联");

myMap.size; // 3

// 获取值
myMap.get(keyString);    // "value 与 'a string' 关联"
myMap.get(keyObj);       // "value 与 keyObj 关联"
myMap.get(keyFunc);      // "value 与 keyFunc 关联"
WeakMap

WeakMap 就是一个 Map,只不过它的所有 key 都是弱引用,意思就是 WeakMap 中的东西垃圾回收时不考虑,使用它不用担心内存泄漏问题。

另一个需要注意的点是,WeakMap 的所有 key 必须是对象。它只有四个方法 delete(key),has(key),get(key) 和 set(key, val):

let w = new WeakMap();
w.set('a', 'b'); 
// Uncaught TypeError: Invalid value used as weak map key

var o1 = {},
    o2 = function(){},
    o3 = window;

w.set(o1, 37);
w.set(o2, "azerty");
w.set(o3, undefined);

w.get(o3); // undefined, because that is the set value

w.has(o1); // true
w.delete(o1);
w.has(o1); // false

1.12. Set 和 WeakSet

Set 对象是一组不重复的值,重复的值将被忽略,值类型可以是原始类型和引用类型:

let mySet = new Set([1, 1, 2, 2, 3, 3]);
mySet.size; // 3
mySet.has(1); // true
mySet.add('strings');
mySet.add({ a: 1, b:2 });
可以通过 forEach 和 for...of 来遍历 Set 对象:

mySet.forEach((item) => {
  console.log(item);
    // 1
    // 2
    // 3
    // 'strings'
    // Object { a: 1, b: 2 }
});

for (let value of mySet) {
  console.log(value);
    // 1
    // 2
    // 3
    // 'strings'
    // Object { a: 1, b: 2 }
}

Set 同样有 delete() 和 clear() 方法。

WeakSet

类似于 WeakMap,WeakSet 对象可以让你在一个集合中保存对象的弱引用,在 WeakSet 中的对象只允许出现一次:

var ws = new WeakSet();
var obj = {};
var foo = {};

ws.add(window);
ws.add(obj);

ws.has(window); // true
ws.has(foo);    // false, foo 没有添加成功

ws.delete(window); // 从结合中删除 window 对象
ws.has(window);    // false, window 对象已经被删除

1.13. 类

ES6 中有 class 语法。值得注意是,这里的 class 不是新的对象继承模型,它只是原型链的语法糖表现形式。

函数中使用 static 关键词定义构造函数的的方法和属性:

class Task {
  constructor() {
    console.log("task instantiated!");
  }

  showId() {
    console.log(23);
  }

  static loadAll() {
    console.log("Loading all tasks..");
  }
}

console.log(typeof Task); // function
let task = new Task(); // "task instantiated!"
task.showId(); // 23
Task.loadAll(); // "Loading all tasks.."

示例:

            class Animal{
                constructor(){  //构造方法,可无
                    console.log("正在构造一只动物");
                }
                
                bark(){
                    console.log("动物在叫...");
                }
                
                static eat(){
                    console.log("动物在吃东西...");
                }
            }
            
            var pig=new Animal();
            pig.bark();
            Animal.eat();

结果:

类中的继承和超集:

class Car {
  constructor() {
    console.log("Creating a new car");
  }
}

class Porsche extends Car {
  constructor() {
    super();
    console.log("Creating Porsche");
  }
}

let c = new Porsche();
// Creating a new car
// Creating Porsche

extends 允许一个子类继承父类,需要注意的是,子类的 constructor 函数中需要执行 super() 函数。

当然,你也可以在子类方法中调用父类的方法,如 super.parentMethodName()。

在 这里 阅读更多关于类的介绍。

有几点值得注意的是:

类的声明不会提升(hoisting),如果你要使用某个 Class,那你必须在使用之前定义它,否则会抛出一个 ReferenceError 的错误 在类中定义函数不需要使用 function 关键词

示例:

        <script type="text/javascript">
            class Animal{
                constructor(){  //构造方法,可无
                    console.log("正在构造一只动物");
                }
                
                bark(){
                    console.log("动物在叫...");
                }
                
                static eat(){
                    console.log("动物在吃东西...");
                }
            }
            
            class Dog extends Animal{
                constructor(){ 
                    super();   //调用父类的构造方法
                    console.log("正在构造一只狗");
                }
                bark(){   //重写
                    console.log("汪汪汪...");
                }
            }
            
            var dog=new Dog();
            dog.bark();
            Dog.eat();

结果:

1.14. Symbol

Symbol 是一种新的数据类型,它的值是唯一的,不可变的。ES6 中提出 symbol 的目的是为了生成一个唯一的标识符,不过你访问不到这个标识符:

var sym = Symbol( "some optional description" );
console.log(typeof sym); // symbol

注意,这里 Symbol 前面不能使用 new 操作符。

如果它被用作一个对象的属性,那么这个属性会是不可枚举的:

var o = {
    val: 10,
    [ Symbol("random") ]: "I'm a symbol",
};

console.log(Object.getOwnPropertyNames(o)); // val

如果要获取对象 symbol 属性,需要使用 Object.getOwnPropertySymbols(o)。

1.15. 迭代器(Iterators)

迭代器允许每次访问数据集合的一个元素,当指针指向数据集合最后一个元素是,迭代器便会退出。它提供了 next() 函数来遍历一个序列,这个方法返回一个包含 done 和 value 属性的对象。

ES6 中可以通过 Symbol.iterator 给对象设置默认的遍历器,无论什么时候对象需要被遍历,执行它的 @@iterator 方法便可以返回一个用于获取值的迭代器。

数组默认就是一个迭代器:

var arr = [11,12,13];
var itr = arr[Symbol.iterator]();

itr.next(); // { value: 11, done: false }
itr.next(); // { value: 12, done: false }
itr.next(); // { value: 13, done: false }

itr.next(); // { value: undefined, done: true }
你可以通过 [Symbol.iterator]() 自定义一个对象的迭代器。

1.16. Generators

Generator 函数是 ES6 的新特性,它允许一个函数返回的可遍历对象生成多个值。

在使用中你会看到 * 语法和一个新的关键词 yield:

function *infiniteNumbers() {
  var n = 1;
  while (true){
    yield n++;
  }
}

var numbers = infiniteNumbers(); // returns an iterable object

numbers.next(); // { value: 1, done: false }
numbers.next(); // { value: 2, done: false }
numbers.next(); // { value: 3, done: false }

每次执行 yield 时,返回的值变为迭代器的下一个值。

1.17. Promises

ES6 对 Promise 有了原生的支持,一个 Promise 是一个等待被异步执行的对象,当它执行完成后,其状态会变成 resolved 或者 rejected。

var p = new Promise(function(resolve, reject) {  
  if (/* condition */) {
    // fulfilled successfully
    resolve(/* value */);  
  } else {
    // error, rejected
    reject(/* reason */);  
  }
});

每一个 Promise 都有一个 .then 方法,这个方法接受两个参数,第一个是处理 resolved 状态的回调,一个是处理 rejected 状态的回调:

p.then((val) => console.log("Promise Resolved", val),
       (err) => console.log("Promise Rejected", err));

二、ES6中的Module模块化

ES6之前使用RequireJS或者seaJS实现模块化, requireJS是基于AMD规范的模块化库, 而像seaJS是基于CMD规范的模块化库, 两者都是为了为了推广前端模块化的工具。

现在ES6自带了模块化, 也是JS第一次支持module, 在很久以后 ,我们可以直接作用import和export在浏览器中导入和导出各个模块了, 一个js文件代表一个js模块;

现代浏览器对模块(module)支持程度不同, 目前都是使用babelJS, 或者Traceur把ES6代码转化为兼容ES5版本的js代码;

2.1、ES6模块化特点

1、每一个模块只加载一次, 每一个JS只执行一次, 如果下次再去加载同目录下同文件,直接从内存中读取。 一个模块就是一个单例,或者说就是一个对象;

2、每一个模块内声明的变量都是局部变量, 不会污染全局作用域;

3、模块内部的变量或者函数可以通过export导出;

4、一个模块可以导入别的模块

2.1、在Chrome浏览器使用Module

Chrome 61就提供了对ES2015 import语句的支持,实现模块加载

查看版本的办法是:在chrome浏览器中输入chrome://version/

谷歌浏览器(Canary 60) – 需要在chrome:flags里开启”实验性网络平台功能(Experimental Web Platform)”

示例:lib.js

/**
 *定义模块
 */
//导出
export let msg="求和:";
export function sum(n){
    let total=0;
    for(var i=1;i<=n;i++){
        total+=i;
    }
    return total;
}

html:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Module模块</title>
    </head>
    <body>
        <script type="module">
            //导入
            import {sum,msg} from './lib.js';
            let result=sum(100);
            console.log(msg+""+result);
        </script>
    </body>
</html>

结果:

2.2、在Node.js中使用Module

升级node 8.5 使用 experimental-modules参数,且要求所有文件名后缀都要修改为mjs node --experimental-modules index.mjs 定义模块lib.mjs:

/**
 *定义模块
 */
//导出
export let msg="求和:";
export function sum(n){
    let total=0;
    for(var i=1;i<=n;i++){
        total+=i;
    }
    return total;
}

定义main.mjs文件

/**
 * 使用模块
 */
//导入
import { sum, msg } from './lib.mjs';
let result = sum(100);
console.log(msg + "" + result);

在命令行下转换到当前目录,使用node加参数experimental-modules执行,结果如下:

2.3、Babel

Babel是一个广泛使用的转码器,可以将ES6代码转为ES5代码,从而在现有环境执行。

2.3.1、配置环境

安装babel命令行工具:

npm install --global babel-cli

安装成功后可以使用babel -V查看版本,可以使用babel -help 查看帮助

创建项目,在当前项目中依赖babel-core

假定当前项目的目录为:E:\Desktop-temp\xww\FastResponse\Mobile\Hybird\vue2_01\vue07_03_babel

使用npm init可以初始化当前项目为node项目

npm install babel-core --save

依赖插件babel-preset-es2015

如果想使用es6语法,必须安装一个插件

npm install babel-preset-es2015

然后在文件夹下面创建一个叫.babelrc的文件,并写入如下代码:

{
"presets": ["es2015"]
}

windows不支持直接命令为.babelrc,可以在DOS下使用@echo结合>实现:

.babelrc文件以rc结尾的文件通常代表运行时自动加载的文件,配置等等的,类似bashrc,zshrc。同样babelrc在这里也是有同样的作用的,而且在babel6中,这个文件必不可少。 在babel6中,预设了6种,分别是:es2015、stage-0、stage-1、stage-2、stage-3、react

2.3.2、转换ES6为ES5

当环境准备好了,就可以编写一个es6风格的文件如:es6.js,内容如下:

let add=(x,y)=>x+y;
const n1=100,n2=200;
var result=add(n1,n2);
console.log(result);

在当前目录执行命令:

babel es6.js -o es5.js

转换后的结果es5.js:

"use strict";

var add = function add(x, y) {
  return x + y;
};
var n1 = 100,
    n2 = 200;
var result = add(n1, n2);
console.log(result);

从转换后的结果可以看出es6已变成es5了,箭头函数不见了。 

2.3.3、使用babel-node运行ES6模块化代码

babel-cli工具自带一个babel-node命令,提供一个支持ES6的REPL环境。它支持Node的REPL(交互式解释器环境)环境的所有功能,而且可以直接运行ES6代码。

在当前目录下创建lib.js文件:

/**
 *定义模块
 */
//导出
export let msg="求和:";
export function sum(n){
    let total=0;
    for(var i=1;i<=n;i++){
        total+=i;
    }
    return total;
}

创建main.js文件调用定义好的模块:

/**
 * 使用模块
 */
//导入
import { sum, msg } from './lib.js';
let result = sum(100);
console.log(msg + "" + result);

在命令行执行:babel-node main.js 结果如下:

到这里共讲解了3种可以运行ES6模块化的环境,任选一种可以用于学习。

2.4、模块(Modules)

ES6从语言层面对模块进行了支持。编写方式借鉴了流行的JavaScript模块加载器(AMD, CommonJS)。由宿主环境的默认加载器定义模块运行时的行为,采取隐式异步模式——在模块可以被获取和加载前不会有代码执行。

定义模块:

// lib/math.js

export function sum(x, y) {
return x + y;
}
export var pi = 3.141593;

导入模块:

//全部导入  
import people from './example'  
  
//有一种特殊情况,即允许你将整个模块当作单一对象进行导入  
//该模块的所有导出都会作为对象的属性存在  
import * as example from "./example.js"  
console.log(example.name)  
console.log(example.age)  
console.log(example.getName())  
  
//导入部分  
import {name, age} from './example'  
  
//导出默认, 有且只有一个默认  
export default App  
  
// 部分导出  
export class App extend Component {};  

*表示所有,as取别名

// app.js

import * as math from "lib/math";
console.log("2π = " + math.sum(math.pi, math.pi));

// otherApp.js

导入部分内容

import {sum, pi} from "lib/math";
console.log("2π = " + sum(pi, pi));

还有的功能包括:export default and export *:

// lib/mathplusplus.js

export * from "lib/math";
export var e = 2.71828182846;
export default function(x) {
return Math.exp(x);
}

// app.js

import exp, {pi, e} from "lib/mathplusplus";
console.log("e^π = " + exp(pi));

导入的时候有没有大括号的区别:

  • 1.当用export default people导出时,就用 import people 导入(不带大括号)
  • 2.一个文件里,有且只能有一个export default。但可以有多个export。
  • 3.当用export name 时,就用import { name }导入(记得带上大括号)
  • 4.当一个文件里,既有一个export default people, 又有多个export name 或者 export age时,导入就用 import people, { name, age }
  • 5.当一个文件里出现n多个 export 导出很多模块,导入时除了一个一个导入,也可以用import * as example

模块的格式:

Babel可以将ES2015的模块转换为一下几种格式:Common.js,AMD,System,以及UMD。你甚至可以创建你自己的方式。

2.4.1、导出方式一

使用 export{接口} 导出接口, 大括号中的接口名字为上面定义的变量, import和export是对应的;

//lib.js 文件
let bar = "stringBar";
let foo = "stringFoo";
let fn0 = function() {
    console.log("fn0");
};
let fn1 = function() {
    console.log("fn1");
};
export{ bar , foo, fn0, fn1}

//main.js文件
import {bar,foo, fn0, fn1} from "./lib";
console.log(bar+"_"+foo);
fn0();
fn1();

示例:

先配置babel的运行环境,创建util.js文件:

let PI=3.14;
function getArea(r){
    return PI*r*r;
}

//集中导出对象
export {PI,getArea}

导入模块main.js:

import {PI,getArea} from './util'

console.log("R=5时面积为:"+getArea(5));

结果:

2.4.2、导出方式二

在export接口的时候, 我们可以使用 XX as YY, 把导出的接口名字改了, 比如: closureFn as sayingFn, 把这些接口名字改成不看文档就知道干什么的

//lib.js文件
let fn0 = function() {
    console.log("fn0");
};
let obj0 = {}
export { fn0 as foo, obj0 as bar};

//main.js文件
import {foo, bar} from "./lib";
foo();
console.log(bar);

2.4.3、导出方式三

这种方式是直接在export的地方定义导出的函数,或者变量:

//lib.js文件
export let foo = ()=> {console.log("fnFoo") ;return "foo"},bar = "stringBar";

//main.js文件
import {foo, bar} from "./lib";
console.log(foo());
console.log(bar);

2.4.4、导出方式四

这种导出的方式不需要知道变量的名字, 相当于是匿名的, 直接把开发的接口给export; 如果一个js模块文件就只有一个功能, 那么就可以使用export default导出;

//lib.js
export default "string";

//main.js
import defaultString from "./lib";
console.log(defaultString);

2.4.5、导出方式五

export也能默认导出函数, 在import的时候, 名字随便写, 因为每一个模块的默认接口就一个

//lib.js
let fn = () => "string";
export {fn as default};

//main.js
import defaultFn from "./lib";
console.log(defaultFn());

2.4.6、导出方式六

使用通配符* ,重新导出其他模块的接口

//lib.js
export * from "./other";
//如果只想导出部分接口, 只要把接口名字列出来
//export {foo,fnFoo} from "./other";

//other.js
export let foo = "stringFoo", fnFoo = function() {console.log("fnFoo")};

//main.js
import {foo, fnFoo} from "./lib";
console.log(foo);
console.log(fnFoo());

在import的时候可以使用通配符*导入外部的模块:

import * as obj from "./lib";
console.log(obj);

2.5、模块加载器(Module Loaders)

这并不是ES2015的一部分:这部分ECMAScript 2015规范是由实现定义(implementation-defined)的。最终的标准将在WHATWG的Loader 规范中确定,目前这项工作正在进行中,下面的内容来自于之前的ES2015草稿。

模块加载器支持以下功能:

  • 动态加载(Dynamic loading)
  • 状态一致性(State isolation)
  • 全局空间一致性(Global namespace isolation)
  • 编译钩子(Compilation hooks)
  • 嵌套虚拟化(Nested virtualization)

你可以对默认的加载器进行配置,构建出新的加载器,可以被加载于独立或受限的执行环境。

// 动态加载 – ‘System’ 是默认的加载器
System.import("lib/math").then(function(m) {
alert("2π = " + m.sum(m.pi, m.pi));
});

// 创建执行沙箱 – new Loaders
var loader = new Loader({
global: fixup(window) // replace ‘console.log’
});
loader.eval("console.log(\"hello world!\");");

// 直接操作模块的缓存
System.get("jquery");
System.set("jquery", Module({$: $})); // WARNING: not yet finalized

需要额外的polyfill 由于Babel默认使用common.js的模块,你需要一个polyfill来使用加载器API。

使用模块加载器 为了使用此功能,你需要告诉Babel使用system模块格式化工具。

三、Vue-cli脚手架搭建、开发与发布项目

单页Web应用(single page web application,SPA),就是只有一张Web页面的应用,是加载单个HTML 页面并在用户与应用程序交互时动态更新该页面的Web应用程序。

提供一个官方命令行工具,可用于快速搭建大型单页应用(SPA)。该工具为现代化的前端开发工作流提供了开箱即用的构建配置。只需几分钟即可创建并启动一个带热重载、保存时静态检查以及可用于生产环境的构建配置的项目:

# 全局安装 vue-cli
$ npm install --global vue-cli
# 创建一个基于 webpack 模板的新项目
$ vue init webpack my-project
# 安装依赖,走你
$ cd my-project
$ npm install
$ npm run dev

注意:CLI 工具假定用户对 Node.js 和相关构建工具有一定程度的了解。如果你是新手,我们强烈建议先在不用构建工具的情况下通读指南,在熟悉 Vue 本身之后再使用 CLI。

3.1、环境搭建

3.1.1、安装node.js

从node.js官网下载并安装node,安装过程很简单,一路“下一步”就可以了。安装完成之后,打开命令行工具(win+r,然后输入cmd),输入 node -v,如下图,如果出现相应的版本号,则说明安装成功。

如果安装不成功,可以直接把安装包修改成压缩包,解压后配置环境变量也可以,就成了绿色版。

这里需要说明下,因为在官网下载安装node.js后,就已经自带npm(包管理工具)了,另需要注意的是npm的版本最好是3.x.x以上,以免对后续产生影响。

注意版本不能太低,如果您已经安装了低版本的node可以使用npm直接更新。

3.1.2、修改npm为淘宝镜像(非必要)

因为npm的仓库有许多在国外,访问的速度较慢,建议修改成cnpm,换成taobao的镜像。

打开命令行工具,复制如下配置:

npm install -g cnpm --registry=https://registry.npm.taobao.org

安装这里是因为我们用的npm的服务器是外国,有的时候我们安装“依赖”的时候很很慢很慢超级慢,所以就用这个cnpm来安装我们说需要的“依赖”。安装完成之后输入 cnpm -v,如下图,如果出现相应的版本号,则说明安装成功。

版本号:

3.1.3、安装webpack

安装webpack,打开命令行工具输入:

npm install webpack -g

安装完成之后输入

webpack -v

如下图,如果出现相应的版本号,则说明安装成功。

3.1.4、安装vue-cli脚手架构建工具

打开命令行工具输入:

cnpm install vue-cli -g

安装完成之后输入 vue -V(注意这里是大写的“V”),如下图,如果出现相应的版本号,则说明安装成功。

3.2、构建项目

1)、在硬盘上找一个文件夹放工程用的。这里有两种方式指定到相关目录:

①cd 目录路径

②如果以安装git的,在相关目录右键选择Git Bash Here

2)、安装vue脚手架输入:

vue init webpack projectName

注意这里的“projectName” 是项目的名称可以说是随便的起名,但是“不能用中文”,要求全部小写。

提示选择项:

$ vue init webpack exprice --------------------- 这个是那个安装vue脚手架的命令
This will install Vue 2.x version of the template. ---------------------这里说明将要创建一个vue 2.x版本的项目
For Vue 1.x use: vue init webpack#1.0 exprice
? Project name (exprice) ---------------------项目名称
? Project name exprice
? Project description (A Vue.js project) ---------------------项目描述
? Project description A Vue.js project
? Author Datura --------------------- 项目创建者
? Author Datura
? Vue build (Use arrow keys)
? Vue build standalone
? Install vue-router? (Y/n) --------------------- 是否安装Vue路由,也就是以后是spa(但页面应用需要的模块)
? Install vue-router? Yes
? Use ESLint to lint your code? (Y/n) n ---------------------是否启用eslint检测规则,这里个人建议选no
? Use ESLint to lint your code? No
? Setup unit tests with Karma + Mocha? (Y/n)
? Setup unit tests with Karma + Mocha? Yes
? Setup e2e tests with Nightwatch? (Y/n)
? Setup e2e tests with Nightwatch? Yes
vue-cli · Generated "exprice".
To get started: --------------------- 这里说明如何启动这个服务
cd exprice
npm install
npm run dev

3)、cd 命令进入创建的工程目录,首先cd projectName;

目录:

|-- build                            // 项目构建(webpack)相关代码
|   |-- build.js                     // 生产环境构建代码
|   |-- check-version.js             // 检查node、npm等版本
|   |-- dev-client.js                // 热重载相关
|   |-- dev-server.js                // 构建本地服务器
|   |-- utils.js                     // 构建工具相关
|   |-- webpack.base.conf.js         // webpack基础配置
|   |-- webpack.dev.conf.js          // webpack开发环境配置
|   |-- webpack.prod.conf.js         // webpack生产环境配置
|-- config                           // 项目开发环境配置
|   |-- dev.env.js                   // 开发环境变量
|   |-- index.js                     // 项目一些配置变量
|   |-- prod.env.js                  // 生产环境变量
|   |-- test.env.js                  // 测试环境变量
|-- src                              // 源码目录
|   |-- components                     // vue公共组件
|   |-- store                          // vuex的状态管理
|   |-- App.vue                        // 页面入口文件
|   |-- main.js                        // 程序入口文件,加载各种公共组件
|-- static                           // 静态文件,比如一些图片,json数据等
|   |-- data                           // 群聊分析得到的数据用于数据可视化
|-- .babelrc                         // ES6语法编译配置
|-- .editorconfig                    // 定义代码格式
|-- .gitignore                       // git上传需要忽略的文件格式
|-- README.md                        // 项目说明
|-- favicon.ico 
|-- index.html                       // 入口页面
|-- package.json                     // 项目基本信息

3.3、运行项目

6)、启动项目,输入:npm run dev。服务启动成功后浏览器会默认打开一个“欢迎页面”,如下图:

编译成功后可以直接在浏览器中查看项目:

3.4、Vue-cli HelloWorld

了解了默认的模板内容,我们可以开始定义自己的vue程序了,这里写一个简单的HelloWorld,在src目录下创建一个Hi.vue文件,内容如下:

<template>
    <div id="app1">
        <input v-model="msg" v-on:click="sayhi"/>
        <p>
            <h2>{{msg}}</h2>
        </p>
    </div>
</template>

<script>
    export default {
        name: 'Hi',
        data() {
            return {
                msg: 'My First vue-cli app!'
            }
        },
        methods:{
            sayhi:function(){
                alert(this.msg);
            }
        }
    }
</script>

<style>
    #app1 {
        font-family: "microsoft yahei";
        color: dodgerblue;
        font-size: 20px;
    }
</style>

修改main.js

// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './Hi'

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  template: '<App/>',
  components: { App }
})

运行结果:

3.5、打包发布

自己的项目文件都需要放到 src 文件夹下

项目开发完成之后,可以输入 npm run build 来进行打包工作

npm run build

打包完成后,会生成 dist 文件夹,如果已经修改了文件路径,可以直接打开本地文件查看

项目上线时,只需要将 dist 文件夹放到服务器就行了。

四、TodoList示例

4.1、创建项目

在磁盘中新建一个空文件夹作为项目的根目录,如c:\todolist

使用webpack的模板初始化一个项目:vue init webpack todolist

4.2、运行默认项目

当看到如下结果时创建项目就完成了,可以运行

 当所有的依赖加载完成后就可以使用指令:npm run dev 运行默认项目了

结果:

在浏览器中运行:localhost:8080

项目的分析可以看视频,这里直接就创建todolist模块:

4.3、创建组件

工作基本原理在视频中已讲解,请查看视频,在页面尾部。

4.3.1、创建子组件TodoItem

将项目导入到开发工具中,如HBuilder,如下所示:

在:todolist\src\components创建TodoItem.vue组件

代码:

<template>
    <li class="item">
        {{title}} <button @click="del">x</button>
    </li>
</template>

<script>
    export default {
        props: ['title'],
        methods: {
            del: function() {
                this.$emit("delete"); //通知父组件执行事件delete
            }
        }
    }
</script>

<style>
    .item {
        color: blue;
    }
    
    .item button {
        color: red;
    }
</style> 

4.3.2、创建父组件Todo

<template>
    <div>
        <p>
            <label>任务:</label>
            <input v-model="newtask" @keypress.enter="addNew"/>
        </p>
        <ul>
            <li is="TodoItem" v-for="(task,index) in tasks" :title="task" @delete="remove(index)"></li>
        </ul>
    </div>
</template>

<script>
    import TodoItem from './TodoItem'
    export default {
        data: function() {
            return {
                tasks: ["买一本书", "给爸妈打电话", "整理自己的硬盘"],
                newtask:''
            }
        },
        components: {
            TodoItem
        },
        methods: {
            remove: function(index) {
                if(confirm("您真的要删除吗?")) {
                    this.tasks.splice(index, 1);
                }
            },
            addNew:function(){
                this.tasks.unshift(this.newtask);
            }
        }
    }
</script>

<style>

</style>

4.4、在App.Vue根组件中调用Todo组件

<template>
    <div id="app">
        <img src="./assets/logo.png">
        <TodoItem :title="'任务项'" @delete='remove'></TodoItem>
        <Todo/>
    </div>
</template>

<script>
    import HelloWorld from './components/HelloWorld'
    import TodoItem from './components/TodoItem'
    import Todo from './components/Todo'

    export default {
        name: 'app',
        components: {
            HelloWorld,
            TodoItem,
            Todo
        },
        methods: {
            remove: function() {
                alert("删除事件");
            }
        }
    }
</script>

<style>
    #app {
        font-family: 'Avenir', Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
        margin-top: 60px;
    }
</style>

4.5、测试结果

4.6、发布项目

进入项目的当前目录,在dos下执行npm run build

 当发布成功时会生成一个dist的目录,这就是最终运行的项目,代码经过了转换,压缩,混淆。

4.7、运行发布的项目

将dist目录放到运行环境下,这里就直接拖动到HBuilder的项目目录中,如下图所示:

将index.html文件中的绝对路径修改为相对路径,注意static之前的斜线:

<!DOCTYPE html>
<html>

    <head>
        <meta charset=utf-8>
        <meta name=viewport content="width=device-width,initial-scale=1">
        <title>todolist</title>
        <link href=static/css/app.b4bf92d7b44dab6d09d7150561ccda45.css rel=stylesheet>
    </head>

    <body>
        <div id=app></div>
        <script type=text/javascript src=static/js/manifest.c7e501867173ed73c464.js></script>
        <script type=text/javascript src=static/js/vendor.8226f675e046df334018.js></script>
        <script type=text/javascript src=static/js/app.96fcd6038a3373ac14ee.js></script>
    </body>

</html>

在浏览器中查看的效果如下:

五、作业

5.1、熟悉ES6的语法,写一个箭头函数,使用Babel转换成ES5的脚本。

5.2、创建一个模块定义一个用于计算最大值的方法,引用该模块并调用计算最大值的方法输出结果。分别使用三种不同的环境执行。

5.3、创建一个vue-cli下的项目,实现TodoList功能,如第四节所示。

5.4、在Vue-cli环境下定义一个分页组件,调用并发布项目。

要求:在第一页时,禁用上一页,以及首页按钮;在最后一页时,禁用下一页,以及尾页按钮;超出范围的页码以...来代替

六、视频

https://www.bilibili.com/video/av17503637/

七、示例

https://git.coding.net/zhangguo5/vue2.git

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏黑泽君的专栏

day11_JSP+EL+JSTL学习笔记

    JSP全称是Java Server Pages,它和servle技术一样,都是SUN公司定义的一种用于开发动态web资源的技术。

1641
来自专栏大内老A

我所理解的Remoting(1):Marshaling & Activation[上篇]

什么是Marshaling &Activation 对任何一项分布式技术(Distributed Technology),比如Remoting,XML Web ...

1898
来自专栏GopherCoder

『Go 内置库第一季:error』

Go 中的错误处理和别的语言不一样,设计哲学也不一样,常有开发者埋怨 Go 语言中的错误处理。

1143
来自专栏JackieZheng

探秘Tomcat——一个简易的Servlet容器

即便再简陋的服务器也是服务器,今天就来循着书本的第二章来看看如何实现一个servlet容器。 背景知识   既然说到servlet容器这个名词,我们首先要了解它...

2275
来自专栏ccylovehs

JavaScript异步编程

平时开发经常会用到js异步编程,由于前端展示页面都是基于网络机顶盒(IPTV的一般性能不太好,OTT较好),目前公司主要采取的异步编程的方式有setTimeou...

1352
来自专栏跟着阿笨一起玩NET

C# 解析js方法,并调用js方法

本文转载:http://www.cnblogs.com/StudyLife/archive/2013/03/11/2953516.html

3263
来自专栏极客猴

Django 学习笔记之模板

本文是自己 Django 学习笔记系列的第四篇原创文章。主要接着篇文章的视图内容,讲解模板的用法。另外也说下 Django 学习笔记系列的安排。自己计划大概 1...

410
来自专栏用户2442861的专栏

web.xml配置详解

1、web.xml学名叫部署描述符文件,是在Servlet规范中定义的,是web应用的配置文件。

3671
来自专栏cnblogs

knockoutjs 上自己实现的flux

在knockoutjs 上实现 Flux 单向数据流 状态机,主要解决多个组件之间对数据的耦合问题。 一、其实简单 flux的设计理念和实现方案,很大程度上人借...

2208
来自专栏大内老A

通过扩展让ASP.NET Web API支持JSONP

同源策略(Same Origin Policy)的存在导致了“源”自A的脚本只能操作“同源”页面的DOM,“跨源”操作来源于B的页面将会被拒绝。同源策略以及跨域...

1877

扫码关注云+社区

领取腾讯云代金券