2019年 前端总结一【转藏】

作者:前端小胖

===========================
三阶段大纲:

  1. nodejs
    给jquery|vue|react|小程序 搭建后端接口
  2. vue 基础
  3. vue 项目 + 高级
  4. vue 项目 + 扩展
  5. react 基础
  6. react 项目 + 高级 + 扩展
  7. react 项目 + 混合开发(codova,jssdk,公众号,ReactNative)
  8. 小程序 (原生|mpvue|taro) + 项目
  9. 模拟面试/项目答辩/面试课

就业周的安排:
1 模拟面试/项目答辩/面试课(就业老师|完善简历)
模拟面试(所有老师): 准备纸质简历-》办公室|会议室
项目答辩(项目经理): 教室
线上项目 2
兴趣DEMO
2 同上
3 面试
4 offter
5 人找不到了

=============================
预习视频概览:

本地存储(localStorage,sessionStorage,)
多媒体(video,audio api,base64,es6/7类加强)
绘图(canvas,svg,游戏思想)
echarts(图形可视化,图表库)
移动端功能适配,原生移动端js(touchstart,touchcancel,touchmove,touchend,tap)
zepto、touchJs(移动端库,手势库)
iscroll、swiper、animate.css(上拉加载,下拉刷新,轮播图库,css动画库,微场景)
第三方地图库/geolocation(各种地图api调用,原生地理定位实现)

本地存储
1. cookie 存东西
特点: 小(~4k),服务器环境(每次都会随着请求,发往服务器),过期时间,完全兼容
document.cookie=值
2. localStorage 、 sessionStorage
特点: 大(~5M),不会发往服务器,没有过期时间
共同点:
不安全、不能跨域、不能跨浏览器
=============================
localStorage: window的属性 返回 一个localStorage对象

属性:
    localStorage.key=value 种、写
    localStorage.key;  获取,读
    delete localStorage.key 删除
    for in  localStorage 枚举 批量删除
方法: √
    localStorage.setItem(key,value) 种、写
    localStorage.getItem(key) 读
    localStorage.removeItem(key) 删除一个
    localStorage.clear() 批量删除
事件:
    window.onstorage 检测key 发生了变化  当前页面无法检测到
        ev.key 返回 变化的key
        ev.newValue 变化后的值
        ev.oldValue 前
写到localStorage里面的都是字符
    json    ->  str 序列化
    str     ->  json 反序列化,实体化
sessionStorage 生命周期 会话结束时
    属性:同上
    方法:同上

补充
图片站位:http://temp.im/320x150
document.querySelector('css选择器'); 一个
document.querySelectorAll('css选择器') 一组,多个,一堆,一坨

obj.dataset.属性 取、设置data-属性的值  返回 string
<xx bulala="sdf"></..> obj.getAttribute('bullaa')
<xx data-a-bac-cdd="xxx">
obj.dataset.aBacCdd 获取

多媒体:
视频
PC:
50% H5 、 50% flash/player播放 actionscript3.0 OOP 类java as cc挂了
touch:
video
video:
dom属性:
src:'视频源'
autoplay="autoplay" -> autoplay 自动播放
controls 是否出现控制句柄 true/false controls="controls"
poster 预览图
loop 循环
....

js属性:
    autoplay:true/false
    v.currentTime 磁头位置,播放时间 读写
    v.volume 音量 0 - 1
    v.muted 是否静音  true/false
    v.playbackRate 返回 播放速率 默认 1  0-8
    v.duration 总时间(长度)
    v.autoplay   true/false
    v.controls   true/false
    v.loop       true/false
    ......
方法:
    v.play() 播放
    v.pause() 暂停
    v.webkitRequestFullScreen();
    ...
事件:
    v.ontimeupdate=fn  播放时
    v.onended=fn 播放完毕时

参考: w3c.org/video
           

source:
    提供多视频源

需求:自定义控制器句柄(DOM模拟)

Audio:
autoplay 问题:
Chrome的autoplay政策做了更改。
新的行为:浏览器为了提高用户体验,减少数据消耗,规则如下
1. muted autoplay始终被允许
2. 音乐的autoplay 只有在下面集中情况下起作用:
1. 有用户行为发生像(click,tap...)
2. 对于桌面程序,用户已经提前播放了音频
3. 对于移动端用户将音频网址home screen.
解决:
Open chrome://flags/#autoplay-policy
Setting No user gesture is required
Relaunch Chrome

es5:
    构造函数: function 函数名(){
        this.属性,
        this.init()自动初始化
    }
    方法:函数名.prototype.方法名=fn(){}

es6
    class 类名 extends 父类{
        constructor(){
            super()
            this.属性=值
            ...
        }

        方法名(){}
        ....
    }

    类名.方法=fn;
    类名.属性=fn;

es7:

    class 类名 extends 父类{

        public|private|pro...  属性=值

        readonly 属性=值

        constructor(){
            super()
            初始化实例属性
        }

        get 属性():number{}
        set 属性(val:string){}

        方法名(){}
        ....
    }

    类名.方法=fn;
    类名.属性=fn;


    实例:
        {
            属性:xx
            data:xx 数据
            btn1:xx 顺序播放  可选
            btn2:xx 随机播放
            btn3:xx 顺序播放
            btn4:xx 列表循环
            now:xx  当前播放索引
            audio:xx 播放器
            state:xx 播放状态  (顺序播放...)
            ...
            _proto_:{
                方法:fn
                init(){} 渲染DOM,添加各种事件
                playMusic(){} 播放
                clearAllState(){}
                rnd(){}  工具方法 应该封装公共库
                ...
            }
        }

===============================================

CANVAS:
干嘛的:画图的
目标: 做效果、画图表(柱状图,饼图..)、小游戏
特点: 没有事件系统

canvas: 是个标签、天生透明、提供画布、绘制靠js、宽高需要DOM属性来指定
    兼容: IE9+
画笔:
    o2d = canvas.getContext('2d');
    o2d==画笔 、特点:所有和绘制相关属性、方法、都在画笔o2d身上

    属性:
        o2d.strokeStyle='rgba(255,34,24,1)';描边色
        o2d.fillStyle='blue';填充色
        o2d.lineWidth=10;线条粗细
        o2d.font='字体大小 字体'
        o2d.lineCap='butt' round square 端点样式
        o2d.lineJoin='miter';//bevel 斜切 round  接洽点样式
        o2d.shadowOffsetX=number x 方向偏移
        o2d.shadowOffsetY=number y 方向偏移
        o2d.shadowBlur=number 模糊、失焦、扩散
        o2d.shadowColor='blue/#xxxxx/rgb/rgba'
        o2d.textAlign='right';//left|center|right  文字水平对齐 默认left
        o2d.textBaseline='bottom';//top|middle|bottom 文字垂直对齐 默认文本基线
    方法:
        o2d.moveTo(x,y) 移动到
        o2d.lineTo(x,y) 连线到
        o2d.closePath();闭合路径  绘制在最后一笔
        o2d.fill() 填充  使用fillStyle的颜色,默认是黑色
        o2d.stroke() 描边 使用strokeStyle的颜色,默认是黑色
        o2d.beginPath();    开启新路径
        o2d.strokeRect(x,y,w,h) 描边矩形
        o2d.fillRect(x,y,w,h) 填充矩形  绘制的是图形
        o2d.rect(x,y,w,h) 路径矩形 需要描边才看的到
        o2d.clearRect(x,y,w,h) 擦除操作
        o2d.arc(cx,cy,r,start,end,false); 路径  start=开始弧度 false 是否顺逆 是个路径
        o2d.fillText(string,x,y) 填充文字
        o2d.strokeText(string,x,y) 描边文字
        gd.measureText('九叔').width  返回文字宽
        o2d.isPointInPath(x,y) 返回 true/false  只检测路径
        o2d.save() 保存当前画笔状态
        o2d.restore() 回复上一次保存的画笔状态
        o2d.translate(x,y) 平移
        o2d.rotate(弧度值) 旋转
        o2d.scale(x,y)  默认1  必须两个值都要写2017-8-15


绘制流程
    1.  选色、线条样式、变形 -> 配置画笔
    beginPath/save
    2.  moveTo/lineTo/drawImage...  -> 绘制
    closePath/restore
    3.  fill/stroke -> 填充、描边  -> 着色
动画: 定时器(setInterval,requestAnimationFrame)
        帧频:
            低: 30    DOM渲染
            高:  16~17   1000/60     60fps  canvas渲染

效果:
        分析:线在动(点构成)->点(需要数据)在动
        运动:定时器->画点->点.速度(数据取)->连线->边缘碰撞检测
        拖尾:oldArr 控制length oldArr.push(aPoint)
        变色: 作业
事件:
    没有事件系统,事件添加给画布,画笔没有事件系统
    添加canvas,手动检测区域

        矩形:ev.clientX>x && ev.clientX<x+w && ev.clientY>y && ev.clientY<y+h
        圆:
            cx2=clientX   cx1= x坐标
            a=cx1-cx2
            b=cx2-cy2
            c=Math.sqrt(a*a+b*b);
            if(c<r){检测}
        异形:
            规则形状 || 规则形状

    自动检测
            o2d/gd.isPointInPath(x,y) 返回 true/false 一个点是否在意个路径范围内

    变形: 操作的是画笔  (中心点都在画布左上角)
        平移: 效果累加
            o2d/gd.translate(x,y)
        旋转: 不会累加
            o2d/gd.rotate(弧度值) -> d2a
            旋转是画笔(中心点画布左上角)
            需求:中心点在物体中心
                理解:物体+中心点绘制画布左上角->旋转/缩放->平移目标位置
                t书写:ranslate(w/2+x,h/2+y)->rotate(弧度)->fillRect(-w/2,-h/2);
        缩放:
            o2d/gd.scale(x,y) 俩个值都必须写
        ....

    作业:
        屏保:要有颜色变化
        心动邮箱
        canvas绘制米老鼠

画笔:
    o2d = canvas.getContext('2d');
    o2d==画笔 、特点:所有和绘制相关属性、方法、都在画笔o2d身上

    属性:
        o2d.strokeStyle='rgba(255,34,24,1)';描边色
        o2d.fillStyle='blue';填充色  blue|#fff|rgb|rgba|createPattern(图)
        o2d.lineWidth=10;线条粗细
        o2d.font='字体大小 字体'
        o2d.lineCap='butt' round square 端点样式
        o2d.lineJoin='miter';//bevel 斜切 round  接洽点样式
        o2d.shadowOffsetX=number x 方向偏移
        o2d.shadowOffsetY=number y 方向偏移
        o2d.shadowBlur=number 模糊、失焦、扩散
        o2d.shadowColor='blue/#xxxxx/rgb/rgba'
        o2d.textAlign='right';//left|center|right  文字水平对齐 默认left
        o2d.textBaseline='bottom';//top|middle|bottom 文字垂直对齐 默认文本基线
    方法:
        o2d.moveTo(x,y) 移动到
        o2d.lineTo(x,y) 连线到
        o2d.closePath();闭合路径  绘制在最后一笔
        o2d.fill() 填充  使用fillStyle的颜色,默认是黑色
        o2d.stroke() 描边 使用strokeStyle的颜色,默认是黑色
        o2d.beginPath();    开启新路径
        o2d.strokeRect(x,y,w,h) 描边矩形
        o2d.fillRect(x,y,w,h) 填充矩形  绘制的是图形
        o2d.rect(x,y,w,h) 路径矩形 需要描边才看的到
        o2d.clearRect(x,y,w,h) 擦除操作
        o2d.arc(cx,cy,r,start,end,false); 路径  start=开始弧度 false 是否顺逆 是个路径
        o2d.fillText(string,x,y) 填充文字
        o2d.strokeText(string,x,y) 描边文字
        gd.measureText('九叔').width  返回文字宽
        o2d.isPointInPath(x,y) 返回 true/false  只检测路径
        o2d.save() 保存当前画笔状态
        o2d.restore() 回复上一次保存的画笔状态
        o2d.translate(x,y) 平移
        o2d.rotate(弧度值) 旋转
        o2d.scale(x,y)  默认1
        oC.toDataURL('类型') 图片导出 格式base64

绘制流程
    1.  选色、线条样式、变形 -> 配置画笔
    beginPath/save
    2.  moveTo/lineTo/drawImage...  -> 绘制
    closePath/restore
    3.  fill/stroke -> 填充、描边  -> 着色

图片操作:
    canvas -画布导出-> 图片
    图形填充  - 平铺背景
    图片画笔  - game

canvas导出  格式base64
    oC.toDataURL('类型') image/png image/jpeg image/gif
    返回: base64

图片填充
    提供图片(资源)->加载完毕->使用图片
    var pat = o2d.createPattern(图片对象|canvas|视频对象,平铺方式)
        平铺方式: string    =  'repeat|no-repeat|repeat-x|repeat-y'
        返回值: pattern 对象  可以作为xxxStyle的值

图片绘制:(画笔状态)
    gd.drawImage(图片对象,x,y)  x y 绘制到的画布位置
    gd.drawImage(图片对象,x,y,w,h)  w h 画多大
    gd.drawImage(图片对象,sx,sy,sw,sh,dx,dy,dw,dh)
        s = source 原图   位置 宽高
        d = destination 目标(画布)画在哪,画多大

游戏:
    产品设计/策划:
        故事脚本,角色形象
    UI设计:
        形象
    开发:
        OOP
        1. 资源加载
           图片批量加载,进度 已加载(onload)/要加载总数
        2. 角色抽象  类  实例:
            鱼
                属性:type x y rotate speed cur
                方法: move draw
            炮筒
                属性: type x y rotate cur timer
                方法: draw emit
            炮弹
                属性: type x y rotate  timer speed
                方法: draw move clear
            金币
                属性:type x y timer
                方法: draw move clear
            渔网
            记分牌
            ..
        3. 业务

    作业:
        捕鱼:
            1. 网的类型
            2. 积分牌
            3. += 换炮

echarts:
干嘛的:画图表的(直线图、曲线图、区域图、柱状图、饼状图、散状点图、仪表图、气泡图、瀑布流图)
echarts: √ canvas 兼容一般 ZRender(渲染) 百度产品
hcharts: svg/vml 兼容性不错
官网:http://echarts.baidu.com/
学习一个库:
0. 下载库(cdn、本地、模块化安装+引入)
1. 快速上手/教程/quick start/... 概览
2. 查文档API(根据需求) document/doc/API/文档....
属性:options{属性:xx}
方法:
事件:
...
3. 工作、找实例、改装
4. 社区、圈子(群)
使用:
1. 使用全局暴露对象
2. 实例化/初始化 目的:生成实例
3. 使用实例.方法()/属性
4. 传递、设置数据(option)

    option  结构关系 看配置选项

touch端 事件js
移动端测试:
1) 模拟器 F12 不靠谱 70% 模拟软件安装
2) 真机
a) 页面 传 外网(github duapp) -> 生成网址 ->生成二维码
b) 本机虚拟服务器(localhost) - > 手机安装对应APP
条件: PC 和手机,要在同一个局域网段
同一个局域网段: 同一个路由
移动端:
obj.on事件名=函数 大部分可用

    onmousedown/onmouseup/onmousemove 没有的
                    ↓
    ontouchstart/ontouchend/ontouchmove
        事件对象描述的是手指信息
        ev.targetTouches   返回 所有手指信息

    推荐:obj.addEventListener('事件',fn,false)

zepto.js

官网:http://zeptojs.com/
移动版的jquery,小库,完成移动的功能

css3能代替的,都没有封装,

默认功能:
zepto   核心方法模块、css attr....
event   基本事件  on() & off()
ajax    XMLHttpRequest and JSONP 交互
form    表单数据交互
ie      pc 机    支持ie10

需要下载的   模块  (组件)    先引入zepto
    fx 动画模块
    fx_methods      动画方法
    selector    扩展选择器
    ....

百度 touchjs
好处 事件全(触摸事件+手势), 模拟器真机效果同步
官网:http://touch.code.baidu.com/
API: http://cloudajs.org/docs/step4_API_Documentation#h2_7

事件添加
    事件绑定
    touch.on( 元素, 事件类型, 回调 );
            oLi/'li'

    事件代理
    touch.on( 事件代理元素DOM或选择器, 事件类型, 代理子元素选择器, 回调 );
                oUl/'ul'                ..          'li'
事件类型:
    缩放: pinchXX
    旋转:     rotateXX
    滑动: swipeXX
    敲、按: tap、doubletap、hold(长按)
    ....

hammerjs

https://hammerjs.github.io/

iScroll
干嘛的:模拟系统滚动
应用场景:非框架情况下,做上拉加载,下拉刷新
官网: http://iscrolljs.com/
中文: http://www.cnblogs.com/leolai/articles/4204345.html
版本:
iscroll.js 基础版 两端
iscroll-lite.js 移动端
iscroll-probe.js 专有 做上拉加载,下拉刷新
iscroll-infinite.js 专有
使用流程:快速上手/读API/例子/社区、圈子
1. new 实例化({配置})/全局对象.init()
var 实例 = new IScroll('滚动容器',{配置})
2.
布局:
滚动容器>内容
#wrapper> scroller/content
配置: options
myScroll=new IScroll('滚动容器',{配置})

            {
                属性:值
            }
        属性:
            实例.属性;获取 实例.属性=值 设置
        方法:
            myScroll实例.方法()
        事件:
            myScroll.on('事件',fn)

    3.

swiper 4
干嘛的:轮播图,非框架情况下使用
官网:http://www.swiper.com.cn/
目标:轮播图、微场景、视差滚动、无限滚动

使用:
    实例 = new Swiper ('.swiper-container', {配置})
    配置:
        {
            属性、变量:值
        }
    实例.属性
    实例.方法

    事件:
    实例.on('事件',fn)
    配置:
        {
            事件名:fn
        }

animate.css:
animate.css 动画库 纯css实现
https://daneden.github.io/animate.css/

    使用:给dom元素提交class
    基本样式 animated 必须
    可选 infinite  无限循环
    运动形式:
        bounce/bounceIn/bounceOut/bounceInLeft/bounceOutRight
                入退场             方向

微场景:
1) 使用swiper 官网提供方案 此插件不适用于loop模式
预先引入swiper基础css和js
引入css/js插件
animate.css/swiper.animate.js
读:http://www.swiper.com.cn/usage/animate/index.html
场景元素运动时机:默认 onSlideChangeEnd 可修改js

2)  css实现       默认切换开始时,给要切换到的silder添加swiper-slide-active
    预先引入swiper基础css和js
    a) transtion
        设定入场前、入场后的位置
    b) animation
        入场前的动作keyframes里面完成
    c) animate库 引入animate.css

3) 第三方开发的UI工具 (策划市场) 花钱 没法定制
   兔展、易企秀、   参考(定风格,画风,动画节奏)

地理定位:

百度|qq|高德|

    http://api.map.baidu.com/api?v=2.0&ak=0933b0c4da0af91731e5dd9ffbc58511

nodeJs
介绍
干嘛的: 写后台管理程序
目标:数据服务,文件服务,web服务
类似: php .net java(jsp) ....
优势:
性能高,方便、入门难度低、大公司都在用(BAT)
劣势:
服务器提供的相对较少
能用的上的学习资料少,对程序员的要求高了
环境:nodejs + web服务器 + 数据库
nodejs:
安装:
官网:https://nodejs.org/en/ | http://nodejs.cn/
镜像:http://npm.taobao.org/
nodejs 环境 npm 环境
测试环境: 命令行(运行->cmd)->node -v
版本:
Vx(主).x(子).x(修正) 包(目录)->模块(文件)
主版本: 变化了,1/3的API发生巨变 , 使用方式变化了
子: API没有删减,使用方式没变化,内部实现发生了变化
修正版:什么都没变,处理一下bug

    V6.8.x   稳定
    V6.9.x 非稳定版
    Vx.x.x-beta 测试
    vx.x.x-rc  测试稳定
    vx.x.x-alpha  测试稳定

编写:
    IDE(有RUN环境,真实服务器)
        Hbuild / webstorm / 
    编辑器
        atom / vscode / sublime text / 
    node命令行

运行:
    1  dos:     win+r->cmd回车->cd 目录-> node 文件名.js | node 文件名
                webstrom->terminal(ALT+f12)  | run
                vscode->终端
                当前目录->右键->git bash-> node 文件名
    2  linux/centos(git):  
                终端->cd 目录-> node 文件名.js | node 文件名
node命令行:  
    dos-> node 回车-> 编写+运行
    测试API的可用度
    
    注意:
        DOM/BOM 不可用
        ECMA 可用

web服务
    web服务器: apache , ngnix , tomcat   localhost -> www.abc.com
    数据库: mysql | sqlserver | mongoDB | orangcle
        数据库: 数字|字符
        磁盘(硬盘) 文件本身(图,视频,PDF)   文件服务器
    后台管理程序:
        nodejs,java........
前后端交互流程
    大后端:
        用户 - > 地址栏(http[s]请求) -> web服务器(收到) - > nodejs处理请求(返回静态、动态)->请求数据库服务(返回结果)->nodejs(接收)->渲染页面->浏览器(接收页面,完成最终渲染)
    大前端:
        前端 - > http[s]请求 -> web服务器(收到) - > nodejs处理请求(返回静态、动态)->请求数据库服务(返回结果)->nodejs(接收)->返回给前端(渲染)->浏览器(接收页面,完成最终渲染)

nodejs搭建web服务器:  使用HTTP模块

    1) 引入http模块 require('http')
    2)  server/app = http.createServer(函数(req,res));//创建服务   返回http对象
            req 请求  浏览器->服务器
                req.url  地址   提取get数据
                req.on('data|end') 提取post数据 所有的http[s]都会触发end事件
            res 响应  服务器->浏览器
                响应头设置:  res.writeHead(200,{'Content-Type':'text/html;charset=utf-8'});
                res.write(字符/数据<string><buffer>)
                res.end() 结束响应

    3)  监听:
        server.listen(端口,[地址],[回调])    回调:监听成功,回调一次
            端口: 1-65535 1024以下系统占用   80
            地址: 虚拟localhost  真实域名xx.duapp.com
    小提示:
        更新后,需要每次服务器自动重启
        推荐: supervisor | nodemon   命令行工具
        npm install supervisor -g  | npm install nodemon -g

静态资源托管:
    什么是静态资源: css/html/js/图片/json/字体..
    
    前端资源请求:
        href/src/url()/locaction.href

    后端资源读取:
        fs.readFile(文件名,[编码],回调(err,data));
            err 错误 null没有错误
            data 数据,buffer流

动态数据请求: 
    前台: get/post/put/delete/ajax/jsonp.....
    后台:http[s] 请求 , 处理方式方式
        address:    req.url  抓取 get请求的数据  字符 切/url模块
        body:   req.on('data',(chunk)=>{CHUNK==每次收到的数据buffer})
                req.on('end',()=>{  接收完毕 字符 切/querystring })
    推荐: postman https://www.getpostman.com/downloads/


url模块   
    作用: 处理 url
    url.parse(str,true)  str -> obj  返回 对象  true 处理query->obj

    obj参数       http://localhost:8002/aaa?username=sdfsdf&content=234234#title4
      protocol: 'http:',    协议
      slashes: true,    双斜杠
      auth: null,   作者
      host: 'localhost:8002',  主机 www.baidu.com
      port: '8002', 端口
      hostname: 'localhost',  baidu
      hash: '#title',   哈希(锚)
      search: '?username=sdfsdf&content=234234',    数据
      query: 'username=sdfsdf&content=234234',  数据
      pathname: '/aaa', 文件路径
      path: '/aaa?username=sdfsdf&content=234234',  文件路径
      href: 'http://localhost:8002/aaa?username=sdfsdf&content=234234#title'

    url.format(obj)  obj -> str   返回str

querystring 模块 
    作用: 处理查询字符串(?key=value&key2=value2)
    querystring.parse(str) -> obj
    querystring.stringify(obj) -> str

------------------------------------------------------------------------------

模块化:

    commonJS
        是主要为了JS在后端的表现制定
        commonJS 是个规范 nodejs / webpack 是一个实现
        ECMA 是个规范  js / as 实现了

    服务器端JS: 相同的代码需要多次执行|CPU和内存资源是瓶颈|加载时从磁盘中加载
    浏览器段js: 代码需要从一个服务器端分发到多个客户端执行|带宽是瓶颈|通过网络加载
    模块: http/fs/querystring/url     require('模块名')  系统模块
    模块化:require module exports      seajs.js / require.js   CMD/AMD/UMD   es5

    require 引入模块、输入  对象|函数|类
        require('模块名')
            不指定路径:  先找系统模块-> 再从项目环境找node_modules|bower_components (依赖模块)->not found
            指定路径      : 指定路径 -> not found
        require(./utils).xx  按需引用

    exports 导出,批量输出 都是属性
        exports.自定义属性=值(any) 输出带接口      require(模块文件名)=modA  modA是个模块实例{自定义属性}

    module  默认输出 函数|类|对象 只能输出一次
        module.exports = {  
            自定义属性:值
        }

        module.exports=对象/fn/class              require('..')=modA  modA 是一个fn或者是类class本身

    commonJS 是 nodejs 默认模块管理方式,不支持es6的模块化管理方式,但支持所有es6+语法

        解决: 
            编写: xx.mjs
            运行:  node --experimental-modules ./xx.mjs

NPM:
    干嘛的:帮助你安装模块(包),自动安装依赖,管理包(增,删,更新,项目所有包)
    类似: bower       yarn    (https://yarn.bootcss.com/)

    全局:任何目录c/d/e,
        工具命令行,脚手架
    项目环境:   只能在当前目录使用

    项目依赖dependencies: 只能在当前项目下使用,上线了,也需要这个依赖  --save
    开发依赖devDependencies:只能在当前项目下使用  ,上线了,依赖不需要了 --save-dev

    安装、卸载到全局
    npm i 包名 -g         g==golbal           yarn add 包名 |  bower install 包名
    npm uninstall 包名 -g                     yarn remove 包名 | bower uninstall 包名

    安装到项目环境

    npm install 包名 --save           |   -S
    npm install 包名 --save-dev     |  -D

    查看已安装:      npm list  列出所有已装包
                    npm outdated 版本对比(安装过得包)
                    npm info 包名 查看当前包概要信息 
                    npm view 包名 versions 查看包历史版本列表
    管理模块(项目):
        package.json的name的名字需要和项目目录名一致,不要和依赖的包重名
        npm init                初始化npm管理文件(package.json)
            {
              "name": "npm",    项目名称
              "version": "0.0.1",   版本
              "description": "test and play",   描述
              "main": "index.js", 入口文件
              "dependencies": {  项目依赖  上线也要用
                "jquery": "^3.2.1"
              },
              "devDependencies": { 开发依赖 上线就不用
                "animate.css": "^3.5.2"
              },
              "scripts": {  命令行
                "test": "命令行",
              },
              "repository": {   仓库信息
                "type": "git",
                "url": "git+https://github.com/alexwa9.github.io/2017-8-28.git"
              },
              "keywords": [  //关键词
                "test",'xx','oo'
              ],
              "author": "wan9",
              "license": "ISC", 认证
              "bugs": {
                "url": "https://github.com/alexwa9.github.io/2017-8-28/issues"
              },
              "homepage": "https://github.com/alexwa9.github.io/2017-8-28#readme"
            }
    安装到项目依赖   以后上线任然需要的包   √
        npm install 包名 --save  /  -S
    安装到开发依赖   上线后不需要的包
        npm install 包名 --save-dev /  -D

    安装package.json里面指定的所有包:
        npm install

    版本约束:
        ^x.x.x   约束主版本,后续找最新
        ~x.x.x   保持前两位不变,后续找最新
        *        装最新
        x.x.x    定死了一个版本
        
    选择源
        npm install nrm -g     安装选择源的工具包

        nrm ls 查看所有源
        nrm test 测试所有源
        nrm use 源名
        
    模块下载    (卡顿)   超过5分钟 | 报错 
        
        ctrl + c -> npm uninstall 包名  -> npm cache 清除缓存 -> 换4g网络 npm install 包名

    发布模块:
        注册账号    npmjs.com
        登录:
            npm login 登录到npmjs.com
                输入 user/password/email
        创建包目录->npm init -y -> 创建入口index.js -> 编写,输出-> npm publish发布

        发布:npm publish
        删除:npm unpublish

        发布操作长时间没有做,邮箱需要确认,收取激活邮件
------------------------------------------------------------------------------
express
    express  库
        http://www.expressjs.com.cn/
        干嘛的:nodejs库,不用基础做起,工作简单化
        类似:koa
    express特点:
        二次封装,非侵入式,增强形
    express搭建服务
        express=require('express')
        server=express()
        server.listen(端口,地址,回调)
    静态页面托管
        express.static('./wwww')
        server.use(express.static('./wwww'));

    接口响应:
        各种请求姿势: get/post/put/delete/....
            server.请求姿势API(地址,处理函数)

            server.get(url,(req,res,next)=>{})
            server.post(url,(req,res,next)=>{})
            
            jsonp请求 == get请求 
                server.get('/jsonp接口',(req,res,next)=>res.jsonp(json)
                设置回调函数的key: server.set('jsonp callback name', '设定回调函数键');  默认callback

        参数接受: req == 请求体
            req.query 获取地址栏的数据
            req.body 获取非地址栏的数据  依赖中间件
                中间件使用:body-parser  1. npm install body-parser  2. require('body-parser')   3. app.use(中间件())
            req.params 获取动态接口名
            req.method 获取前端提交方式
        
    发送给浏览器: res == 响应体
        任何类型: res.send(any) ~~ res.write + end
        JSON: res.json(json)
        jsonp: res.jsonp(响应数据) 响应数据-》jsonp请求时的回调函数
        404 : res.status(404).send({error:1,msg:"Sorry can't find that!"})
        静态页面: res.sendFile(path.resolve('public/error.html'))//渲染纯 HTML 文件
            
    后端跳转:   res.redirect(url)
        
    处理一部分接口 共有业务逻辑:

        server.all('/admin/*',fn) all匹配全路径 处理所有HTTP 需要next 延续后续

    use: 安装中间件 | 路由
        server.use(地址,中间件|路由|函数体)

    中间件(middleware): 不处理业务,只处理请求   到     结束响应  的中间部分
            body-parser
            中间件:    npmjs.com   查看使用方式
            body-parser         获取post数据,限定大小,约定返回数据类xx.urlencode({limit:xx})

    扩展:

        Request 对象 - request 对象表示 HTTP 请求,包含了请求查询字符串,参数,内容,HTTP 头部等属性。常见属性有:

            req.app:当callback为外部文件时,用req.app访问express的实例
            req.baseUrl:获取路由当前安装的URL路径
            req.cookies:Cookies
            req.fresh / req.stale:判断请求是否还「新鲜」
            req.hostname / req.ip:获取主机名和IP地址
            req.originalUrl:获取原始请求URL
            req.path:获取请求路径
            req.protocol:获取协议类型
            req.route:获取当前匹配的路由
            req.subdomains:获取子域名
            req.accepts():检查可接受的请求的文档类型
            req.acceptsCharsets / req.acceptsEncodings / req.acceptsLanguages:返回指定字符集的第一个可接受字符编码
            req.get():获取指定的HTTP请求头
            req.is():判断请求头Content-Type的MIME类型

        Response 对象 - response 对象表示 HTTP 响应,即在接收到请求时向客户端发送的 HTTP 响应数据。常见属性有:

            res.app:同req.app一样
            res.append():追加指定HTTP头
            res.set()在res.append()后将重置之前设置的头
            res.cookie(name,value [,option]):设置Cookie
                opition: domain / expires / httpOnly / maxAge / path / secure / signed
            res.clearCookie():清除Cookie
            res.download():传送指定路径的文件
            res.get():返回指定的HTTP头
            res.location():只设置响应的Location HTTP头,不设置状态码或者close response
            res.render(view,[locals],callback):渲染一个view,同时向callback传递渲染后的字符串,如果在渲染过程中有错误发生next(err)将会被自动调用。callback将会被传入一个可能发生的错误以及渲染后的页面,这样就不会自动输出了。
            res.sendFile(path [,options] [,fn]):传送指定路径的文件 -会自动根据文件extension设定Content-Type
            res.set():设置HTTP头,传入object可以一次设置多个头
            res.status():设置HTTP状态码
            res.type():设置Content-Type的MIME类型

----------------------------------------------------------------
cookie-session
    用户和服务器交互通过什么?   http协议,http是无状态的
        通过IP(公司环境)
        通过浏览器保存一些信息,每次访问服务器带过去
            如何保存信息给浏览器:
                1. 前端种cookie/localstorage
                2. 后端种: 前端请求后端,后端给前端种cookie(加了密)的同时,在服务器上生成seesion
        服务器给浏览器种只种cookie:  cookie-parser
        服务器给浏览器种cookie的同时在服务器上生成seesion:  cookie-session

安装+引入
    种:  req.session.key=value  
    读: req.session.key
    删:  delete req.seesion.key | req.session.key = undefined/null
----------------------------------------------------------------
multer:  文件上传
    multer->文件名会随机->fs模块改名->path系统模块解析磁盘路径

    文件上传:前端表单->后端接收到文件本身->保存到服务器上->给数据库记录文件一些信息->库返回给nodejs相关信息->nodejs返回给前端
            前端: <input type=file enctype="multipart/form-data" name="fieldname"
            后端:multer 接受 form-data编码数据 

        配置
        let multer  = require('multer');    引入
        let objMulter = multer({ dest: './upload' });   实例化  返回 multer对象
            dest: 指定 保存位置(存到服务器)
        app.use(objMulter.any());   any 允许上传任何文件

        req.files   数组
            fieldname: 表单name名
            originalname: 上传的文件名
            encoding: 编码方式
            mimetype: 文件类型
            buffer: 文件本身
            size:尺寸
            destination: 保存路径
            filename: 保存后的文件名  不含后缀
            path:   保存磁盘路径+保存后的文件名 不含后缀
    fs模块:  操作是异步的
        fs.rename('当前文件','该后的文件名',(err)=>{});

--------------------------------------------------------------
path 系统模块
    磁盘路径:
        编码:
            windows:  'c:\\user\\admin\\a.jpg'
            mac: ~/desktop/1901
        UI: 
            windows: c:\user\admin
            mac: ~/desktop/1901

        path.parse('c:\\wamp\\xx.png'); 磁盘路径(str -> obj)
            {
               root: 'c:\\', 盘符
               dir: 'c:\\wamp', 目录
               base: 'xx.png',  文件名
               ext: '.png', 扩展名
               name: 'xx'   文件,不含扩展名
            }
        path.join('磁盘路径1','磁盘路径2')
            __dirname 魔术变量  返回当前文件所在的磁盘路径


        path.dirname == __dirname   当前文件位置

        path.resolve('磁盘路径1','磁盘路径n') 合并磁盘片段,右到左找根,左到右拼接,没有找到根,以当前文件路径为根
--------------------------------------------------------------

渲染页面(模板引擎):
    前端:dom操作                虚拟dom操作 二次渲染,后期多次渲染, 优点:局部渲染
          jq/js/angualrJs       vue/react/.......   渲染页面(数据整合到静态页面)
    后端:
          抓取前端静态页面 + 渲染引擎 + 数据  返回data ->  send(data)
          渲染引擎: jade / ejs / ....

    arttemplate  underscore baiduTemplate mustach .......

    -------------------------------------------------------------
    jade|pug: 库
        侵入式,强依赖
        jade.render('html'); 返回字符
        jade.renderFile('jade模板文件',{数据},{pretty:true}); 返回字符
        jade模板语法
            父子要缩进
            属性:  标签(key=value,key2=value)
            内容: 标签 内容
    -------------------------------------------------------------
    ejs:    模板渲染是异步的
        非侵入式,温和,弱依赖

        ejs.render(str)  返回  str
        ejs.renderFile('ejs模板',{数据},回调(err,data))   data == str

        ejs模板语法:
            ejs 结构就是html
            输出: <%= 数据名|属性名|变量名 + 表达式 %>
            语句: <% 语句 %>  需要被<%  %>  包裹
            非转义输出:  <%- 数据名|变量名  + 表达式 %>

            https://www.npmjs.com/package/ejs
    -------------------------------------------------------------
    consolidate 管理多个模板引擎    consolidate
        安装: npm i consolidate -S
        注意: ejs 需要安装,但无需引入

        app.set('view.engine','html');  模板最终    输出类型设置
        app.set('views','./views');     引擎模板目录设置

        app.engine('html',consolidate.ejs); 输出与引擎匹配
        app.engine('css',consolidate.jade); 输出与引擎匹配

        渲染API:
        res.render('模板文件名',{数据}) 整合页面和数据,完成渲染,发往浏览器

-------------------------------------------------------------

路由(router):  告诉你去哪
    前端:导向 路由就告诉你应该去哪
    后端: 子服务 一个路由就是一个小的服务(server/app)

    创建路由
        router = express.Router();  返回路由对象
        app.use('地址,接口',router); 响应的处理过程给了router(子服务)

        处理过程:
        app.METHOD(PATH, HANDLER)
        router.get('/',fn)
        router.get('/1',fn)
        router.get('/2',fn)
    router.use(处理当前路由的中间件) 需要next 延续
    router.all('*',当前router路由下的验证工作) 需要next 延续

-----------------------------------------------------------------
Express 应用程序生成器: 
    干嘛的:自动搭建项目环境的,无需手动
        命令行: 脚手架名称 目录 回车
    安装:需要安装到全局
        npm install express-generator -g   验证 express -h
    创建项目:
        express -e 目录 | . 当前目录创建 
            -e 需要ejs模板引擎
        express -f  强制在非空目录下创建
        cd 目录
        npm install         安装依赖包
        npm start    -> node ./bin/www
    参考资料: http://www.expressjs.com.cn/starter/generator.html

------------------------------------------------------------------

数据库:(mysql , mongodb)

    mysql: 关系数据库(二维表(表头))
        数据库:需要安装服务
            服务:
                a) 安装wamp 开启 mysql服务
                b) 安装mysql 开启服务
        库操作:
            客户端:软件操作(UI工具)
                wamp的客户端是phpmyadmin
                navicat 收费
                nodeJs(后台管理程序),依赖mysql库
        链接库:
            wamp\mysql
            c:P..G..\mysql
        库操作  编码方式 UUC
            建|删 库(目录)
            建:  CREATE DATABASE  `2017-12-6` DEFAULT CHARACTER SET armscii8 COLLATE armscii8_general_ci;
            建|删|改 表(文件)
                创建表头(字段头)
                CREATE TABLE  `2017-12-6`.`user` (
                `name` VARCHAR( 32 ) NOT NULL ,
                `age` INT( 3 ) NOT NULL ,
                `address` VARCHAR( 128 ) NOT NULL
                ) ENGINE = INNODB
        表操作 增删改查
            增:
                INSERT INTO 表 (字段列表) VALUES(值列表)
                INSERT INTO user (name,age,address) VALUES('苏菲',38,'')
            删:
                DELETE FROM 表 WHERE 字段名=值
                DELETE FROM user WHERE name='alex'
            改:
                UPDATE 表 SET 字段名=值 WHERE 字段名=值
                UPDATE user set name='sufei' WHERE name='苏菲'
            查:
                SELECT ? FROM 表
                SELECT * FROM user  查所有

------------------------------------------------------------------
mongoDb
    干嘛的:数据库,nosql(非关系型|缓存型)
    场景:解决大规模数据集合多重数据种类
    下载:https://www.mongodb.com/download-center

    启动库:启动数据库服务(服务端)
        C:\Program Files\MongoDB\Server\3.4\bin
            mongod.exe 启动服务端
            port=27017  默认端口
            mongodb://127.0.0.1:27017   协议+IP+端口
    指定数据存储目录: 需要指定一次
        mongod --dbpath c:\data\db
    环境变量:为了在任意盘符下去启动库  mongod服务端|mongo客户端

    开启客户端: mongo 回车
        UI: 收费(下载+缴费)
        命令行(shell): cmd->mongo回车    git bash - > mongo回车     webstrom->dos/linux

    和mysql对比

    名词
        mysql           mongoDb

        database(库)     database(库)
        table(表)        collection(集合)
        row(一条数据)   document(文档)
        column(字段)      field(区域)
        ...

    存储方式:
        mysql 二维表
        MongoDB json
    库操作:
        查: show dbs
            db 查看当前库
        建:  use 库名  没有建,有就切换
        删: db.dropDatabase()  删除当前库
    集合(表)操作:
        建:db.createCollection('表名',{配置})
            配置:{size:文件大小,capped:true,max:条数|文档数} capped定量
            db.表(集合).isCapped() 返回 true/false 是否是定量
        查:show collections / db.getCollectionNames()
        删:db.表|集合.drop()
    文档(row)操作:
        增:
            db.集合.save({}) / db.集合.insert({})  添加一条
            db.saveOne({})
            db.insertOne({})

            db.集合.save([{},{}]) / db.集合.insert([{},{}]) 多条
            insert  不会替换相同ID    save会

        删:
            db.集合.deleteOne({要删数据条件描述}) db.集合.remove({},true)  一条
            db.集合.remove({要删数据条件描述}) 多条
            db.集合.remove({}) 清空表
        改:
            db.集合.udpate({查询条件},{替换条件},插入,全替换)
            替换后的:
                {$set:{数据},$inc:{age:1}}
        查:
            所有:db.集合.find(条件)
            条数: db.集合.find().count()
            去重:db.集合.distinct(key)

            条件
                {age:22}        age == 22
                {age:{$gt:22}}  age > 22
                {age:{$lt:22}}    age < 22
                {age:{$gte:22}} age>=22
                {age:{$lte:22}} age<=22
                {age:{$lte:122,$gte:22}}    age<=122 && age>=22
                {$or:[{age:22},{age:122}]}  22 or 122
                {key:value,key2,value2}  value && value2
                {name:/正则/}

            db.集合.find({条件},{指定要显示列区域})
                指定要显示列区域: 区域名username:1 显示着个区域,其他不显示
                指定要显示列区域: 区域名username:0 不显示着个区域,其他显示
                _id 是默认显示
            限定:
                db.集合.find().limit(number)  限定
                db.集合.find().skip(number)   跳过
                db.集合.findOne() / db.集合.find().limit(1)  查询第一条
        排
            升:db.集合.find().sort({key:1,key2:-1})
            降:db.集合.find().sort({key:-1})


        db.集合.find({条件},{指定显示区域}).skip(10).limit(10).sort({key:1})
        db.insertOne({数据描述})


    mongodb 客户端包
        1.  require('mongodb'); 引入模块  第三方模块
        2.  创建客户端   mongoCt = mongodb.MongoClient
        3.  创建链接    mongoCt.connect('协议://地址:端口',回调(err,client))
        3.5 链库      client.db('库名')
        4.  链接集合(表) user = db.collection('集合名');
        5.  user.API()  表操作     返回 对象
            one === 1  Many 多个

            insertOne(对象数据,(err,res)=>{})  res = 对象  res.result 结果
            insertMany(arr数据,(err,res)=>{}) res = 对象  res.result 结果 res.ops内容

            updateOne({条件},{更新后},(err,res)=>{})
            updateMany({条件},{更新后},(err,res)=>{})
                updateMany({条件},{更新后},{配置},(err,res)=>{})
                配置: upsert:true 插入
                      projection:true 全局替换

            user.find({条件},{skip:1,limit:1,projection:{name:1}},(err,result)=>{result=对象})
            user.find({条件},{projection:{name:1}}).toArray((err,result)=>{reulst==arr})
            user.countDocuments((err,num)=>{num返回数量})

            排序
            user.find(..).sort({key:-1}).toArray..
            user.find({},{projection:{},sort:{key:-1}}).toArray..
-------------------------------------------------------------

项目:
    1. 定义数据字典(数据库设计) | api 请求方式
        banner: [
                { 
                    "_id" : xx, 
                    "title" : "1", 
                    "sub_title" : "1", 
                    "banner" : "xxxx", 
                    "time":234234,
                    "detail" : { 
                        "auth" : "", 
                        "content" : "<p>xxx<p>", 
                        "icon" : "/upload/banner/9d4083b4f1d28a6c0fb4c463526790eb.jpg" 
                    }, 
                }
            ]
        product: home/follow/column
            { 
                "_id" : xx, 
                "title" : "1_", 
                "des" : "2", 
                "time":234234,
                "detail" : { 
                    "auth" : "4", 
                    "content" :"<p>3</p>", 
                    "auth_icon" : "/upload/user/xxx.jpg" 
                } 
            }
        user:   
            { 
                "_id" : xx, 
                "username" : "alex", 
                "password" : "alex123", 
                "follow" : "100", 
                "fans" : "200", 
                "nikename" : "九叔_", 
                "icon" : "/upload/968a3b7218ee744931276a64c9b7ea01.png", 
                "time" : 1551620448550 
            }
        super:
            { 
                "_id" : xx, 
                "username" : "admin", 
                "password" : "admin123", 
                "icon" : "/img/avatar-5.jpg" 
            }

    2. 素材准备(静态页面模板用户端|管理端)
    3. 搭建服务器
    4. 处理 http请求,拿到数据,后端渲染页面,前端制作API
    5. 项目目录规划

           bin 启动文件
           common 全局公共
                |- douban|mgd
           config 全局配置
                |- global (page_num,page_start,rule,q,upload...)
                |- server (local,http,https)
           public 资源托管
                |-admin 管理端
                |-template 用户端
                |-upload
                    |- banner|product|user
                    |- product
                        |- home|follow|column
           routes 子服务,路由
                admin 管理端
                    |- feedback
                        |- success|error
                    |- product
                        |- add|del|check
                    |- banner
                        |- add|del|check
                    |- user
                        |- add|del|check
                    |- home| product|banner|user
                    |- islogin | login | reg | logout
                api 用户端
                    |- product | detail | banner | user
                proxy 代理
                ...
           views 管理端模板 
                |- feedback
                        |- success|error|app_error
                |- ... 结构同 admin 管理端
                |- common
                    |- header|footer|slider|crumb|toolbar|paging

    6. 管理端业务模块

    登录注销 管理员:
        种cookie,保存session
            req.session['username'] = username;
            req.session['icon'] = result[0].icon;库图片事先存好

    success|error|app_error

    
    添加
    http://localhost:3000/admin/product/add?dataName=xx
        必传参数: dataName
        库存储: 添加时间 
        添加页ejs数据: start=1|q=''|rule=''|page_header|active|user_session
        提交时ejs数据: dataName|start=1

        富文本框使用:
            注意jq库冲突: 要使用富文本框提供的jquery-3.2.1.slim.min
            问题: slim.min没有$.ajax
            解决: 使用jquery,禁用slim.min
        图片上传:
            FormData混合提交 流文件与普通表单混合
            form_data = new FormData() | new FormData(表单本身)
            form_data.append(key,value) 通过req.body获取
            value 可以是file: 
                <input type="file" name="file2" id="file2" />
                formData.append("file2", $('#file2')[0].files[0]);
                通过multer的req.files获取

            $.ajax({
                contentType: false,//不设置编码类型,在进行文件流与普通字符串混合上传的时候,需要设置为false
                processData: false,//不进行数据处理
            })
            后端需要处理未传图(req.files空)
            
        js抓取ejs变量
            form_data.append('dataName',"<%=dataName%>");
        ajax提交后,nodejs需返回跳转地址,由前端跳转
        子节点排序
            .sort({'detail.time':-1,xx:oo})

    删除
    http://localhost:3000/admin/product/del?dataName=xx&_id=xx&start=2&count=2&q=b&rule=_id
        必传参数: dataName|_id
        js所需数据:user_session|page_header|start|q|rule|count|dataName
        ejs所需数据: start|q|rule|count|dataName

        ID操作注意
        var ObjectId = require('mongodb').ObjectId;
        id = ObjectId(req.query.id);     此时的id才是ajax传过来的id,才能与数据库对照

    修改
    http://localhost:3000/admin/product/check?dataName=xx&_id=xx&start=2&count=2&q=b&rule=_id
        必传参数: dataName||_id
        js所需数据:user_session|page_header|start|q|rule|count|dataName|_id
        ejs所需数据: 接口所需数据 + 查询到的page_data

        提交时:
            接口所需数据:{title,des,auth,content,dataName,auth_icon_old,_id,start,q,count,rule}
            ejs所需数据: dataName|start|q|rule|count

        后端需要处理未传图(req.files空)
        前端修改时抓取库图地址(渲染用),提交时传递接收到的库图和本地图,服务器优先抓取本地图
        修改时删除之前的图片fs.unlink
        ajax提交后,nodejs需返回跳转地址,由前端跳转

    检索|排序
    http://localhost:3000/admin/product?dataName=home&start=2&count=2&q=b&rule=detail.time
        必传: dataName
        ejs数据: api_name|page_header|dataName|start|count|q|rule|session|page_data|page_count

        查询 eval('/'+ q +'/g')
        排序 sort:rule ? {[rule]:-1} : {'detail.time':-1}
        排序关键字: (标题title|时间:detail.title)
        
        分页: 取所有,挑出对应页数,返回给浏览器

    

    7. 客户端 api 生成,api文档

        https://www.easyapi.com 在线
        密码加密: bcrypt
            加密: var hash = bcrypt.hashSync(用户传过来的明文密码, 加盐数); 
            校验:  bcrypt.compareSync(用户传过来的明文密码, hash); // true|false

        
    8. 跨域

    后端解决方案

    cors 中间件
        app.use(cors({
          "origin": ["http://localhost:8001","http://localhost:5000","http://localhost:8080"],  //允许所有前端域名
          "credentials":true,//允许携带凭证
          "methods": "GET,HEAD,PUT,PATCH,POST,DELETE", //被允许的提交方式
          "allowedHeaders":['Content-Type','Authorization']//被允许的post方式的请求头
        }));

        特点: localhost != 127.0.0.1
              ajax会有缓存,需要请求不同的数据来刷新

------------------------------------------------------------------
异步流程控制async 第三方模块(非node系统模块)

    类似jquery的deferred
    需要安装: npm init->npm i async -D
    文档:http://caolan.github.io/async/
    参考: C:\Users\user\Desktop\NodeJS\视频\Day2\AM

    串行无关联:(多个异步依次请求,请求之间不依赖)
        async.series([fn1(callback),fn2(callback)],处理函数(err,result))
        callback(err,数据)->callback(null,'one')
        花费时间是:fn1+fn2

        async.series({xx:fn(callback),xx:fn(callback)},处理函数(err,result))
        花费时间是:fn1+fn2

    并行无关联:(多个异步同时请求,请求之间不依赖)
        async.parallel(数组|对象,回调(err,result))  √
        async.parallel([fn1(callback),fn2(callback)],处理函数(err,result))
            callback(err,数据)->callback(null,'one')
        async.parallel({xx:fn(callback),xx:fn(callback)},处理函数(err,result))
        花费时间是:用时最多的那个fn

    串行有关联:(多个异步依次请求,请求之间依赖)
        async.waterfall(数组|对象,回调(err,result)) √
        async.waterfall([fn1(callback){callback(null,data)},fn2(data,callback)],处理函数(err,result))
            result 接受最后一个函数传递过来的一个参数

-----------------------------------------------------------------
代理
    豆瓣:
        hostname:'api.douban.com',//主机名
        port: 443,//端口
        path:'/v2/movie/top250?start=3&count=1',
        method:'get'

    数据代理proxy:      request请求 抓取异步数据
      options={
        hostname:'api.douban.com',
        port:443,
        path:'/v2/movie/top250?count='+req.query.count,
        method:'GET'
      };

      发送http[s]请求
      http[s].request(配置项,回调(响应对象resHttp))  返回请求对象reqHttp
        resHttp 响应对象
        resHttp.statusCode 状态码  200 OK
        resHttp.headers 获取响应头信息
        resHttp.setEncoding('utf-8') 设置编码方式
        resHttp.on('data/end',fn)  ->send给前端
      });

      reqHttp 请求对象
      reqHttp.on('error',(err)=>{console.log(err)});    监听请求失败信息
      reqHttp.end();请求结束
------------------------------------------------------------------------
socket.io 

    场景:
        Web领域的实时推送技术,也被称作Realtime技术。这种技术要达到的目的是让用户不需要刷新浏览器就可以获得实时更新。它有着广泛的应用场景,比如在线聊天室、在线客服系统、评论系统、WebIM等。
    原理:
        双向通信,前端H5api (WebSocket) + 后端net模块
    API:
        cn:
            https://www.w3cschool.cn/socket/socket-k49j2eia.html
        en:
            https://socket.io/

    服务端socket环境:
        const app = express(); 创建express应用
        const server = require('http').Server|createServer(app);//创建web服务器,集成app应用 Server(fn|obj)==createServer(fn|obj)
        const io = require('socket.io')(server);//创建io,集成到server服务
        server.listen(3000, () => {});
        require('./mod/chat-server')(io);
    配合express脚手架
        修改www
        const io = require('socket.io').listen(http实例);
        require('../mod/...')(io); 服务器热监听
        注意: www 不热重启,不检查
    客户端配置:
        <script src="/socket.io/socket.io.js"></script>
        连接服务器:socket = io('http://localhost:3000');

    服务端主动推送到客户端:
        let io = require('../../../bin/www'); require要在需要时再引入
        io.emit('mess_type',{data:'服务端的推送数据')//推送

        客户端接受
        socket.on('new_movie',(data)=>{
            console.log('首页_客户端收到',data)
        })
    客户端推送到客户端:
        客户端(未指定消息|指定的消息)->服务器(广播|私信给指定)->客户端
    聊天室思想:
        客户端(未指定消息|指定的消息)->服务器(广播|私信给指定)->客户端
    服务端(io)
        检测客户端连接:io.on('connection', (socket) =>{}) 回调函数接收客户端socket
            接受:socket.on('消息名称',(data)=>{}) data=接受到的消息
            广播: io.emit('消息名称', {数据});

            检测客户端下线:    socket.on('disconnect',(data)=>{})
            接受私信:
                socket.on('消息名称',(toUserName,data,callback)=>{})
                toUserName==目标用户 callback==给发送端的回调
            发私信:    接受消息的socket.emit('消息名称',{数据})
                发私信 ->      socket   == onlineUsers[toUserName]
                注意,data数据 里面不可以包含socket对象,发往客户端,量太大
    客户端(socket):

        发送未指定消息:    socket.emit('消息名称',{到服务器的数据})
        发送指定消息: socket.emit('消息名称',toUserName,{到服务器的数据},(由服务器返回的数据)=>{})
        接受消息:   socket.on('消息名称',(data)=>{})


    需求:
        当有人连接或断开连接时,将消息广播给连接的用户 √
        添加对昵称的支持 √
        显示谁在线  √
        添加私人消息 √

VUE:
官网:https://cn.vuejs.org
API:https://cn.vuejs.org/v2/api/

小 -> 大
封装(函数)->多个封装(文件)==库||框架
                ↓               ↓
               插件               插件

               模块(文件)  分类(目录)->包
框架:改变了编码思想   VUE: 数据驱动,一切都是数据,面向数据
    面向  事件
    面向  对象
    面向  数据
库:  工具本身不改变编码的方式
    jquery -> dom       事件驱动

MVC:html页面里面会出现<%=后台属性%> {后台属性} {{后台属性}} mustache
一个思想、是后端产物,是为了前后端分离
1. 后台 java(jsp+html) php(php+html+js) nodejs(nodejs+ejs)
2. 前台 (html+css+js)
3. 编辑 (拿着后台给他开发的后台管理页面xxx.com:8008/admin.php)
4. 设计

前端MVC(分离js): 数据、表现、业务分离
model M 数据 ajax/jsonp/数据解析 可以复用
| xx.js
...
view V 视图表现层 展示数据、创建元素,变色,运动 可以复用
...
control C 控制层(串业务,事件驱动) 一般不复用
...

function readBaidu(str,callback){..拿着需求str,求数据,调用回调带数据出去.}
function writeLi(data,callback){...拿着数据写页面}
window.onload=function(){
    oBtn.onclick=function(){
        readBaidu('xxx',function(res){
            writeLi(res);
            winObj.close()
        })
    }
}

VUE: 是个M V VM框架
MVC:衍生出很多变体 MVP MVVM MV*
mv vm~C
MVVM M <-> VM <-> V

基本使用:
new出来一个Vue的实例,传一堆配置参数,控制一片html

VM:     响应系统 - > vDOM做标记 ->一个循环周期结束后->操作DOM
    new Vue   返回  VM
    new Vue({
        el:'选择器'  要控制的那片html代码
        data:{key:value}数据
        methods:{fnName:fn} 方法
    })
M:  初始化数据
    data 选项   number/string/boolean/array/json/undefined/null
V:  数据绑定
    {{数据名}} 模板 mustche  插值表达式
    v-text="数据名"        vue特有的属性(指令)
    v-html="strong"     非转义输出
    v-for="(val,index) in 数据"       val值  index索引   变量数组、对象
        key="bmw"  指定key 是个bmw字符 vue是认得 修改VDom的key值
        :key="item.id"  指定key 是数据id(唯一性) 修改VDom的key值
    v-bind:html属性="数据"  普通的html属性绑定数据
        :html属性="数据"  简写   title/src/url/.....
    事件:
        v-on:事件名="方法"
           @事件名="方法"    简写
           @事件名="方法(参数)"
           @事件名="方法($event,参数)"     methods:{方法:function(ev,参数){ev/event}}

双向绑定:
v-model: 创建双向数据绑定(M<->V) , 用在能生产数据的表单元素
input/radio/select/..... 绑定的是表单元素的 value值
单向绑定:
:value="model层属性"
:checked="......"

单向绑定模拟双向绑定:
:value="...." model->view
v-on:input="fn($event.value)" 输入时把事件对象的value携带到方法,方法修改了model

类属性|方法 和 实例属性|方法
Vue == 类
vm == new Vue({配置}) 返回 实例
在配置内部 的 this == vm 实例
Vue.类方法|类属性
vm.实例方法()|实例属性 == this.实例方法()|实例属性
vm==this this.$set/Vue.set

数据检测:vue的数据是响应式,非响应式的情况如下
数组数据变化:
问题:对数组使用了非变异 (non-mutating method) 方法(返回的了新数组)
解决: 对象合并

        问题:利用索引直接设置一个项|修改数组的长度时
        解决:Vue.set(数组对象, key, value) | vm|this.$set(数组对象, key, value)
对象的数据变化:
        问题:data:{a:1}
                a 数据是响应式的
                vm.b='qq';  b 属性不是响应式的
        解决:Vue.set(对象, key, value) | vm|this.$set(对象, key, value)

模板表达式:
{{数据本身|data的属性|变量|表达式}}
v-指令名="数据|data的属性|变量|表达式"
计算属性: 是一个函数,所依赖的元数据变化时,就会再次执行
computed:{
计算属性:函数:function(){return 返回值} 使用: {{计算属性}}
}

与method的区别: 方法会每次调用,计算属性不会(只有在与之相关的元数据发生变化时,才调用)
    计算属性的性能高: 适合做筛选
    方法:适合在列表渲染使用,强制渲染执行
计算属性:计算属性也可以是个对象
    读取了属性 -> get:fn
    修改了属性 -> set:fn

属性检测|数据观测

watch:{
    数据名:'函数名'
    数据名:函数体(new,old)
    数据名:{
        handler:fn(new,old),
        deep: true 深度检测
        immediate: true 首次运行
        '数据1.数据2':fn
    }
}

指令: 扩展了html语法功能,区别了普通的html属性
vue自带的指令: v-text/v-html/v-bind/v-for/v-model/v-on

        v-show="布尔"             v-if="布尔"
区别: 操作css                   操作dom
场景: 适合频繁切换          适合不频繁切换
性能: 初始渲染消耗          频繁切换回有消耗

class操作/style操作:
v-bind:class="数据|属性|变量|表达式"
:class/style = " 数据 " 数据类型:字符/对象 / 数组
:class="{类名:true,类名2:false}" 布尔值决定样式是否使用
:style="[{css属性名:值},{css属性名小驼峰:值}]"

双向绑定:
v-model: 创建双向数据绑定(M<->V) , 用在能生产数据的表单元素
input/radio/select/..... 绑定的是表单元素的 value值
单向绑定:
:value="model层属性"
:checked="......"

单向绑定模拟双向绑定:
:value="...." model->view
v-on:input="fn($event.value)" 输入时把事件对象的value携带到方法,方法修改了model

指令(directive):
v-text/v-html/v-bind/v-on/v-model/v-for/v-if/v-show/v-else/v-else-if/v-bulala

自定义指令: 指令是个函数|对象,用来操作dom的, 里面的this 返回window

a)  Vue.directive('指令名不带v-',函数(el,binding))
    el == 使用指令的DOM元素
    binding 是个对象 含有传入的 参数(binding.value)
b)  定义在选项里面
    directives:{
        指令名不带v- : 函数(el,binding){}
    }

指令是个函数(简写),可以是个对象

{
    钩子函数
    inserted:fn(el,binding)     绑定指令的元素插入到父节点时调用
    bind:fn 指令第一次绑定到元素时调用
    update:fn   指令所在的元素的model层的数据,view有更新请求时
    componentUpdated:fn 更新完成时
}

简写方式: bind + update

事件:
绑定行间事件: <xx v-on:事件名="方法名(event,参数)" <xx @事件名="方法名(event,参数)"
绑定自定事件:
定义:vm.on( '自定义事件名'|['自定义事件名1','自定义事件名2'], 回调(参数) ) 销毁:vm.off( '自定义事件名'|['自定义事件名1','自定义事件名2'], 回调(参数) )
触发: vm.$emit(自定义事件名1,参数)

    自定义事件名: 使用 kebab-case 的事件名
事件对象:
    事件对象可以不传递,需要传递的场景:传参数同时使用事件对象时
    show($event,参数)

冒泡:默认冒泡
    $event   ev|event.cacelBubble=true ev.stopPropagation()
    @click.stop 修饰符
默认行为:
    event|ev.preventDefault();   @事件.prevent

连缀: @事件.修饰符.修饰符 @事件.prevent.stop

修饰符:  keyCode/键名(enter/left/right/up/down/...)

修饰符:
事件|自定义事件修饰符: @click.stop
capture: 使用事件捕获模式
self: 点到时才触发,不是从内部元素触发的
once: 只会触发一次
passive: onScroll事件 结束时触发一次,不会频繁触发,移动端使用
按键修饰符: @keyup.left
系统键 .ctrl
...
exact 严格默认 @键盘事件.修饰符1.修饰符2.exact 只有1+2才可触发
鼠标按钮修饰符:
.left
.right
.middle

表单修饰符: 
    v-model.lazy   :    确认时才修改model数据
    v-model.number :    提取数子
    v-model.trim :   删除前后空格

过滤器(filter): 就是个函数

场景: 格式数据
currency / number / date   vue1.x
vue2.x 取消了自带过滤器,  需要用户自行封装

使用: {{数据名 | 过滤器名(参数1,参数2)}}
        v-xxx="数据名 | 过滤器名(参数1,参数2)"
        :属性="数据| ... "
定义:
        a) Vue.filter('过滤器名称',函数(要过滤的元数据,参数))
        b) 选项
            filters:{
                过滤器名称:函数
                过滤器名称2:函数(要过滤的元数据,参数)
            }

数据交互:

XMLHttpRequest|ActiveXObject
    - ajax函数
$.ajax()

promise
fetch()  返回 是一个promise
async + await

https://www.baidu.com/s?wd=xunlei&&cb=show

vue-resource   下载安装引入   vue1.x 推荐   支持jsonp
    使用: 返回的是promise对象
    this.$http.get('url地址?key=value&key=value').then(succFN,errorFn)
    this.$http.get('url地址',{配置}).then(succFN,errorFn)
    this.$http.post('url地址',{数据},{配置}).then(succFN,errorFn)
    this.$http.jsonp('url地址',{配置}).then(succFN,errorFn)
        params:{ //携带参数
            wd:'bulala'
          },
        jsonp:'cb', //约束回调函数的key,默认是callback
        jsonpCallback:'show',//回调函数名

        请求:
            url:'',
            body: post需要携带的数据
                支持string  a=1&b=2
                对象<需要设置emulateJSON:true>
                UrlSearchParams 类型 new Xxx() -> x.set(key,value)
            params: {key:value}  携带数据  get
            headers: {}  携带请求头
            method:'get' get/post/.....   string
            timeout: number  超时
            progress:fn   进度
            credentials: false  是否携带跨源凭证
            emulateJSON:true post请求是需要携带的请求头
            jsonp:'回调函数键' 默认是callback
            jsonpCallback: 回调函数名
        响应:
            body      返回的数据  对象  (JSONP.parse)
            bodyText  返回的数据  文本格式  toString
拦截器:希望所有的请求在发出之前或数据回来之后,做一些的处理,辅助工作,是个统一配置的过程
    Vue.http.interceptors.push(function (request, next) {//拦截请求
      console.log('请求发出前,要做的拦截');//显示loading...
      next(function (response) {
        console.log('响应回来之后,要做的拦截')//关闭loading...  数据统一处理
      });
    });

axios   下载安装引入  vue2.x只有推荐      支持jsonp ??

    axios({
        url: 地址
        method: 提交方法   get/post/put........
        params: get提交参数
        data: post提交参数  参数==URLSearchParams  new URLSearchParams().set(key,value)
        timeout:超时间
        headers: 请求头
    })

数据交互:
vue-resource 下载安装引入 vue1.x 推荐 支持jsonp
使用: 返回的是promise对象
this.http.get('url地址?key=value&key=value').then(succFN,errorFn) this.http.get('url地址',{配置}).then(succFN,errorFn)
this.http.post('url地址',{数据},{配置}).then(succFN,errorFn) this.http.jsonp('url地址',{配置}).then(succFN,errorFn)

        配置:
            params: {key:value}  携带数据  get
            headers: {}  携带请求头
            method:'get' get/post/.....   string
            timeout: number  超时
            progress:fn   进度
            credentials: false  是否携带跨源凭证
            emulateJSON:true post请求是需要携带的请求头
            jsonp:'回调函数键' 默认是callback
        响应:
            body      返回的数据  对象  (JSONP.parse)
            bodyText  返回的数据  文本格式  toString
拦截器:希望所有的请求在发出之前或数据回来之后,做一些的处理,辅助工作,是个统一配置的过程
    Vue.http.interceptors.push(function (request, next) {//拦截请求
      console.log('请求发出前,要做的拦截');//显示loading...
      next(function (response) {
        console.log('响应回来之后,要做的拦截')//关闭loading...  数据统一处理
      });
    });

数据监听:监听到数据的变化,启动处理函数
    1)  watch 选项|属性   √
        watch:{data里面的属性名:处理函数|对象}
        watch:{
            元数据:函数(新值,老值)
            元数据:{
                handler:函数(新值,老值),
                deep: true   深度检测   默认false
            }
        }

axios   下载安装引入  vue2.x推荐        支持jsonp ??
    希望可以this.$http 去使用axios,需要 配置: Vue.prototype.$http=axios;

    this.$http|axios({配置}).then(成功回调(res)).catch(失败回调(res))
    this.$http|axios.get(url,{配置}).then(成功回调(res)).catch(失败回调(res))
    this.$http|axios.post(url,pollfill).then(成功回调(res)).catch(失败回调(res))

    post请求:
        pollfill = new URLSearchParams() -> params.append('a', 111);
        this.$http|axios.post(url,pollfill).then(成功回调(res)).catch(失败回调(res))

        this.$http|axios({
            url:'',
            method:'post',
            data:pollfill
        })

    配置:
        url:'' 地址
        method: '' 提交姿势
        params:{} get提交的数据
        data:{} post提交数据
    res:
        data:  数据

实例|组件 生命周期
beforeCreate/created/beforeMount/mounted/beforeUpdate/updated/beforeDestroy/destroyed


作业:
百度下拉(键盘遍历,回车搜索,参考baidu)
微博完善
许愿墙完善

预习:组件  cn.vuejs.org

vue-dev-tools安装
https://github.com/vuejs/vue-devtools
1. Clone this repo downloadzip 到桌面
2. npm install 安装依赖
3. npm run build build目录项目就有各种浏览器的安装包
4. 打开 chrome -> 设置->更多工具->扩展应用->开发者模式->加载扩展程序->指向->build目录项目下的chrome


组件:
Vue根实例表示1个应用,一个应用有若干个组件拼装而成

使用组件
<组件名></组件名>
<组件名/>

<Header></Header>脚手架环境下webpack协助解决了,调用时依然不能小写header

定义组件
定义:
a) let 组件变量名= Vue.extend({
template:'<div class="header">我是header组件</div>'
});
b) let 组件变量名={}; √
注册(拼装)
a) Vue.component('组件名',组件变量名);
全局注册的行为必须在根 Vue 实例 (通过 new Vue) 创建之前发生
b) 选项
components:{
组件名:组件变量名 √
}
组件数据
data 要是个函数,且要有返回值 object
一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝,否则组件复用时,数据相互影响
模板:外部模板,字符模板(行间模板|inline-template)
组件内部: template: '#id名'
<script type="x-template" id='id名'
<template id='id名'...

组件(单文件|.vue)
script + template + style

注意:
组件名不可和html同名
组件没有el选项
组件一定要有根元素

推荐:
组件变量名: 大驼峰 XxxXxx
组件名: xx-xx 大驼峰


组件数据流动/数据传递/通讯

父->子  属性(props)     √
    <子 :自定义属性="父数据"></..>
    子组件:
        选项
        props:['自定义属性']   props:{自定义属性:{type/default/required/...}}
    展示: 子模板 {{自定义属性}}

    props命名:
        props: ['postTitle']
        <xx post-title="hello!"></xx>
    单向下行绑定: 父级 prop 的更新会向下流动到子组件中,但是反过来则不行
        在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变这个对象或数组本身将会影响到父组件的状态
子->父  事件(自定义)
    <子 @自定义事件="父方法"></..>
    子:      this.$emit('自定义事件',子.数据名)
    父:      methods-> 父方法(接受数据){处理}

父子之间共享数据和方法
    <子 ref="自定义子名称"></..>

    父访问子:   this.$refs.自定义子名称.数据名/方法()
    子访问父:   this.$parent.数据名/方法()

    $refs 只会在组件渲染完成之后生效,并且它们不是响应式的,避免在模板或计算属性中访问 $refs

公共总线  new Vue()=bus  bus.$emit()/$on()
    
    let bus = new Vue();        返回  Vue对象

    bus.$emit('自定义的事件名',数据)
    bus.$on('自定义的事件名',function(接){处理})

组件通讯:解决方案

逐层传递

    父->子  
        props父子     使用场景: 电商 (松耦合)
        $parent 子抓父     使用场景: 通用组件(紧耦合)
    子->父  
        自定义事件  使用场景: 电商 (松耦合)

        $ref 引用dom元素 | $children 父抓子  | $parent 子抓父   使用场景: 通用组件(紧耦合)

    兄弟->兄弟
        中间人

    路由

集中式管理
    订阅发布模式(第三方库 pubsub )
    
    公共总线(vue)

    $root(vue)
        子组件内部直接找到根实例,访问,操作根data数据
        this.$root == vm

    web存储(cookie,localstroge/session)
    存库
    
    状态管理

永久: 存库 , 前端文件存储(localstroge,cookie),后端文件存储(writeFile)
临时:状态管理,订阅发布模式,公共总线(vue),$root(vue)

动态组件:
动态组件: 组件动态化(数据化),在不同组件之间进行动态切换,component自身不会渲染
<component is="'组件名'"></component>
<component :is="数据"></component>

缓存组件:
keep-alive 包裹了目标组件,对目标组件缓存,不会触发卸载挂载,但会触发activated/deactivated
keep-alive 不给props时,默认内部出现过得组件,都会被缓存
默认缓存第0个组件
props:
:include: ['组件名','组件名2'] 加入一部分
:exclude: ['组件名','组件名2'] 排除一部分
:max = 数字 最多可缓存的组件数
最多可以缓存多少组件实例。一旦这个数字达到了,在新实例被创建之前,已缓存组件中最久没有被访问的实例会被销毁掉(卸载挂载)。

组件钩子:
activated 活动组件 被缓存时起效果
deactivated 非活动组件


内容分发
template: slot 组件槽位 <slot 属性:name=槽名
<组件名><标签 slot="槽名">内容</..</...


vue动画

动画组件: transition|transition-group   谁做动画,就包着谁

组件属性:
    name =  "动画名"
    mode="out-in|in-out"
    enter-class = "类名"
    enter-active-class = "类名"
    leave-class = "类名"
    leave-active-class = "类名"
样式:
    .动画名-enter{..}  入场前(打哪来)
    .动画名-enter-active{..} 入场后(来了停哪)
    .动画名-leave{..} 离场前
    .动画名-leave-active{..} 离开场后(到哪去)

事件:
    @before-enter="方法(el)"   el==做动画的元素(原生)
    @enter="方法"
    @after-enter="方法"
    @before-leave="方法"
    @leave="方法"
    @after-leave="方法"

1) css3
    transition:无跳变
        .动画名-enter{..} 打哪来1   +   .动画名-leave-active{..} 到哪去4
    animation:有跳变
        .动画名-enter-active{指定入场} 来了停哪2 + .动画名-leave-active{指定离场}到哪去4
2) css库 animate.css
    同 animation 有跳变
3) js库 动画名  需要设定初始位置
    下载 velocity.js   http://velocityjs.org/
    使用: Velocity(el,{css属性},{配置})
            move(obj,{样式},{complete,progress,time,over})
            $(obj).animate({样式},{配置})

    配置:
        duration: 毫秒   事件
        easing: 动画类别 ''
        Queue
        complete:fn()
        progress:fn
        loop: 1 次  true无限
        delay: 毫秒 延时
        display:'none/block' 动画结束时是否可见
    注意:
        leave(el,done){}

transition-group:
一组动画
transition-group 包着一组元素 ,每个元素要有key 其他的同transition


SPA|MPA: https://www.cnblogs.com/nangezi/p/9201226.html

webpack 4 目标: 模块化,工程化(开发,生产),自动化
官网: http://webpack.github.io/docs/
中文: https://webpack.docschina.org
前身: browserify 缺点,只能转化js

webpack作用?
干嘛的: 项目管理、打包、模块管理、加载资源(js/css/html/png../woff/data/vue...),转换器(loader)
前身:grunt/gulp/browserify->webpack->
backbone/jq/angularjs/react / vue1.x/vue2-3/angular7

webpack的历史
webpack1支持CMD和AMD,同时拥有丰富的plugin和loader,webpack逐渐得到广泛应用。
webpack2 支持ES Module,分析ESModule之间的依赖关系,webpack1必须将ES,Module转换成CommonJS模块,2支持tree sharking
webpack3 新的特性大都围绕ES Module提出,如Scope Hoisting和Magic Comment;

环境支持: Node8+、npm5+

安装:
全局
npm i webpack webpack-cli -g
cli 命令行工具 打包会依赖cli

本地
npm i webpack webpack-cli --save-dev 开发依赖

概念:
webpack是一种静态编译工具(预编译)
入口文件、出口、转换器、插件
打包
webpack --mode development 会自动找src/index.js 到 dist/main.js
要求设置开发模式|生产模式

webpack 入口地址 --mode development  指定入口

环境分离
    development:
        a.浏览器调试工具
        b.注释、开发阶段的详细错误日志和提示
        c.快速和优化的增量构建机制
    production:
        a.开启所有的优化代码
        b.更小的main大小
        c.去除掉只在开发阶段运行的代码
        d.Scope hoisting(作用域提升)和Tree-shaking(打包的结构只包含实际用到的 exports)

自动解决依赖:

原理:Webpack 会分析入口文件,解析包含依赖关系的各个文件。这些文件(模块)都打包到 main.js 。Webpack 会给每个模块分配一个唯一的 id 并通过这个 id 索引和访问模块。在页面启动时,会先执行 main.js 中的代码,其它模块会在运行 require 的时候再执行。

loader:
webpack默认只支持javascript文件(默认)
* 其他文件(CSS/LEASS/..) 需要用加载器(loader)

loader: 类似一种转化器, 它可以把一个东西,转成另一个
    需要下载    style-loader(读取到的css文件插到页面) css-loader(读取css文件)
    下载: npm install style-loader css-loader -D
    require('style-loader!css-loader!./xx.css')

配置 webpack.config.js 是一个nodejs
作用: 配置一些webpack需要入口、出口、loader、Chunk代码块、Plugin扩展插件、Module模块

编写:
    module.exports={
        entry:'./src/index.js'  入口文件
        output:{ 默认输出到dist
            path:path.resolve(__dirname,'dist')//指定编译目录  不写默认指定到dist
            filename:'js/boundle.js'//以编译目录为根的出口文件路径
        },
        module{
            rules:[
                {test:'/\.css$/',use:['style-loader','css-loader']}
            ]
        },
        mode:'development' | production 区别环境
    }



entry: 入口接收string | json
    {app:'index1.js',app2:'index2.js'} 输出要求多输出
output: {
    path:path.resolve(__dirname,'dist') //指定编译目录
    publicPath:'/', //公共路径 影响资源(图)路径,devserver路径,多页资源路径,开发生产环境基础资源路径,cdn路径
    filename:'bundle.js' 单文件输出 | '[name].js' 多输出  html引入app和app2 配合
}

webpack 开发环境下编译(打包到bundle.js)
webpack -p  生产环境下编译(打包到bundle.js,并压缩) == mode production 
webpack -w  监听文件改动,自动编译,不用每次运行,但不会自动刷新浏览器

需要自动刷新: webServer 搭建前端开发服务器
cnpm install webpack-dev-server -g | -D

默认: 自动打包(放到内存),创建前端开发型服务器(默认找webpack.config所在的位置,找index.html)
参数:
    命令行
    webpack-dev-server --port 8088 --open --mode development

    写到webpack.config.js配置文件:

    devServer: {//和module同级     publicPath:'/'
        port: 8088,
        open:true
    }

    终端运行方式2: webpack-dev-server --port 8088 --open


把运行命令放到package.json文件:  devServer可以不要了
    "scripts":{
        "dev": "webpack-dev-server  --port 8088 --open"
    }

    终端: npm run dev
问题:代码错误没有报到正确位置
解决:devtool:'inline-source-map'

省略引入文件名后缀
配置webpack.config.js
resolve: { 与module同级
extensions: [ '.js', '.css', '.json', '.jsx']
}
require('./style') 可以省略后缀名


优雅降级: ES6->ES5 tracuer babel
npm install babel-loader @babel/core @babel/preset-env -D
做个js文件模块,测试导出引入
导出模块:
export default {}

引入模块:
    import 名字 from 模块名

配置babel预设:
    module>rules> + {}
      {
        test:/\.js$/,
        exclude:/node_modules/, 排除
        use:[{
          loader:'babel-loader',
          options:{
            presets:['env']
          }
        }]
      } 

产出html
npm i html-webpack-plugin ---save-dev
const HtmlWebpackPlugin = require('html-webpack-plugin')
plugins:[
new HtmlWebpackPlugin({
template: './index.html',
filename: './index.html',//到path目录找生产环境资源|开发环境的publicPath
hash:true,//防止缓存,会给文件后面加入hash
minify:{
removeAttributeQuotes:true//压缩 去掉引号
}
})
]

publicPath: '/', 
filename: 'js/[name].js',

css抽离(代码分离)
yarn add extract-text-webpack-plugin@next --dev
npm i extract-text-webpack-plugin@next --save-dev | -D
@next 兼容webpack4 未来会不需要

const extractTextPlugin = require("extract-text-webpack-plugin")

loader配置:
use: ExtractTextWebapckPlugin.extract({
      use: 'css-loader'
    }) //不再需要style-loader

new ExtractTextWebapckPlugin('css/[name][hash:6].css')

同类推荐: mini-css-extract-plugin

图片|文件 打包
yarn add url-loader file-loader --dev
npm i url-loader file-loader --save-dev
url-loader 存base64 file-loader存文件
{
test:/.(png|jpg|gif)/,
use:[{
loader: 'url-loader',
options: {
limit: 5000,
outputPath: 'images/', 5000意思存到images
}
}]
}

css中引入 | js动态(模块化) 引入

资源copy: 静态资源(js,数据图片,json) -> 生产环境
require('copy-webpack-plugin')
new CopyWebpackPlugin([
{ from: path.resolve(__dirname,'static'), to: path.resolve(__dirname,'build/static') }
])

同类推荐: transfer-webpack-plugin

自动加载模块,而不必到处 import 或 require

new webpack.ProvidePlugin({
$: 'jquery',
_: 'lodash',
bulala:'jquery'
})

拆分公共模块 | 分片(chunk)打包 :

分片打包?使用第三方类库?

由于CommonChunkPlugin已被webpack4废弃,webpack4推荐使用SplitChunkPlugin来提取公共模块

用法:
plugins:[
    new CommonChunkPlugin({配置})
]

entry: {
'vendor/jquery': 'jquery',
'common/b': './src/mod/b',
}

和plugin评级
optimization: {
splitChunks: {
cacheGroups: {
jq: {// 自定义 无意思
test: /jquery/,
chunks: 'initial',//有三个可选值:initial(初始块)、async(按需加载块)、all(全部块),默认为all;
name: 'vendor/jquery',//拆分出来块的名字(Chunk Names),默认由块名和hash值自动生成;设置ture则使用默认值
priority: 10//表示缓存的优先级
},
cm: {
test: /common/,
chunks: 'initial',
name: 'common/b',
priority: 10
}
}
}
}

http://www.php.cn/js-tutorial-390965.html


webpack.config -> webpack.config.dev|prod|base

"dev": "webpack --mode development --config config/webpack.config.dev.js",
"build": "webpack --mode production --config config/webpack.config.prod.js",
"server": "webpack-dev-server --mode development --config config/webpack.config.dev.js"


webpack-merge 安装

SPA: single page application 单页面应用
特点: 速度快,数据ajax请求,通过路由,页面不会整体重载
实现: 路由 -> 根据url的不同,加载组件

区别:https://www.cnblogs.com/nangezi/p/9201226.html

路由使用流程:
-1. 安装 : npm i vue-router -S
0. import VueRouter from 'vue-router' -> Vue.use(VueRouter) 安装|注册到全局
1. 使用路由 (去哪)
<router-link to="/home">首页</router-link>
<router-view>展示区</router-view>
router-link 组件属性
tag='li' 指定编译后的标签
active-class='类名' 指定激活后的样式
2. 配置路由(建立组件和请求的对应关系) 数组
[{path:'/home',component:home},,{}]
path 路径
component: 指向的组件变量名
3. 创建路由(传递配置)
router = new VueRouter(配置)
配置: {routes:数组}
4. 顶层|根组件,注册路由 (路由控制页面组件的加载)
选项
router(选项):router (router对象)
子路由:children
routes=[
{},
{
path:xx
component:xx
children:[ 子路由
{}
..
]
},
{}
]

参数配置:
    {path:'xx/:参数变量',component:xx}

传递参数 and 数据
router-link to='xx/参数?a=1b=2'
router-link :to='{name:'xx',params:{id:1},query:{a:2,b:3}}'


接收参数和数据
    a)  模板: {{$route.params|query|path}} 
        组件: this.$route.xxx
    
    b)  模板: {{id|a|b}} 
        组件:  props:['id','a','b'] 
        路由配置: props:(route)=>({id:route.params.id, ...route.query})

        松耦合 组件便于不依赖路由,单独测试

组件内部: this == 组件   this.方法|数据 访问组件自己的  this.$xxx 访问全局
           this.$router
组件模板:   {{xxx}}  子个的数据  {{this.$router}} 全局数据
            @事件="$router.xx()"

编程式跳转:
    router.push(...)
    this.$router.push({name:'...'})   添加一个路由 (记录到历史记录)
    this.$router.replace({name:'...'})   替换一个路由 (不记录到历史记录)
    this.$router.go(-1|1)|goBack()  回退/前进  history.go|goBack

导航守卫: 路由授权|守卫

    全局守卫/路由独享的守卫/组件内的守卫

    beforeRouteEnter(to,from,next){}    前置守卫,进入
        to 目标路由
        from 当前路由
        next 是个函数  next() == next(true)  运行跳转
                       next(false) 不让跳转
                       next('字符路径')/next({对象路径}) 重定向

    beforeRouteLeave(to,from,next){}  后置守卫,离开

路由数据预载:
    beforeRouteEnter(to,from,next){
        1. 数据附加到目标路由上 to.query.数据名=值
        2. next( _this => _this.属性="拿到的数据")
    }

vue-cli 脚手架 (搭建项目环境) 命令行工具
内部代码: webpack
安装:
1. npm install -g @vue/cli || yarn global add @vue/cli
2. npm install -g @vue/cli-init || yarn global add @vue/cli-init

可以使用 cli2 命令行 cli3

cli2:

创建项目环境:
    vue init webpack-simple 目录|.    webpack-simple|webpack  模板
    cd 目录
    npm install
    npm run dev  运行
    开发....
    npm run build 打包

webpack-simple:
    webpack-dev-server --port 8001 --open 设置端口,开启浏览器
    webpack.config.js:
        devServer
            port    端口
            open  开启浏览器
webpack:
    webpack-dev-server --port 8001 --open 设置端口,开启浏览器
    config/index.js
        autoOpenBrowser: 开启浏览器
        useEslint: eslint检查的开启关闭
        port: 端口
    build/webpack.base.conf.js
        loader 配置
        entry 入口

vue cli3
官网: https://cli.vuejs.org/zh/
关于旧版本
Vue CLI 的包名称由 vue-cli 改成了 @vue/cli。 如果你已经全局安装了旧版本的 vue-cli (1.x 或 2.x),你需要先通过 npm uninstall vue-cli -g 或 yarn global remove vue-cli 卸载它。

安装cli3 :
    npm install -g @vue/cli | yarn global add @vue/cli

    npm install -g @vue/cli-init  和cli2 命令行 并用 (后续在cli3下指向一切cli2操作)

创建项目:
    vue create xxx / .
        删除默认配置:     .vuerc     C:\Users\Admin
    vue ui      图形UI形式


打包: 构建目标
    npm run build
    npx vue-cli-service build
    vue-cli-service build   需要安装vue-cli-service 命令行?

    index.html 会带有注入的资源和 resource hint
    第三方库会被分到一个独立包以便更好的缓存
    小于 10kb 的静态资源会被内联在 JavaScript 中
    public 中的静态资源会被复制到输出目录中

Index.html 文件
    插值:
        使用 lodash template 语法插入内容
        <link rel="icon" href="<%= BASE_URL %>favicon.ico">
        打包后 ->  <link rel=icon href=/favicon.ico>
    Preload:
         是一种 resource hint,用来指定页面加载后很快会被用到的资源,所以在页面加载的过程中,我们希望在浏览器开始主体渲染之前尽早 preload。 vue 自动生成
         <link href=/css/app.8c41b469.css rel=preload as=style>
    Prefetch:
        用来告诉浏览器在页面加载完成后,利用空闲时间提前获取用户未来可能会访问的内容。
         Vue CLI 应用会为所有作为 async chunk 生成的 JavaScript 文件 (通过动态 import() 按需 code splitting 的产物) 自动生成 prefetch 提示。


资源安排:

    相对路径 指向assets开发目录
    绝对路径 指向public目录

    在 JavaScript 被导入或在 template/CSS 中通过相对路径被引用。这类引用会被 webpack 处理。
    放置在 public 目录下或通过绝对路径被引用。这类资源将会直接被拷贝,而不会经过 webpack 的处理。

    url-loader 将小于 10kb 的资源内联,以减少 HTTP 请求的数量

    推荐 资源放置到assets:
        脚本和样式表会被压缩且打包在一起,从而避免额外的网络请求。
        文件丢失会直接在编译时报错,而不是到了用户端才产生 404 错误。
        最终生成的文件名包含了内容哈希,因此你不必担心浏览器会缓存它们的老版本
    何时使用 public 文件夹
        你需要在构建输出中指定一个文件的名字。
        你有上千个图片,需要动态引用它们的路径。
        有些库可能和 webpack 不兼容,这时你除了将其用一个独立的 <script> 标签引入没有别的选择。

安装CLI插件:  vue官网 找vue-cli-plugin-插件名  生成器
        vue add axios   /指向public
        vue add router / vue add vuex
            可以安装包,并创建文件(插件配置的基本小样)

        npm i axios vue-router vuex -D
            需要自行编写配置

        vue add animate 没有vue-plugin-animate-> npm i animate.css
        vue add swipe   没有 -> npm i vue-swipe

        注意:
            vue add 的设计意图是为了安装和调用 Vue CLI 插件。这不意味着替换掉普通的 npm 包。对于这些普通的 npm 包,你仍然需要选用包管理器。

            CLI 插件都会包含一个 (用来创建文件的) 生成器和一个 (用来调整 webpack 核心配置和注入命令的) 运行时插件
配置

    修改启动端口:
        a) package.json->scripts->vue-cli-service serve --port 8001 --open
        b) node_modules/@vue/cli-service/lib/options
        c) vue.config.js  √

    node_module配置:  cli-service 对 webpack 配置进行了抽象
        @vue/cli-service/lib/options
            css: {
                extract: false,
                modules: true, //css模块化
                    使用:
                        style标签 添加module属性 
                        模板:$style.类|id  
                        组件:this.$style  返回所有的类和id的集合
                    场景: scoped/moudle 对css内部import css时无效时

              }
    vue.config.js
            参考:https://github.com/vuejs/vue-cli/tree/dev/docs/zh/config
                  https://cli.vuejs.org/zh/config/

            module.exports={
              css: {
                extract:false,//css分离 生产环境下是 true,开发环境下是 false
                modules: true, //开启css模块化
                loaderOptions:{//向 CSS 相关的 loader 传递选项
                  css: {
                    // 这里的选项会传递给 css-loader
                  },
                  postcss: {
                    // 这里的选项会传递给 postcss-loader
                  }
                }
              },
              devServer:{
                // proxy: 'http://localhost:3000',//告诉开发服务器将任何未知请求 (没有匹配到静态文件的请求) 代理到http://localhost:4000。
                // host: '0.0.0.0',
                port: 8003,
                open: true,
                // https: false,
                // proxy: null, // string | Object
              },
              lintOnSave:false, //关闭esling警告
              lintOnSave: process.env.NODE_ENV !== 'production', //生产构建时禁用
              productionSourceMap:false, //打包不携带map文件

              cli3 代理
              proxy:{//代理是从指定的target后面开始匹配的,不是任意位置;配置pathRewrite可以做替换
                  '/api':{//axios访问 /api ==  target + /api  
                    target:'https://uncle9.top',
                    changeOrigin:true,//创建虚拟服务器 
                    ws:true,//websocket代理
                  },
                  '/douban':{// axios 访问 /douban == target + '/douban'
                    target:'https://api.douban.com',
                    changeOrigin:true,
                    pathRewrite:{//路径替换
                      '^/douban':'',// axios 访问/douban/v2 == target + /v2
                    }
                  }
                }
            }

状态管理 store
什么时候用: 打算开发中大型应用
集中式数据管理, 一处修改,多处使用

思维:
                                        store.js
                this.$store.commit('increment') -> mutations
                this.$store.dispatch('jia')     -> actions
                 mapActions() ->actions                         mapGetters()->getters
            学生          代课老师            校长          财务      版主任     学生
        components - >  actions     ->  mutations -> state  <- getters  <-  components
            发送请求      处理            修改状态
                          业务逻辑      修改state            读取state
                          异步
                                                    state<-$store.state <-  学生
-----------------------------------------------------------------------------
配置:
    安装 vuex 状态管理插件
    引入 + use + 注册到根

------------------------------------------------------------------------------
mapActions/mapGetters  执行后, 返回来的是对象
    对象: {incremen:fn,decrement:fn,xx,xx}
------------------------------------------------------------------------------
this.$store.commit('类型',参数/负载/payload) -> mutations
this.$store.dispatch('类型',参数/负载/payload)  -> actions
this.$store.dispatch({type:类型,负载key:value})
const actions = {
    类型:({store对象},负载)=>{}
}

<xx @click="请求类型(负载)" 

项目流程

0.  技术栈:    vue-cli3 + vue-router + axios + vue-aniamte + vuex .....   全家桶  + nodejs + express + mongodb

1. 环境搭建(vue-cli +  vue-router + axios + vuex == 全家桶)

2. 目录分析(组件安排)
    dist
        |-...
    public
        |-data:
            |-数据.json
            ....
    src
        |-assets
            |-js
            |-css
            |-image
        |-pages
            |-  home.vue / follow.vue / column.vue / user.vue
            |-  detail.vue / login.vue / reg.vue
            |-  
        |-filters
            date.js
            fillzero.js
            ...
        |-plugins 插件配置
            axios
            router
        |-components
            |- Header.vue / Footer.vue
            |- loading.vue / error.vue
            |- silder.vue /  list.vue
        |-store
            |-index.js
            |-actions.js
            |-mutations.js
            |-getters.js
            |-state.js
            |-types.js
        main.js
        App.vue
    package.json
3. 布局
    a) 切图(需要设计稿)
        css特点:
            style-loader 插入到style标签(先插入下层组件样式)
            样式会继承父组件
        css 规划问题:
            a) css命名空间   .app-home.box{}   .app-product-box{}  √    BEM
            b) css模块化
                    style: <style module>
                    模板:  <div :class="$style.选择器名"....
                    组件:  this.$style
            c) <style scoped 独立样式作用域
            
    b) UI库  (bootstrap/elementUI pc端 / mintUI移动端 / 妹子UI/ant.design/weex)
    c) html+css模板 移植 到 组件
        01 全局资源 在主入口文件(index.html)引入
        02 全局资源 主程序(main.js|app.vue) | providerPlugin webpack插件
        03 自执行脚本,跟着要控制的元素走    *
        04 私有资源,进入组件内部
        05 开发环境资源引入动作,取决于资源的输出方式,和业务自身的要求(如何暴露)


        插件推荐 
            vue-scroller:上拉下拉
            vue-lazyload:懒加载
            vue-awesome-swiper:轮播图
            .....

        vue-swipe:
            npm i vue-swipe -D  安装
            import './node_modules/vue-swipe/dist/vue-swipe.css'; 引入样式
            import { Swipe, SwipeItem } from 'vue-swipe'; 引入组件

            Vue.component('swipe', Swipe);    注册安装
            Vue.component('swipe-item', SwipeItem);

            注册到选项components 私有使用

4. 路由搭建

    css 冲突:
        style-loader 插入所有组件css 到 style标签,选择器重名
    解决:
        1)  css命名空间   √
            .app-home{}   .app-product{}  √
        2)  css模块化
            cli2
                webpack 配置:
                    utils->css-loader->options{modules:true}
                
                组件引入样式1: 
                    应用样式:  <div :class='$style.类名|id名'

                组件引入样式2: 写在style内部
                    应用样式:  <div :class='$style.类名|id名'

            cli3
                <style  module>  使用: <xx :class={$style.box}
                    注意: 对css内部@import() css时无效时
        3)  scoped 独立样式作用域
            style 标签 计入scoped属性
                注意: 对css内部@import() css时无效时

5. 数据交互: 

    没有接口:
        mock假数据 json
        假接口: nodeApi

    home(axios)->porps(list,dataName)->list         -> 路由传递参数和数据-> detail(参数->axios)
    follow(axios)->porps(list,dataName)->list

    全局过滤器:  Vue.filter('name',varname)
    全局组件(loading)

    同时执行多个请求

    axios.all([
        axios.get('https://api.github.com/xxx/1'),
        axios.get('https://api.github.com/xxx/2')
      ])

      .then(axios.spread(function (userResp, reposResp) {
        // 上面两个请求都完成后,才执行这个回调方法
        console.log('User', userResp.data);
        console.log('Repositories', reposResp.data);
      }));

    当所有的请求都完成后,会收到一个数组,包含着响应对象,其中的顺序和请求发送的顺序相同,可以使用 axios.spread 分割成多个单独的响应对象


6. 非状态管理
    this.$root  返回的是根实例  new Vue (main.js)
    this.$root.$data.根数据名;
    this.$root.$data.根数据名=值

7. 状态管理

    路由监听:数据观测|检测 watch   找一个根组件
    拦截器:插件读数据时(提交前|数据返回后)



8 动画
    vue-animate    vue add animate
        import varname form 'vue-animate'
    animate.css  npm i xxxx
        全局引入

8.5 路由守卫

    条件: user->同步->state

    login: dispatch -> actions -> mutations -> 修改state

    actions可以返回promise给调用dispatch的组件
        actions:return axios(..).then(commit(...))
        组件: this.$dispatch(..).then(()=>{业务})

9. 组件懒加载 | 异步路由 | 分片(块)打包

    原理: 利用webpack对代码进行分割是懒加载的前提,懒加载就是异步调用组件,需要时候才下载,
          告诉webpack把组件打包成块,告诉路由激活时触发一个函数,函数再加载组件,加载时会请求组件的块代码,块代码会插入当前组件的样式
    webpack配置:
        output:
            chunkFilename:'chunks/[name]-[chunkhash:8].js',//build之后的代码更便于识别
    路由配置:
        const home =()=>import(/* webpackChunkName: "group-home" */ "../components/home.vue");
        import 导入 需要安装 babel-plugin-syntax-dynamic-import ,import会被提升,先执行?
        配置 babelrc  "plugins": ["syntax-dynamic-import"]
        webpackChunkName: "group-home" 给块命名 | 同名会拆到一个块,可减少请求次数
    组件内部注册异步组件:
        const navbar =()=>import(/* webpackChunkName: "group-home" */ "./components/navbar.vue");
        components:{navbar}
        navbar 不异步的话,代码会打到app.js,而不是home块或者navbar块

    component: r => { require(['./login/Login'], r) }
    
10. 滚动行为
    VueRouter({scrollBehavior})
    scrollBehavior(to,from,savedPosition){
        return { x: 0, y: 0 }//对于所有路由导航,简单地让页面滚动到顶部
    }

    滚动到之前位置:
        模块变量: let top=0 | 存到 cookie | store | vue根
        destroyed:  top=document.documentElement.scrollTop;  
        mounted:    document.documentElement.scrollTop=top; 

12. sass
    SCSS 是 Sass 3 引入新的语法
    sass:
        使用换行和缩进

        $bg-color: #939
        .box
            background: #bg-color

    scss:
        使用分号和花括号
        $bg-color: #399;
        .box{
          background: $bg-color;
        }
    scss for vue-cli3脚手架 需要安装node-sass / sass-loader
        引入: <style lang="scss"  使用: <xx class="box"
        引入: <style lang="scss" module  使用: <xx class={$style.box}
        引入: <style lang="scss" scope  使用: <xx class=box

    引入sass全局变量?
        定义主题: $theme-color: '#300' -> base.scss -> assets
        配置webpack:
        1) Vuevue.config.js
            module.exports = {
              css: {
                loaderOptions: {
                  sass: {
                    data: `
                      @import "@/assets/css/base.scss";
                    `
                  }
                }
              }
            }
        2) node_modules/@vue/cli-service/lib/options.js
            css: {
                // extract: true,
                // modules: false,
                // localIdentName: '[name]_[local]_[hash:base64:5]',
                // sourceMap: false,
                loaderOptions: {
                  sass: {
                    data: `
                      @import "@/assets/css/base.scss";
                    `
                  }
                }
              }

    scss for vue-cli2脚手架 
        需要安装node-sass / sass-loader
        配置: 在build文件夹下的webpack.base.conf.js的rules里面添加配置
          {
            test: /\.scss$/,
            loaders: ["style-loader", "css-loader", "sass-loader"]
          }

        使用: <style lang="scss"  使用: <xx class="box"
        使用: <style lang="scss" module  使用: <xx class={$style.box}
        使用: <style lang="scss" scope  使用: <xx class={box}

    引入sass全局变量?
        定义主题: $theme-color: '#300' -> base.scss -> assets
        
        配置webpack: 在build/utils.js中修改配置
        scss: generateLoaders('sass').concat(
          {
            loader: 'sass-resources-loader',
            options: {
              resources: path.resolve(__dirname, '../src/assets/scss/base.scss')  //注意自己的路径
            }
          }
        ),
12.5 把请求独立成service模块: 组件 -> 服务 -> 发送action
    局部调用
        //service.js
        export default {
           getUserInfo(v){
              return v.axios...state
           }
        }
        //component.vue
        import service from 'path'
        //调用
        service.getUserInfo(this).then(业务)
    全局函数
        //service.js
        exports const install = function (Vue, options) {
            Vue.prototype.service= {
                getUserInfo(v){
                  v.axios...
                }
            }
        }

        //main.js
        import service from 'path'
        Vue.use(service)

        //components.vue
        this.service.getUserInfo(this)
13. 前后端分离
    

14. 购物车
    购物车actions内部state需要copy一份
15. echarts
16. 百度地图
17. 多页面cli3
18. nuxt
19. elementUi vant
20. 部署

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,992评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,212评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,535评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,197评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,310评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,383评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,409评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,191评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,621评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,910评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,084评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,763评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,403评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,083评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,318评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,946评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,967评论 2 351

推荐阅读更多精彩内容

  • 响应式布局的理解 响应式开发目的是一套代码可以在多种终端运行,适应不同屏幕的大小,其原理是运用媒体查询,在不同屏幕...
    懒猫_6500阅读 782评论 0 0
  • ## 框架和库的区别?> 框架(framework):一套完整的软件设计架构和**解决方案**。> > 库(lib...
    Rui_bdad阅读 2,899评论 1 4
  • 33、JS中的本地存储 把一些信息存储在当前浏览器指定域下的某一个地方(存储到物理硬盘中)1、不能跨浏览器传输:在...
    萌妹撒阅读 2,080评论 0 2
  • 一、 组件component 1. 什么是组件? 组件(Component)是 Vue.js 最强大的功能之一。组...
    饥人谷_Leonardo阅读 1,954评论 0 18
  • 基于Vue的一些资料 内容 UI组件 开发框架 实用库 服务端 辅助工具 应用实例 Demo示例 element★...
    尝了又尝阅读 1,144评论 0 1