Javascript的异步机制

0. 异步入坑。

讨论这个问题一定是在入坑Node.js后,而且很有可能是在入坑Express和Koa后。在ES5和ES6中,与异步相关的机制主要涉及以下几个概念:

  • 回调函数(ES5)
  • Promise + then(ES6)
  • Generator + next(ES6)
  • async + await(ES6)

1. 回调函数

Javascript异步操作最基本的解决方案。
由于name的定义是异步的,存在1秒的时延,所以name变量只有通过回调函数才能获取到。

function getData(callback){
    setTimeout(()=>{
        var name = "Tom";
        callback(name);
    },1000);
}
getData((data)=>{
    console.log(`Name is ${data}`);
});

2. Promise + then

ES6异步的另一种解决方案。和回调函数的语法和逻辑最为相像。

function getData(resolve,reject){
    setTimeout(()=>{
        try{
            var name = "Tom";
            resolve(name);
        }catch(e){
            reject(e);
        }
    },1000);
}
var p = new Promise(getData);
p.then((data)=>{
    console.log(`My name is ${data}!`);
});

上面的代码也可以合并简化一下:

var p = new Promise(function(resolve,reject){
    setTimeout(function(){
        try{
            var name = "Tom";
            resolve(name);
        }catch(e){
            reject(e);
        }
    },1000);
});
p.then((data)=>{
    console.log(`My name is ${data}!`);
});

3. Generator + next

3.1 基本概念。

形式上,Generator 函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

var hw = helloWorldGenerator();

上面代码定义了一个 Generator 函数helloWorldGenerator,它内部有两个yield表达式(hello和world),即该函数有三个状态:hello,world 和 return 语句(结束执行)。

然后,Generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是,调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是上一章介绍的遍历器对象(Iterator Object)。

下一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。换言之,Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。

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

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

hw.next()
// { value: 'ending', done: true }

hw.next()
// { value: undefined, done: true }

上面代码一共调用了四次next方法。

第一次调用,Generator 函数开始执行,直到遇到第一个yield表达式为止。next方法返回一个对象,它的value属性就是当前yield表达式的值hello,done属性的值false,表示遍历还没有结束。

第二次调用,Generator 函数从上次yield表达式停下的地方,一直执行到下一个yield表达式。next方法返回的对象的value属性就是当前yield表达式的值world,done属性的值false,表示遍历还没有结束。

第三次调用,Generator 函数从上次yield表达式停下的地方,一直执行到return语句(如果没有return语句,就执行到函数结束)。next方法返回的对象的value属性,就是紧跟在return语句后面的表达式的值(如果没有return语句,则value属性的值为undefined),done属性的值true,表示遍历已经结束。

第四次调用,此时 Generator 函数已经运行完毕,next方法返回对象的value属性为undefined,done属性为true。以后再调用next方法,返回的都是这个值。

3.2 异步

function* gen(x) {
  var y = yield x + 2;
  return y;
}

var g = gen(1);
g.next() // { value: 3, done: false }
g.next() // { value: undefined, done: true }

上面代码中,调用 Generator 函数,会返回一个内部指针(即遍历器)g。这是 Generator 函数不同于普通函数的另一个地方,即执行它不会返回结果,返回的是指针对象。调用指针g的next方法,会移动内部指针(即执行异步任务的第一段),指向第一个遇到的yield语句,上例是执行到x + 2为止。

换言之,next方法的作用是分阶段执行Generator函数。每次调用next方法,会返回一个对象,表示当前阶段的信息(value属性和done属性)。value属性是yield语句后面表达式的值,表示当前阶段的值;done属性是一个布尔值,表示 Generator 函数是否执行完毕,即是否还有下一个阶段。

4. async + await

async函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。

async function getStockPriceByName(name) {
  const symbol = await getStockSymbol(name);
  const stockPrice = await getStockPrice(symbol);
  return stockPrice;
}

getStockPriceByName('goog').then(function (result) {
  console.log(result);
});

上面代码是一个获取股票报价的函数,函数前面的async关键字,表明该函数内部有异步操作。调用该函数时,会立即返回一个Promise对象。(此部分参考:https://es6.ruanyifeng.com)

移动端开发的一些常用知识

1. 字体的px,em和rem

1.1 px

px是固定的像素,一旦设置了就无法因为适应页面大小而改变。

1.2 em

  • 子元素字体大小的em是相对于父元素字体大小
  • 元素的width/height/padding/margin用em的话是相对于该元素的font-size

1.3 rem

rem中的r代表的是是root。
rem是全部的长度都相对于根元素,根元素是谁?<html>元素。
通常做法是给html元素设置一个字体大小,然后其他元素的长度单位就为rem。

1.4 总结

在做项目的时候用什么单位长度取决于具体的需求,但一般是这样的:

  • 像素(px):用于元素的边框或定位。
  • em/rem:用于做响应式页面,不过我更倾向于rem,因为em不同元素的参照物不一样(都是该元素父元素),所以在计算的时候不方便,相比之下rem就只有一个参照物(html元素),这样计算起来更清晰。

2. 分辨率,PPI,DPI,DPR的概念解释

2.1 分辨率

是指设备屏幕宽度上和高度上最多能显示的物理像素点个数。

2.2 PPI

PPI:Pixel Per Inch。屏幕像素密度,即每英寸(1英寸=2.54厘米)聚集的像素点个数,这里的一英寸是对角线长度。

2.3 DPI

DPI:Dots Per Inch。每英寸像素点,印刷行业标准。衡量打印机精度以及图片精度的标准。

2.4 DPR

DPR:Device Pixel Ratio。是默认缩放为100%的情况下,设备物理像素和CSS像素的比值。
举个例子:
这里先无限diss B站中的某个傻X老师,乱讲一通,害得我思考了好久。
iPhone6的CSS的物理像素是:750x1334。
iPhone6默认的DPR=2,那么其默认的css逻辑像素就375x667。
这样的结果是什么呢?结果就是,如果你在代码中写了

<div style="width=375px;height:667px"></div>

这个div就会充满iPhone的屏幕。相当于css中的1px占用了手机的2个像素。

2.5 应用

2.5.1 有了以上的概念,我们便可以通过动态viewport设置页面,使css像素对应设备屏幕物理像素。
<script type="text/javascript">
        var scale = 1/window.devicePixelRatio;
        document.write(`
            <meta name="viewport" content="initial-scale=${scale},minimum-scale=${scale},maximum-scale=${scale},user-scalable=no" />
        `);
</script>
2.5.2 还可以模仿微信对屏幕的丈量标准,将屏幕宽度分为750份。
<script type="text/javascript">
    var unit = window.innerWidth / 750; //将屏幕分为750个单位
    document.write(`
        <style>
            html{
                font-size:$(100*unit)px; /*根元素字体大小是100个单位*/
            }
            .zhengWen{
                font-size:0.05rem; /*正文字体大小是5个单位*/
            }
        </style>
    `);
</script>

Vue.js随学随记

1. v-bind和v-model理解

1.1 v-bind

v-bind代表元素的某个属性交给vue处理,实现元素属性和vue的绑定,v-bind="表达式",引号里面的本质是表达式。但是这种绑定是单向的。比如下面的代码,当你在网页中修改input中值的时候,上面的message内容不会随着修改。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
        <script type="text/javascript" src="vue.js"></script>
    </head>
    <body>
        <div id="app">
            <div>{{message}}</div>
            <div>
                <input type="text" v-bind:value="message"/>
            </div>
        </div>

        <script type="text/javascript">
            var vm = new Vue({
                el:"#app",
                data:{
                    message:"Welcome to Vue"
                }
            });
        </script>
    </body>
</html>

如果想实现双向绑定,还需要向下方一样为标签添加v-on指令,这样在修改input里面内容的时候,上面message的内容也会相应的修改。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
        <script type="text/javascript" src="vue.js"></script>
    </head>
    <body>
        <div id="app">
            <div>{{message}}</div>
            <div>
                <input type="text" v-on:input="message = $event.target.value" v-bind:value="message"/>
            </div>
        </div>
        <script type="text/javascript">
            var vm = new Vue({
                el:"#app",
                data:{
                    message:"Welcome to Vue"
                }
            });
        </script>
    </body>
</html>

1.2 v-model

v-model实际上是v-bind:和v-on:的语法糖,实现了简易的元素属性双向绑定。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
        <script type="text/javascript" src="vue.js"></script>
    </head>
    <body>
        <div id="app">
            <div>{{message}}</div>
            <div>
                <input type="text" v-model="message"/>
                <!--上方代码等同于下方代码
                <input type="text" v-on:input="message = $event.target.value" v-bind:value="message"/>
                -->
            </div>
        </div>
        <script type="text/javascript">
            var vm = new Vue({
                el:"#app",
                data:{
                    message:"Welcome to Vue"
                }
            });
        </script>
    </body>
</html>

2. v-on:click 事件调用函数的问题。

2.1 方法一:直接调用函数名

<button v-on:click="change">改变一下</button>

这种情况下,函数被调用时,自动传递$event参数。

2.2 方法二:调用函数名()

这种情况下,函数被调用时,不会自动传递$event参数,如果需要,则必须人工指定为函数名($event)。也就是说下面的代码等同于上面的代码:

<button v-on:click="change($event)">改变一下</button>

3. v-if和v-show的区别。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
        <script type="text/javascript" src="vue.js"></script>
    </head>
    <body>
        <div id="app">
            <button type="button" v-if="isShowButton">使用v-if判断</button>
            <button type="button" v-show="isShowButton">使用v-show判断</button>
        </div>
        <script type="text/javascript">
            var vm = new Vue({
                el:"#app",
                data:{
                    isShowButton:false
                }
            });
        </script>
    </body>
</html>

运行结果如下:
file

  • 对于v-if,如果=后面的表达式是false,则整个标签不会显示。
  • 对于v-show,如果=后面的表达式是false,则会以display:none的形式隐藏标签。

4. v-for对数组和对象的使用方法。

4.1 示例代码

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
        <script type="text/javascript" src="vue.js"></script>
    </head>
    <body>
        <div id="app">
            <table border="1" cellspacing="0" cellpadding="0">
                <tr><th>姓名</th><th>性别</th><th>分数</th></tr>
                <tr v-for="student,arrayIndex in students">
                    <td v-for="value,key,objectIndex in student">{{arrayIndex}} - {{objectIndex}} - {{key}} - {{value}}</td>
                </tr>
            </table>
        </div>
        <script type="text/javascript">
            var vm = new Vue({
                el:"#app",
                data:{
                    students:[
                        {name:"张三",gender:"男",score:"100"},
                        {name:"李四",gender:"女",score:"88"},
                        {name:"王五",gender:"男",score:"99"}
                    ]
                }
            });
        </script>
    </body>
</html>

运行结果如下:
file

4.2 对于数组

<tr v-for="student,arrayIndex in students">
</tr>

遍历students数组的每一个元素。

4.3 对于对象

<td v-for="value,key,objectIndex in student">{{arrayIndex}} - {{objectIndex}} - {{key}} - {{value}}</td>

遍历student对象的每一个属性。

5. Vue-cli 3.0 的使用

5.0 理解Vue-cli的关键点

vue-cli是将Node.js和webpack封装进了vue-cli内部。

5.1 安装Vue-cli 3.0

cnpm install -g @vue/cli

需要注意的是,如果之前安装过Vue-cli的2.X版本,需要先卸载之前的版本再安装。卸载方法在安装Vue-cli 3.0的时候有提示,这里不赘述。

5.2 创建项目

安装完Vue-cli 3.0工具后,我们在命令行里面就可以直接使用vue命令了,下面的命令用于创建一个Vue.js的项目框架:

vue create 项目名

这里面有几个注意点:

  1. vue create 项目名命令只对Vue-cli 3.0及以后版本有效,2.X版本无法使用vue create命令
  2. 运行这个命令后,会自动在cmd当前目录下创建【项目名】文件夹,然后在里面生成Vue.js的项目框架。
  3. 这里要特别解释下“脚手架”这个词。初次学Vue.js的时候,对“脚手架”工具特别不理解,不明白啥叫脚手架。说白了,“脚手架”就是辅助工具。Vue-cli的作用就是辅助进行大型的Vue.js项目开发。如果只是在项目中一般性的使用Vue.js,在网页中使用script引入Vue.js文件就够了,不是必须使用Vue-cli脚手架工具。
  4. 项目创建好后,项目的目录结构如下:
    file
    从上面的截图能看出来,其实vue create命令创建的是个node.js项目。其目的和原因也很简单:通过Node.js模拟服务器运行及热更新环境,这样可以让开发过程更加简单、高效。所以,如果要使用Vue-cli工具,前提是机器中必须提前安装了Node.js(如果不使用Vue-cli工具而是只用Vue.js,可以没有Node.js环境)。

5.3 构建项目

既然vue create项目创建的是Node.js项目,那么当时用npm run命令的时候,就可以直接调用package.json里面<script>标签下的三个命令。

5.1.1 serve命令:开发环境构建
npm run serve

这一步最主要的操作是通过Node.js启动一个Web服务器,这样可以直接在本地模拟服务器环境。

5.1.2 build命令:生产环境构建
npm run build

运行这个命令后,会在项目根目录生成一个【dist】文件夹,这里面的文件是经过编译、打包、压缩后,最终用于部署到服务器上的代码。
实际上,这个操作是webpack做的,vue-cli只是把webpack封装进去了。。

5.1.3 lint命令:代码检查工具
npm run lint

6 vue-router路由库使用

6.1 关于SPA

vue-router路由一般使用在SPA(Single Page Application)单页面程序上。SPA的简单的来说就是无论程序有多复杂,所有程序代码都写在一个页面上。
当然,在开发阶段,SPA项目开始分模块开发的,开发完毕后会通过构建工具将各类模块组成一个单页面程序。
SPA的有点有很多,比如:一次加载后,页面无需再跳转和下载新内容,页面速度快;再比如:有效降低网页被复制和爬取的风险。缺点也很明显,搜索引擎索引起来不是很好。不过SPA程序一开始就不是为搜索引擎而设计的。

6.2 vue-router

vue-router是vue.js的官方路由工具。第三方路由如Page.js、Director也很好用,与vue.js的集成也比较容易。

6.3 vue-router的安装

cnpm install vue-router --save

或者直接下载vue-router.js导入到项目中。

6.4 vue-router的使用

6.4.1 HTML代码
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>

<div id="app">
  <h1>Hello App!</h1>
  <p>
    <!-- 使用 router-link 组件来导航. -->
    <!-- 通过传入 `to` 属性指定链接. -->
    <!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
    <router-link to="/foo">Go to Foo</router-link>
    <router-link to="/bar">Go to Bar</router-link>
  </p>
  <!-- 路由出口 -->
  <!-- 路由匹配到的组件将渲染在这里 -->
  <router-view></router-view>
</div>
6.4.2 Javascript代码
// 0. 如果使用模块化机制编程,导入 Vue 和 VueRouter,要调用 Vue.use(VueRouter)

// 1. 定义(路由)组件。
// 可以从其他文件 import 进来
const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }

// 2. 定义路由
// 每个路由应该映射一个组件。 其中"component" 可以是
// 通过 Vue.extend() 创建的组件构造器,
// 或者,只是一个组件配置对象。
// 我们晚点再讨论嵌套路由。
const routes = [
  { path: '/foo', component: Foo },
  { path: '/bar', component: Bar }
]

// 3. 创建 router 实例,然后传 `routes` 配置
// 你还可以传别的配置参数, 不过先这么简单着吧。
const router = new VueRouter({
  routes // (缩写)相当于 routes: routes
})

// 4. 创建和挂载根实例。
// 记得要通过 router 配置参数注入路由,
// 从而让整个应用都有路由功能
const app = new Vue({
  router
}).$mount('#app')

// 现在,应用已经启动了!

当然,上面代码也可以稍微简化一下:

const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }

const router = new VueRouter({
  routes = [
    { path: '/foo', component: Foo },
    { path: '/bar', component: Bar }
  ]
})

const app = new Vue({
  router
}).$mount('#app')

Object.defineProperty的应用

1. 解释

使用Object.defineProperty(对象,属性名,GetSet方法对象),可以为某个对象设置属性和属性值。
在为对象设置属性值和提取属性值的时候,都可以添加额外功能。

2. 代码示例

var obj = {
    name:"Zhang"
};

var ageValue;

Object.defineProperty(obj,"age",{
    get(){
        console.log("正在获取age属性");
        return ageValue;
    },
    set(value){
        console.log("正在设置age属性");
        ageValue = value;
    }
});

console.log(obj.age);
obj.age = 100;
console.log(obj.age);

运行以上代码输出结果如下:
file

Windows下安装和配置MongoDB4.2.3及MongoDB基本命令

1. 下载安装文件。

MongoDB分为收费版和免费社区版,我们学习自然用免费就可以了,官网下载地址如下:
https://www.mongodb.com/download-center/community

2. 安装MongoDB

2.1 选择自定义安装

file

2.2 选择下安装目录

这里有个比较重要的地方,强烈建议安装目录中间不要有空格等非英文字符。因为在使用cmd的时候,带空格会导致不便。
file

2.3 这步默认即可

file

2.4 不要安装MongoDB Compass

这是个MongoDB的可视化管理软件,如果一起安装,安装过程奇慢。需要的可以后面自行安装。
file

2.5 安装完成后需要重启。

file

3. 连接MongoDB数据库

3.1 新建db目录

在安装目录以下位置新建空的db目录:MongoDB\data\db

3.2 开启MongoDB服务器

  1. cmd进入MongoDB目录的bin目录
  2. 运行命令:mongod --dbpath D:\ProgramData\MongoDB\data\db

如果出现以下内容,证明MongoDB服务已经开启:

file

3.3 连接MongoDB服务器

  1. cmd进入MongoDB目录的bin目录
  2. 运行命令:mongo.exe
  3. 使用以下任一命令测试是否连接成功:
db.stats()
db.help()
show dbs

4. MongoDB基本命令

4.0 名词“集合”和“文档”

集合:相当于SQL里面的表;
文档:相当于SQL里面表里面的一条数据;

4.1 db

显示当前所在的数据库。

4.2 db.stats()

显示当前数据库统计信息。

4.3 db.help()

帮助信息。

4.4 show dbs

显示MongoDB下的所有数据库名称(空数据库不显示,只显示有数据的数据库)。

4.5 use 数据库名

切换到相应的数据库。
如果数据库不存在,则新建这个数据库。

4.6 show collections

显示当前数据库里面的collection(类似MySQL的表)。空集合不显示,只显示有数据的集合。

4.7 db.集合名.find(条件)

db.student.find({})

上面代表:显示集合下的所有信息。

db.student.find({}).pretty()

上面代表:格式化显示集合下的所有信息。

db.student.find({price:120})

上面代表:条件查询,相当于SQL里面的where name = "张三"

db.student.find({price:{$gt:120}})

上面代表:条件查询,相当于SQL里面的where price > 123

db.student.find({price:{$gte:120}})

上面代表:条件查询,相当于SQL里面的where price >= 123

db.student.find({price:{$lt:120}})

上面代表:条件查询,相当于SQL里面的where price < 123

db.student.find({price:{$lte:120}})

上面代表:条件查询,相当于SQL里面的where price <= 123

db.student.find({price:{$ne:120}})

上面代表:条件查询,相当于SQL里面的where price != 123

db.student.find({
    $and:[
            {id:1},{name,"张三"}
    ]
})

上面代表:条件查询,相当于SQL里面的where price id=1 and name="张三"

4.8 db.dropDatabase()

删除当前数据库

4.9 skip和limit

db.student.find({}).skip(20).limit(10)

分页显示:跳过前面20条数据,显示10条数据。相当于分页显示第三页数据。

4.10 db.createCollection(集合名字,参数)

在当前数据库下创建一个集合

4.11 db.集合名.drop()

删除集合

4.12 db.集合名.insert({对象})

向集合中插入数据,比如:
db.product.insert({"productCategory":"电子产品","productName":"笔记本电脑"})

4.13 db.集合名.update(条件参数,新的数据)

db.age.update(
    {title:"张小三"},
    $set:{title:"张三",age:"18"}
}

更新集合中的数据,相当于SQL的:

update age set title="张三", age="18" where title="张小三"

4.14 db.集合名.remove(条件)

按条件删除集合中的某些数据

4.15 db.集合名.find().sort({属性:1或-1})

按照属性(列)进行排序,1为升序,-1位降序。

Node.js随学随记

0. Node.js动态文件编译的问题

在一切开始之前,先要明白一点:Node.js虽然使用Javascript语言编写,但是他的运行机制和Java、C++等编译型语言类似。也就是说,使用Node.js编写的代码如果要正常运行,是先要经历一个类似编译的过程的。
Node.js的这种运行机制所直接引发的结果就是:

  1. Node.js比普通Javascript运行速度要快。是否比Java和C++快我不清楚,但是至少应该比PHP快(因为PHP是解释性语言)。
  2. Node.js代码再修改后,必须重新运行node XXX.js命令后才能生效。

1. 利用NPM

npm有几个比较重要的命令:install,uninstall,init,run

1.1 install

带参数的install

当项目需要添加一个新的包的时候,使用npm install 包名称 --参数的形式,这时有四种安装方式可能:

npm install 包名称             //安装到当前cmd所在目录,不将包的依赖关系写入package.json文件
npm install 包名称 --save      //安装到当前cmd所在目录,将包的依赖关系写入package.json的dependencies节点
npm install 包名称 --save-dev  //安装到当前cmd所在目录,不将包的依赖关系写入package.json的devDependencies节点
npm install 包名称 -g          //安装到全局目录下,不将包的依赖关系写入package.json文件,且安装的这个包全局可用。

所以呢,对于Node.js使用的工具类包,比如:查询Node.js对ES6的支持度,使用--g是比较合适的,因为你肯定不希望每次使用这个工具的时候还要特意的将cmd目录切换到安装目录下。
而对于Node.js项目中使用的第三方类库比如express等,使用--save是比较合适的。因为这个类库只对当前项目有效。

不带参数的install

上面提到了,如果使用npm install 包名称 --save命令,会将安装的包的信息添加到package.json文件中。
package.json可以通过npm init命令创建,也可以在第一次使用npm install 包名称 --save命令的时候创建。
package.json文件有四个比较重要的节点信息:dependencies、devDependencies、script、main。

  • dependencies
    你的Node.js项目可能会通过npm install 包名称 --save命令安装很多包,当你把你的项目给别人的时候,你不需要把这些安装的包文件发给他们,因为你的这个包信息都写在了dependencies节点下面,拿到你项目文件的人只需要在项目目录中运行npm install,就可以安装所有package.json的dependencies节点下面的包了。这就极大的提升了项目和系统移植的便捷性。
  • devDependencies
    和dependencies节点的功能类似,只不过devDependencies节点下面的包只在你开发的时候有效,换句话说这些包是你在开发的时候临时用的。
  • script
    在项目目录中使用npm run 命令的时候,运行的实际上就是script节点下预先定义好的命令。
  • main
    指定整个Node.js项目的入口文件。

1.2 uninstall

卸载安装的包,如果这个包存在于package.json文件的dependencies节点中,相关信息将会被移除。

1.3 init

初始化一个新的Node.js项目,自动生成package.json文件架构。

1.4 run

npm run 命令
运行package.jsons文件cript节点下预先定义好的命令

2. events事件模块

events模块只提供了一个对象:events.EventEmitter。EventEmitter的核心就是事件触发事件监听

var events = require('events');   //引入events模块
var emitter = new events.EventEmitter();    //新建EventEmitter对象
emitter.on('someEvent', function(arg1, arg2) { 
    console.log('listener1', arg1, arg2); 
}); //事件监听
emitter.on('someEvent', function(arg1, arg2) { 
    console.log('listener2', arg1, arg2); 
}); //事件监听
emitter.emit('someEvent', 'byvoid', 1991); //事件触发。一个事件触发可以对应多个事件监听

3. fs文件系统模块

3.1 fs模块概述

Node.js 文件系统(fs 模块)模块中的方法均有异步和同步版本,例如读取文件内容的函数有异步的 fs.readFile() 和同步的 fs.readFileSync()。
异步的方法函数最后一个参数为回调函数,回调函数的第一个参数包含了错误信息(error)。
建议大家使用异步方法,比起同步,异步方法性能更高,速度更快,而且没有阻塞。

3.2 readFile,writeFile

读取和写入文件,适用于读取和写入数据量不大的情况。如果读取写入数据量很大,建议使用后面的stream相关函数。

var fs = require("fs");
// 读取文件
fs.readFile('input.txt', function (err, data) {
   if (err) {
       return console.error(err);
   }
   console.log("读取: " + data.toString());
});
// 写入文件
fs.writeFile('input.txt', '我是通 过fs.writeFile写入文件的内容',  function(err) {
   if (err) {
       return console.error(err);
   }
   console.log("数据写入成功!");
});

3.3 createReadStream,createWriteStream,pipe

对于大型文件的输入输出,使用stream相关函数。这样可以将大文件切分为小的块通过stream进行传输。
先看数据读取:

var fs = require("fs");
var data = '';
// 创建可读流
var readerStream = fs.createReadStream('video.mp4');
// 数据读取进行时
readerStream.on('data', function(chunk) {
   console.log(chunk);//这里面打印的就是buffer信息。
});
// 数据读取结束时
readerStream.on('end',function(){
   console.log("程序执行完毕");
});
// 数据读取出错时
readerStream.on('error', function(err){
   console.log(err.stack);
});

再看数据输出

var fs = require("fs");

var readerStream = fs.createReadStream('video.mp4');
var writerStream = fs.createWriteStream('video-bk.mp4');

readerStream.on('data', function(chunk) {
   writerStream.write(chunk);
});

pipe直接对接输入输出流

上面的输出代码可以通过pipe简化为:

var fs = require("fs");

var readerStream = fs.createReadStream('video.mp4');
var writerStream = fs.createWriteStream('video-bk.mp4');

readerStream.pipe(writerStream);

4. http和https模块

4.1 创建http服务器。

//server.js
const http = require('http');

const hostname = '127.0.0.1';
const port = 3000;

const server = http.createServer((req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end('Hello World');
});

server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});
//在cmd下运行node server.js即可。

4.2 爬取网页内容

http继承于stream类,所以其监听事件与stream很像。
如果是http网站。

const http = require('http');
http.get("http://www.royotech.com",function(response){
    var html = "";
    response.on("data", function(data){
        html = html + data;
    });
    response.on("end", function(data){
        console.log("网页爬取结束");
        console.log(html); 
    });
});

如果是https网站。

const http = require('http');
http.get("https://www.sina.com",function(response){
    var html = "";
    response.on("data", function(data){
        html = html + data;
    });
    response.on("end", function(data){
        console.log("网页爬取结束");
        console.log(html); 
    });
});

5. cheerio模块

该模块可以用来分析html代码。

const http = require('http');
const cheerio = require('cheerio');

http.get("http://www.royotech.com",function(response){
    var html = "";
    response.on("data", function(data){
        html = html + data;
    });
    response.on("end", function(data){
        console.log("网页爬取结束");
        const $ = cheerio.load(html);
        let linksSet = $('a');
        for(let i=0; i<linksSet.length; i++){
            let url = linksSet[i].attribs.href;
            if(url && url.search("http") != -1)
                console.log(url);
        }
    });
});

6. express框架

express框架本质上是基于Node.js的web相关模块进行的封装。

6.1 使用express框架搭建一个web服务器。

const express = require("express");
const app = express();
app.listen(3030,function(err){
    if(err){
        console.log(err);
        return;
    } else {
        console.log("服务器已经启动,请访问http://localhost:3030");
    }
});
app.get("/",function(request,response){
    response.send("这里是首页");
 });

cmd下运行node index.js后访问:http://localhost:3030/

6.2 express的get方法。

const express = require("express");
const app = express();
app.listen(3030,function(err){
    if(err){
        console.log(err);
        return;
    } else {
        console.log("服务器已经启动,请访问http://localhost:3030");
    }
});
app.get("/",function(request,response){
    response.send(`这里是首页!<br/>
    你可以<a href="http://localhost:3030/get_username/">点我</a>进入get_username页面,或者直接在浏览器上输入"http://localhost:3030/get_username/"访问
    `);
 });
app.get("/get_username",function(request,response){
   response.send({
       name:"张三",
       sex:"男"
   });
});

cmd下运行node index.js后访问:http://localhost:3030/

6.3 express的post方法。

//根目录下index.js文件
const express = require("express");
const app = express();
app.listen(3030,function(err){
    if(err){
        console.log(err);
        return;
    } else {
        console.log("服务器已经启动,请访问http://localhost:3030");
    }
});

const bodyParser = require("body-parser");
app.use(bodyParser.json());//分析提交上来的数据,将数据转换为json后挂在到request的body属性上。
// 创建 application/x-www-form-urlencoded 编码解析
var urlencodedParser = bodyParser.urlencoded({ extended: false });
app.post("/doRegister", urlencodedParser, function(request,response){
    response.send({
        username:request.body.username,
        password:request.body.password
    });
});

根目录下register.html文件的内容如下:

<html>
    <head>
        <title>注册页面</title>
    </head>
    <body>
        <div>
            <form action="/doRegister" method="post">
                用户名:<input name="username" type="text">
                密码:<input name="password" type="password">
                <input type="submit" value="注册提交">
            </form>
        </div>
    </body>
</html>

6.4 使用express.static来设置静态文件。

加入我们在项目根目录中建立一个public文件夹,里面增加两个静态文件:

  • admin.html
  • flower.png
    admin.html的内容如下:

    <html>
    <head>
        <title>Hello Index</title>
    </head>
    <body>
        你好!绝对路径下的花朵
        <img src="/flower.png">
        <hr/>
        你好!相对路径下的花朵
        <img src="./flower.png">
    </body>
    </html>

    再在根目录创建index.js文件,代码如下:

    const express = require("express");
    const app = express();
    app.listen(3030,function(err){
    if(err){
        console.log(err);
        return;
    } else {
        console.log("服务器已经启动,请访问http://localhost:3030");
    }
    });
    app.use(express.static(__dirname + "/public"));

    cmd下运行node index.js后,浏览器中访问http://localhost:3030/admin.html 的结果如下:
    file

这里要表达的是:
当某个文件夹A使用express.static设置为静态文件存储目录后,无论之前文件夹A所处的实际目录在什么位置,被设置后的A文件夹都被视为网站的根目录,对应的A文件夹下面的所有文件都被视为在网站根目录下,且文件类型都是静态文件。

6.5 配合axios实现Ajax功能。

//index.js
const express = require("express");
const app = express();
app.listen(3030,function(err){
    if(err){
        console.log(err);
        return;
    } else {
        console.log("服务器已经启动,请访问http://localhost:3030");
    }
});
app.use(express.static(__dirname + "/public"));

app.get("/get_username",function(request,response){
   response.send({
       name:"张三",
       sex:"男"
   });
});
<!--index.html-->
<html>
    <head>
        <title>Hello Index</title>
        <script src="https://cdn.bootcss.com/axios/0.19.0/axios.min.js"></script>
        <script>
                function getUserName(){
                    axios.get("./get_username").then(function(result){
                        document.getElementById("user_name").innerHTML = result.data.name;
                    });
                }
        </script>
    </head>
    <body>
        这里是网站首页
        <button onClick="getUserName()">获取用户名,通过Ajax请求</button>
        <div id="user_name"></div>
    </body>
</html>

6.6 使用Express的Router路由

const express = require("express");
const app = express();
app.listen(3030,function(err){
    if(err){
        console.log(err);
        return;
    } else {
        console.log("服务器已经启动,请访问http://localhost:3030");
    }
});
app.use(express.static(__dirname + "/public"));

const userRouter = express.Router();
const genderRouter = express.Router();
userRouter.get("/",function(request,response){
    response.send({
        name:"张三",
    });
 });
 genderRouter.get("/",function(request,response){
    response.send({
        gender:"男",
    });
 });
app.use("/get_username",userRouter);
app.use("/get_gender",genderRouter);

// //等同于下面的代码
// app.get("/get_username",function(request,response){
//    response.send({
//        name:"张三",
//    });
// });

// app.get("/get_gender",function(request,response){
//     response.send({
//         gender:"男",
//     });
//  });

7. Node.js连接MongoDB数据库

7.1 安装mongodb模块。

cmd切换到工作目录,然后:

cnpm install mongodb --save

7.2 连接数据库代码

//数据库名:product
//集合名:product_info
const mongodb = require("mongodb");
const mongoClient = mongodb.MongoClient;
var DB_CONN_STR = "mongodb://localhost:27017";
mongoClient.connect(DB_CONN_STR,function(err,client){
    if(err){
        console.log(err);
    } else {
        console.log("数据库连接成功!");
    }
    client.db("product").collection("product_info").find({}).toArray(function(err,result){
        if(err){
            console.log(err);
        } else {
            console.log(result);
        }
    });
    client.close();
});

如何将Joomla1.5升级至Joomla2.5

前言

本文转自我之前的博客,写于2012年。以下为文章原文:

近期接了一个荷兰的订单,主要的工作是将当前网站的Joomla1.5系统升级为Joomla2.5系统。

在进行升级工作之前,我曾经查阅了很多互联网上的文献,遗憾的是,国内没有一篇完整记录Joomla1.5升级至2.5的技术类文章。想起来还是我的英语帮了我大忙,经过不断的搜索,从Joomla官方网站上找到了Joomla1.5升级为Joomla2.5的权威文章。文章链接如下:http://docs.joomla.org/Migrating_from_Joomla_1.5_to_Joomla_2.5

为了方便不习惯看英文文档的技术ers,我将文章的精髓进行翻译,并补充各种我在实际升级过程中遇到的问题。

Joomla1.5升级至Joomla2.5神功现在开始!

一、升级之前的准备。

升级开始前,以下准备工作是必须的。

  1. 重新判断一下,你的Joomla是否真的要升级?当然,如果你是接单赚钱的话,没有真不真的问题,你肯定要做的。但是如果是你自己的网站,你真的要考虑一下,是否真的有升级的必要?因为升级就肯定存在风险,而且产生很多附带的工作量,对于这两点,你必须做好思想准备。
  2. 你的Joomla1.5是不是最新版本?这里的最新是针对Joomla1.5这个大版本号而言的,Joomla1.5的最新版本是1.5.26。如果你的Joomla1.5不是最新版本,则必须要将Joomla1.5升级至最新版本后才可以进行后面的操作。(原因:我们将会使用JUpgrade进行Joomla的升级工作,而JUpgrade支持Joomla1.5.26到Joomla2.5的升级,很多朋友都抱怨JUpgrade不好用,其实是因为自己的Joomla1.5版本不对)。
    经验之谈:至于如何将Joomla1.5升级至最新版本,请看这篇文章:how to update from Joomla! 1.5.x to the latest version(这篇文章也是英文的,但是内容比较简单,如果大家有问题可以随时联系我,我抽时间会将此文章也翻译一下)。
  3. 当前的Joomla插件是否支持Joomla2.5?这也是个不大不小的问题。某些在Joomla1.5下面安装的插件在升级后是不支持Joomla2.5的。因此先要对插件的情况进行一下了解。最理想的情况是在升级Joomla之前,现在Joomla1.5的环境下将所有插件都升级到最新版本,之后再进行Joomla的升级。
    经验之谈:在升级过程中,插件方面我是比较幸运的。我没有按照要求对插件进行升级,而是先对Joomla进行了升级,升级后发现,诸如“Acymailing”,“Community Builder”等插件是无法正常使用的。但好在这两个插件都有相关的补救办法,具体方法我再后面还会说明。
  4. 你是否更改了Joomla的核心文件?有关“核心文件”的定义并没有确切的界限,简单的说,凡是升级过程中会被新文件覆盖的文件都属于核心文件。这个问题我想大家不要过于纠结,因为从1.5升级至2.5是大版本号的升级,很多核心文件都会被替换,所以如果你确实修改了很多核心文件的话,我建议你还是考虑一下升级的必要性。
  5. 你当前使用的Joomla模版和Joomla语言包是否支持Joomla2.5,这个插件的兼容性基本上属于一个问题。还是那句话,既然进行大版本号的升级,就要做好一切准备。

二、备份、备份、一定要备份

首先还是要说,我的升级之路比较顺利,虽然遇到过问题,但是没有遇到哪些需要重新来过的大问题。但是对于每一个即将进行升级操作的人而言,我还是想各位说一句“一定要备份、备份、在备份”。这里包括数据库和代码的完整备份,如果哪位连怎么备份都不知道的话,我建议您还是不要升级了,您肯定搞不定接下来的事情。

三、开始升级

1. 首先需要下载升级工具jUpgrade。

地址如下:http://extensions.joomla.org/search?q=jupgrade。话说这个工具真是个神作,这里要特别重复一下,这个工具是绝对好用的,如果你使用的时候有异常,多半情况下是没有按照本文第一部分的要求将Joomla1.5升级至最新版本

2. 安装jUpgrade。

安装方法就是Joomla1.5通用扩展安装方法。

2.1 进入你的Joomla1.5后台,然后依次点击Extensions >> Install/Uninstall。
file
2.2 Browse >> Select com_jupgrade >> Upload File & Install
file
2.3 安装成功
file

3. 启动“Mootools Upgrade ”插件。

  • 进入 Extensions | Plugin Manager
  • 查找 "System - Mootools Upgrade"
  • 将此插件开启
    请确保Mootools Upgrade插件已经正确安装并开启, jUpgrade需要依靠此插件才能正常运行。

4. 对JUpgrade进行设置。

截止到本文编写时,JUpgrade的版本号是2.5,支持将Joomla从1.5升级至1.7或者2.5,并附带了众多的升级选项设置。具体设置位置在:Administrator > Components > jUpgrade > Parameters.Global

  • Distribution - 选择要升级到的Joomla版本号,1.7或是2.5
  • Prefix for old database - 当前Joomla网站的数据库表前缀
  • Prefix for new database - 升级后的Joomla网站的数据库表前缀

Skips
Skip checks - 略过升级前的检测
Skip download - 略过下载升级包(注意:当你已经自行下载了升级包,并将升级包放入temp文件夹中,才选择此选项)
Skip decompress - 略过解压缩(注意:当你自行下载升级包后,且将升级包放入temp文件夹中,并自行解压缩后,才选择此选项)

Templates
Keep original positions - 保留原始的目录结构

Debug
Enable Debug - 允许错误调试,当升级出现错误的时候,显示相关的错误信息。

以上信息配置完成后,保存即可。

5. 移植开始。

依次进入:Components >> jUpgrade
file
开始升级
file
file
这个时候要注意不要退出页面,直至所有加载条都完成加载后才可以关闭页面。
file

另一个注意事项,JUpgrade只将Joomla1.5的默认模版进行了升级,因此当你打开升级后的网站时,网站的默认模版是被选中的,需要在后台进行切换才可以。

四、升级之后

  1. 首先先要恭喜你经过了这个惊心动魄的过程。话说我在进行升级的过程中,每点击一个按钮时心都提到嗓子眼了,当发现没有报错,又会暗自狂喜一下。
  2. JUpgrade的升级过程实际上是非常安全的。JUpgrade在升级网站的时候,没有对原来的Joomla网站进行任何的修改,原来的数据库表、源文件还是在那里,JUpgrade实际是在原来网站的根目录里面创建了一个“jupgrade”文件夹,并将升级后的Joomla2.5网站文件保存在了这个文件夹里面。
  3. 依次检查一下下面的这些功能是否还是正常工作:
    Banners
    Categories
    Contacts
    Content
    Menus
    Modules
    Newsfeeds
    Users
  4. 了解了升级原理,并确认升级成功后,下面就可以对原有网站进行清楚了。清楚地方法也很简单,将数据库中Joomla1.5表前缀的数据表全部清除,并将网站根目录中除了jugrade文件夹的源文件全部删除,然后将jugrade文件夹中的文件转移至网站根目录即可。

五、有关“Acymailing”和“Community Builder”升级后的问题。

这两个插件在Joomla从1.5升级至2.5都出现了问题。问题描述和解决办法如下:

1. Acymailing

问题描述:升级后Acymailing在Component菜单下直接消失了。
解决方法:很简单,下载一个最新版的Acymailing插件,然后重新安装即可。可以放心,原来的数据都会保留。

2. Community Builder

问题描述:升级后点击Community Builder菜单后报错。
解决方法:也不难,下载一个最新版的CB,解压缩后按照里面的提示对CB进行升级即可,原来的数据库也会保留。

彻底搞懂npm -g,npm -save,npm -save-dev

1. 回顾 npm install 命令

最近在写Node程序的时候,突然对 npm install 的-save和-save-dev 这两个参数的使用比较混乱。其实博主在这之前对这两个参数的理解也是模糊的,各种查资料和实践后对它们之间的异同点略有理解。遂写下这篇文章避免自己忘记,同时也给node猿友一点指引。

我们在使用 npm install 安装模块的模块的时候 ,一般会使用下面这几种命令形式:

npm install moduleName # 安装模块到项目目录下

npm install -g moduleName # -g 的意思是将模块安装到全局,具体安装到磁盘哪个位置,要看 npm config prefix 的位置。

npm install -save moduleName # -save 的意思是将模块安装到项目目录下,并在package.json文件的dependencies节点写入依赖。

npm install -save-dev moduleName # -save-dev 的意思是将模块安装到项目目录下,并在package.json文件的devDependencies节点写入依赖。

那么问题来了,在项目中我们应该使用四个命令中的哪个呢?这个就要视情况而定了。下面对这四个命令进行对比,看完后你就不再这么问了。

2. npm install moduleName 命令

  1. 安装模块到项目node_modules目录下。
  2. 不会将模块依赖写入package.json的devDependencies或dependencies节点。
  3. 运行 npm install 初始化项目时不会下载模块。

3. npm install -g moduleName 命令

  1. 安装模块到全局,不会在项目node_modules目录中保存模块包。
  2. 不会将模块依赖写入package.json的devDependencies或dependencies 节点。
  3. 运行 npm install 初始化项目时不会下载模块。

4. npm install -save moduleName 命令

  1. 安装模块到项目node_modules目录下。
  2. 将模块依赖写入package.json的dependencies节点。
  3. 运行 npm install 初始化项目时,会将模块下载到项目目录下。
  4. 运行npm install --production或者注明NODE_ENV变量值为production时,会自动下载模块到node_modules目录中。

5. npm install -save-dev moduleName 命令

  1. 安装模块到项目node_modules目录下。
  2. 将模块依赖写入package.json的devDependencies节点。
  3. 运行 npm install 初始化项目时,会将模块下载到项目目录下。
  4. 运行npm install --production或者注明NODE_ENV变量值为production时,不会自动下载模块到node_modules目录中。

6. 总结

devDependencies 节点下的模块是我们在开发时需要用的,比如项目中使用的 gulp ,压缩css、js的模块。这些模块在我们的项目部署后是不需要的,所以我们可以使用 -save-dev 的形式安装。像 express 这些模块是项目运行必备的,应该安装在 dependencies 节点下,所以我们应该使用 -save 的形式安装。

本文转自:https://www.limitcode.com/detail/59a15b1a69e95702e0780249.html

ECMAScript6随学随记

1.let

ES6新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。
可以简单的理解为,var声明的是全局变量,let声明的局部变量。

function f1() {
    var n = 5;
    if (true) {
      var n = 10;
    }
    console.log(n);
  }
  f1();//输出结果为10

  function f2() {
    var n = 5;
    if (true) {
      let n = 10;
    }
    console.log(n);
  }
  f2();//输出结果为5

2. const

const声明一个只读的常量。一旦声明,常量的值就不能改变。
这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。

但对于复合类型的变量,变量名不指向数据,而是指向数据所在的地址。const命令只是保证变量名指向的地址不变,并不保证该地址的数据不变,所以将一个对象声明为常量必须非常小心。

3. 模板字符串

传统的JavaScript语言,输出模板通常是这样写的($('#xxx')是JQuery语法)。

$('#result').append(
  'There are <b>' + basket.count + '</b> ' +
  'items in your basket, ' +
  '<em>' + basket.onSale +
  '</em> are on sale!'
);

上面这种写法相当繁琐不方便,ES6引入了模板字符串解决这个问题。

$('#result').append(`
  There are <b>${basket.count}</b> items
   in your basket, <em>${basket.onSale}</em>
  are on sale!
`);

模板字符串(template string)是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。

// 普通单行字符串
`In JavaScript '\n' is a line-feed.`

// 多行字符串
`In JavaScript this is
 not legal.`

console.log(`string text line 1
string text line 2`);

// 字符串中嵌入变量
var name = "Bob";
var time = "today";

//模板字符串中嵌入变量,需要将变量名写在${}之中。
var nameTime = `Hello ${name}, how are you ${time}?`;
console.log(nameTime);//输出:Hello Bob, how are you today?

//大括号内部可以放入任意的JavaScript表达式,可以进行运算,以及引用对象属性。
var x = 1;
var y = 2;
`${x} + ${y} = ${x + y}`// "1 + 2 = 3"
`${x} + ${y * 2} = ${x + y * 2}`// "1 + 4 = 5"
var obj = {x: 1, y: 2};
`${obj.x + obj.y}`// 3

//模板字符串之中还能调用函数。
function fn() {
  return "Hello World";
}
`foo ${fn()} bar`// foo Hello World bar

4. 标签模板

模板字符串的功能,不仅仅是上面这些。它可以紧跟在一个函数名后面,该函数将被调用来处理这个模板字符串。这被称为“标签模板”功能(tagged template)。

alert`123`
// 等同于
alert(123)

“标签模板”的一个重要应用,就是过滤HTML字符串,防止用户输入恶意内容。

var sender = '<script>alert("abc")</script>'; // 恶意代码
var message = SaferHTML`<p>${sender} has sent you a message.</p>`;//SaferHTML是个自定义函数。

console.log(message);// <p><script>alert("abc")</script> has sent you a message.</p>

5. 对象扩展

ES6允许在对象之中,只写属性名,不写属性值。这时,属性值等于属性名所代表的变量。下面是另一个例子。

function f(x, y) {
  return {x, y};
}
// 等同于
function f(x, y) {
  return {x: x, y: y};
}
f(1, 2) // Object {x: 1, y: 2}

6. Module

6.1 解决Javascript的模块化问题。

早期的Javascript,最让人头痛的就是js代码无法互相引用的问题。这对于大型项目而言就像噩梦一般。
ES6原生了代码引用功能,从根本上解决了js代码模块化的问题。

6.2 export和import语法。

6.2.1 使用export暴露模块。
// profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
function v1() { ... }
export {
  firstName,
  lastName,
  year,
  v1 as streamV1,
};
6.2.2 使用export default暴露模块
export default function foo() {
  console.log('foo');
}
//等同于
function foo() {
  console.log('foo');
}
export default foo;

每个模块只能有一个export default

6.2.3 使用import使用模块。
// 使用export default输出
export default function crc32() {
  // ...
}
// 针对export default的输入语法
import anyName from 'crc32';

// 使用普通export输出
export function crc32() {
  // ...
};
// 针对普通export的输入语法
import {crc32} from 'crc32';

上面代码的两组写法,第一组是使用export default时,对应的import语句不需要使用大括号;第二组是不使用export default时,对应的import语句需要使用大括号。

export default命令用于指定模块的默认输出。显然,一个模块只能有一个默认输出,因此export deault命令只能使用一次。所以,import命令后面才不用加大括号,因为只可能对应一个方法。

本质上,export default就是输出一个叫做default的变量或方法,然后系统允许你为它取任意名字。

6.4 ECMAScript6和CommonJS在模块化上的区别

参考:https://www.w3cschool.cn/ecmascript/ueqp1q5g.html

6.4.1 Node.js不支持ES6的模块语法

做这个比较主要是由于Node.js不支持ES6的import和export功能导致的。说来也奇怪,Node.js已经支持了92%的ES6语法,唯独对ES6这个最重要的模块功能不支持。这一点可以通过安装ES-Checker模块来查看Node.js对ES6的支持:

$ npm install -g es-checker
$ es-checker

结果如下:
file

6.4.2 Node.js使用CommonJS解决模块问题

在Node.js中默认集成了CommonJS,所以一般都用CommonJS来实现模块的输入输出。
CommonJS的模块语法是使用require和exports。
exports示例如下:

//  ./utils/util.js
var textHello = "Hello World!";

function addFun(a,b){
    return a+b;
}
function sayHello(){
    return "Say Hello!";
}

//完整写法:module.exports.在外面被引用的名字 = 模块内的名字
module.exports.textHello = textHello;
//简写:exports.在外面被引用的名字 = 模块内的名字
exports.addFun = addFun;
//在外面被引用的名字和模块内的名字可以不一样
exports.sayH = sayHello;

//上面三个export语句等同于下面一句
module.exports = {
    textHello:textHello,    //完整写法 - 在外面被引用的名字:模块内的名字
    addFun,                 //简写 - 在外面被引用的名字和模块内的名字相同,可以只写一个
    sayH:sayHello           //如在外面被引用的名字与模块内的名字不同,则必须用:方式
}

require示例如下:

// main.js
moduleUtil = require("./utils/util");
console.log(moduleUtil.textHello);
console.log(moduleUtil.addFun(2,3));
console.log(moduleUtil.sayH());
/*以上三行输出:
Hello World!
5
Say Hello!
*/

当然,如果你必须要在Node.js中使用ES6原生的import和export也是可以的,前提是必须借助Babel这类的转换器。

6.4.3 两者的区别

ES6模块加载的机制,与CommonJS模块完全不同。CommonJS模块输出的是一个值的拷贝,而ES6模块输出的是值的引用。

CommonJS模块输出的是被输出值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。

ES6模块的运行机制与CommonJS不一样,它遇到模块加载命令import时,不会去执行模块,而是只生成一个动态的只读引用。等到真的需要用到时,再到模块里面去取值,换句话说,ES6的输入有点像Unix系统的“符号连接”,原始值变了,import输入的值也会跟着变。

7. 属性和方法的简写

var name = "Tom";
var app = {
    name = name,
    run:function(){
        console.log(`${name} is running!`);
    }
}

等同于:

var name = "Tom";
var app = {
    name, //前提是属性名和属性值相同
    run(){
        console.log(`${name} is running!`);
    }
}

8. 箭头函数

箭头函数可以理解为是对匿名函数的一种缩写。但是与匿名函数不同的是,箭头函数里面的this指向的是上下文。

8.1 极简示例。

function (x) {
    return x * x;
}

等同于

(x)=>{return x * x;}

8.2 另一个示例。

setTimeout(function(){
    console.log("Hello!There!");
},1000);

等同于

setTimeout(()=>{
    console.log("Hello!There2!");
},1000);

ECMAScript6入坑

1. 前言

话说自己是从2015年左右从一线Web开发人员转为电商运营的。这段时间由于个人的求知欲不断暴涨,又开始接触Web开发。才发现,2015年真是Web技术的爆发之年,感觉这几年的电商从业让自己和Web开发都有些脱节了。
不过好在之前的Web开发基础打得够深、够牢固(怎么说我也是有着十几年Web开发经验的高材生),现在学习2015年大爆发之后的技术也不会太费力。最重要的是,2015年开始诞生的那些新技术经过5年的沉淀,已经被广泛的支持,有了很好的生态环境,现在学习感觉也不晚。
今天开始入坑ECMAScript6,走起!

2. ECMAScript6入门理解

网上关于ECMAScript6的解释性文章有很多,有兴趣的可以自己去百度里面查找。我对ECMAScript6理解其实就是一句话:Javascript的进化

Web前端技术的三要素分别是:HTML,CSS,Javascript。

  • HTML进化为了HTML5
  • CSS进化为了CSS3
  • 而Javascript则进化为了ECMAScript6

当前很多Javascript衍生技术比如Node.js,Vue.js,微信小程序开发等,都是基于Javascript语言。而对于我这个中间断档了几年的前端开发人员而言,初看Node.js这些技术代码的时候总觉得有些不可思议,后来才发现,Node.js这些技术使用的是ECMAScript6,所以呢,在学些这些新技术之前,把ECMAScript6的基础打好才是最重要的。

3. 一些名词解释

3.1 ES6和ES2015

ES6就是ECMAScript6的简写,由于是2015年正式发布的,也被成为ES2015,所以,ES6和ES2015是一个意思。

3.2 Babel和Traceur

和HTML5、CSS3类似,由于ES6标准的推出时间也不过5年,很多浏览器包括对ES6支持度非常高的Node.js都没有做到对ES6标准的100%支持。如果想要在开发中发挥ES6的全部能量(所有代码完全依照ES6标准编写),就需要Babel这个转码器工具。

Babel和Traceur都是被广泛使用的ES6转码器,可以将ES6代码转为ES5代码,从而在现有环境执行。这意味着,你可以用ES6的方式编写程序,又不用担心现有环境是否支持。

3.3 REPL

在Babel,Node.js等技术中,经常会提到REPL这个词。
REPL是英语:“Read-Eval-Print Loop”的缩写,简单的来说,就是一个简单的基于cmd(或者Linux的shell)命令行工具。你可以把简单的代码通过cmd输入进REPL,REPL则会输出代码运行结果。这个类工具一般只适合进行简单的代码块验证。