mambat / blog

mambat - 博客
6 stars 0 forks source link

前端入门 #9

Open mambat opened 6 years ago

mambat commented 6 years ago

本文写于2016年06月22日,现在读起来仍能温故知新。

从模块化谈起

模块的定义

模块,又称构件,是能够单独命名并独立地完成一定功能的程序语句的集合(即程序代码和数据结构的集合体)。

模块的特征

前端为什么要模块化

image

Javascript模块化编程

原始写法


        function m1 () {
          //...
      }

       function m2 () {
          //...
       }
    

对象写法


        var module1 = new Object({
          _count : 0,
          m1 : function (){
            //...
          },
          m2 : function (){
            //...
          }
        });
    

立即执行函数表达式写法


      var module1 = (function(){
        var _count = 0;
        var m1 = function(){
          //...
        };
        var m2 = function(){
          //...
        };
        return {
          m1 : m1,
          m2 : m2
        };
      })();
    

放大模式


      var module1 = (function (mod){
        mod.m3 = function () {
          //...
        };
        return mod;
      })(module1);
    

宽放大模式



      var module1 = ( function (mod){
        //...
        return mod;
      })(window.module1 || {});
    

输入全局变量



          var module1 = (function ($) {
            //...
          })(jQuery);
    

思考


为什么需要模块化规范

大家都以同样的方式编写、加载模块,更易阅读和协作。

Javascript模块化规范


CommonJS



        var x = 5;
        var addX = function(value) {
          return value + x;
        };
        module.exports.addX = addX;
    

        var example = require('./demo.js');
                 // 6
        console.log(example.addX(1));
    

AMD - Asynchronous Module Definition




        define("module", ["dep1", "dep2"], 
            function(d1, d2) {  
                return someExportedValue;
            }
        );
    

        require(["module", "../file"], 
            function(module, file) { 
                // 
            }
        );  
    


CMD - Common Module Definition




UMD - Universal Module Definition



ES6 Module




不仅仅是Javascript


Webpack

Webpack是什么



借助 Babel 可以将 ES6 模块 编译成 CommonJS 和 AMD

Webpack的特点



在项目中使用Webpack



Hot Reload(HMR)



var express = require('express');
var webpack = require('webpack');
var config = require('./webpack.dev.conf');

var app = express();
var compiler = webpack(config);

var devMiddleware = require('webpack-dev-middleware')(compiler, {
  publicPath: config.output.publicPath,
  stats: {
    colors: true,
    chunks: false
  }
});

var hotMiddleware = require('webpack-hot-middleware')(compiler);
// force page reload when html-webpack-plugin template changes
compiler.plugin('compilation', function (compilation) {
  compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
    hotMiddleware.publish({action: 'reload'});
    cb();
  })
});

// serve webpack bundle output
app.use(devMiddleware);
// enable hot-reload and state-preserving
// compilation error display
app.use(hotMiddleware);
// serve pure static assets
app.use('/static', express.static('./static'));

Mock Server




var express = require('express');
var proxyMiddleware = require('http-proxy-middleware');

var app = express();

var proxyTable = {
  // http://thx.github.io/RAP/index_zh.html
  '/login': 'http://rap.taobao.org/mockjs/1986/'
};

// proxy api requests
Object.keys(proxyTable).forEach(function (context) {
  var options = proxyTable[context];
  if (typeof options === 'string') {
    options = {target: options};
  }
  app.use(proxyMiddleware(context, options));
});

Long-Term Caching



"There are only two hard things in Computer Science: cache invalidation and naming things."


Phil Karlton

output.filename

除了可以指定具体的文件名以外,还可以使用一些占位符,包括:

name

hash

chunkhash

Option 1: One hash for the bundle



{
    output: {
        filename: "output.[hash].bundle.js",
        chunkFilename: "[id].[hash].bundle.js"
    }
}

Option 2: One hash per chunk



{
    output: {
        filename: "output.[chunkhash].bundle.js",
        chunkFilename: "[id].[chunkhash].bundle.js"
    }
}

Code Splitting


代码分隔,按需加载。


import main from '../../main.js';
import OrderQuery from '../../views/order/OrderQuery';
import OrderDetail from '../../views/order/OrderDetail';

// 定义路由规则
let routeMap = {
  '/': {
    name: 'orderQuery',
    component: OrderQuery
  },
  '/order-detail': {
    name: 'orderDetail',
    // 异步加载会导致丢失Style.(未被抽取到common.css中,也不在当前的chunks bundle js中)
    component: function (resolve) {
      require(['../../views/order/OrderDetail'], resolve);
    }
    // component: OrderDetail
  }
};

// 启动应用
main.startApp(routeMap);

CommonsChunkPlugin




plugins: [
  // If 'chunks' option omitted then all entry chunks are selected
  new CommonsChunkPlugin({name: 'commons'}),
  // Mark meta.js as entry chunk instead commons.js.
  new CommonsChunkPlugin({name: 'meta', chunks: ['commons']})
]

UglifyJsPlugin



压缩和混淆代码,减小 Bundle 的大小,提高加载速度,节省流量。


  plugins: [
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false
      }
    })
  ]

HtmlWebpackPlugin



// generate dist index.html with correct asset hash for each entry.
Object.keys(baseConfig.entry).forEach(function (name) {
  var htmlConf = {
    filename: '../'.concat(name).concat('.html'),
    template: './src/index.html',
    inject: true,
    chunks: ['meta', 'commons', name],
    chunksSortMode: function (a, b) {
      var index = {'meta': 1, 'commons': 2};
      index[name] = 3;
      var ai = index[a.names[0]],
        bi = index[b.names[0]];
      return ai && bi ? ai - bi : -1;
    },
    minify: {
      // more options: https://github.com/kangax/html-minifier#options-quick-reference
      removeComments: true,
      collapseWhitespace: true
    }
  };
  baseConfig.plugins.push(new HtmlWebpackPlugin(htmlConf));
});

Loaders


loaders: [
  {
    test: /\.vue$/,
    loader: 'vue'
  },
  {
    test: /\.css$/,
    loader: 'vue-style!css!px2rem?remUnit=75' // 将 px 批量转成 rem
  },
  {
    test: /\.js$/,
    loader: 'babel',
    include: projectRoot,
    exclude: /node_modules/
  },
  {
    test: /\.html$/,
    loader: 'vue-html'
  },
  {
    test: /\.(png|jpg|gif|svg|woff2?|eot|ttf)(\?v=[0-9\.]+)?$/,
    loader: 'url',
    query: {
      limit: 10000,
      name: '[name].[ext]?[hash:7]'
    }
  }
]

响应式设计

保证尽可能多的用户都能正常访问网站。 渐进增强是保证基本内容能够正常展示的情况下,增强现代浏览器的用户体验。 优雅降级是保证老浏览器也能够正常展示网页内容,去除非必须的酷炫特效。

什么是响应式设计




适配不同终端



响应式布局



响应式图片



移动端适配

一些概念



物理像素(Physical Pixel)



一个物理像素是显示器(手机屏幕)上最小的物理显示单元,在操作系统的调度下,每一个物理像素都有自己的颜色值和亮度值。

设备独立像素(Density-Independent Pixel)




在不同的设备中,CSS 中的 1px 所代表的物理像素可能是不同的。

设备像素比(Device Pixel Ratio)



位图像素(Bitmap Pixel)



以 iPhone6s 为例



用一张图来说明




在不同的屏幕上(普通屏幕 vs Retina屏幕),CSS像素所呈现的大小(物理尺寸)是一致的,不同的是1个CSS像素所对应的物理像素个数是不一致的。

图片模糊





对于dpr=2的retina屏幕而言,1个位图像素对应于4个物理像素,由于单个位图像素不可以再进一步分割,所以只能就近取色,从而导致图片模糊。

对于图片高清问题,比较好的方案就是两倍图(@2x),如:200×300(CSS Pixel)img标签,就需要提供 400×600 的图片。

图片缺少锐利度





肉眼看上去虽然图片不会模糊,但是会觉得图片缺少一些锐利度,或者是有点色差(但还是可以接受的)。

  • 对比度:明暗
  • 锐利度:边缘
  • 清晰度:模糊

图片显示 Demo



100×100的图片,分别放在100×100,50×50,25×25的img容器中,在Retina屏幕下的显示效果:



只需上传大图(@2x),小图交给图片服务器处理,页面只要负责拼接 URL 即可。

图片高清适配方案



不同 dpr 下,加载不同尺寸的图片。


利用 CSS Media Queries 或 Javascript。

搭建图片服务器,通过url参数,控制图片质量,或将图片裁剪成不同的尺寸。

Q: Bootstrap 布局适配的原理?

布局适配方案



目前比较流行的、也是助手采用的布局适配方案是使用相对单位 rem。

rem 是什么




        html {
          font-size: 75px;
        }
    

        div {
          width: 2rem;
        }
    


div 的实际宽度 width = 75px * 2 (即 150px)

还得聊聊视觉稿



在前端开发之前,VD 会给我们一个psd文件,称之为视觉稿。


移动端开发中,为了做到页面高清的效果,视觉稿往往会遵循以下两点:



现在你应该知道,对于dpr=2的手机,为什么画布大小×2,就可以解决高清的问题了。

思考:对于2倍大小的视觉稿,在具体的CSS编码中如何还原每一个区块的真实宽高(也就是布局问题)?

rem 方案原理



针对不同屏幕尺寸dpr动态改变根节点html的font-size(基准值)。


document.documentElement.clientWidth * dpr / 10



iPhone4/5: 320px * 2 / 10 = 64px


iPhone6/6s: 375px * 2 / 10 = 75px

CSS方式动态改变根节点 html 的 font-size



/*iPhone 4/5*/
@media screen and (min-width: 320px) {
  html {
    font-size: 64px !important; /*no*/
  }
}

/*iPhone 6/6s*/
@media screen and (min-width: 375px) {
  html {
    font-size: 75px !important; /*no*/               缺点:不够精确,但是够用。
  }
}

/*iPhone 6p/6sp*/
@media screen and (min-width: 414px) {
  html {
    font-size: 124.2px !important; /*no*/
  }
}

JS方式动态改变根节点 html 的 font-size



let docEl = document.documentElement;
let fontEl = document.createElement('style');

let dpr = window.devicePixelRatio || 1;
let rem = docEl.clientWidth * dpr / 10;

// 动态写入样式
fontEl.innerHTML = 'html{font-size:' + rem + 'px!important;}';
docEl.firstElementChild.appendChild(fontEl);

// 给js调用的,某一dpr下rem和px之间的转换函数
window.rem2px = function (r) {                        精确计算rem基准值,但要加载一段 js
  r = parseFloat(r);
  return r * rem;
};
window.px2rem = function (p, base) {
  let r = p / (base || 75);
  return r.toFixed(6);
};
window.jspx = function (r) {
  r = parseFloat(r / 75.0);
  return r * rem;
};

http://teazean.com/2015/11/02/reflow&repaint

同时使用CSS方式JS方式



如何在 CSS 中还原视觉稿的真实宽高



基于 iPhone6s 的 750×1334 高清视觉稿中有一个宽高 750×300px 的 div。

由前面的公式可算出 iPhone6s 下 rem基准值 = 75px


如何自动适配 iPhone4s



由前面的公式可算出 iPhone4s 下 rem基准值 = 64px


px2rem 自动化



利用 Webpack 的 px2rem-loader 实现 px2rem 的自动转换。


发现问题了吗?




这意味着在高清屏上,目前的 CSS 使页面放大到了两倍,页面无法完全展示。

页面缩放



let docEl = document.documentElement;
let metaEl = document.querySelector('meta[name="viewport"]');

let dpr = window.devicePixelRatio || 1;
let scale = 1 / dpr;

// 设置viewport,进行缩放
metaEl.setAttribute('content', 
    'width=' + dpr * docEl.clientWidth + ',initial-scale=' + scale +
    ',maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no');

// 设置data-dpr属性,留作的css hack之用(如根据不同的 dpr 设置不同的 font-size)
docEl.setAttribute('data-dpr', dpr);

普通屏幕,页面不进行缩放;2倍高清屏时,页面缩放50%;3倍高清屏时。。。

思考:rem 方案先按物理像素进行设计,然后又进行页面缩放、适配 CSS像素,这么折腾到底为了什么?开发时直接按照 CSS像素进行页面布局不是也可以么?

字体大小问题



在讨论字体是否需要缩放时,通常要考虑设计师的要求:


字体大小的统一和缩放




        font-size: 16px; /* no */
        [data-dpr="2"] input {
          font-size: 32px; /* no */
        }
    

        font-size: 32px;
    


前提VD 按照 iPhone6s 的物理像素 750×1334px 进行设计,且视觉稿上 font-size 为32px。(左边进行了页面 scale,右边使用了 rem 方案适配)

左边font-size = dpr * 16px,结合页面scale = 1 / dpr,这就保证了字体在所有屏幕上的大小均为16px(CSS 像素)。

右边font-size = 32px / 75(rem基准值),这使得字体能够根据屏幕的分辨率进行等比缩放处理。

Retina下,border: 1px; 问题



这大概是设计师最敏感,最关心的问题了。



上图中,对于一条1px宽的直线,它们在屏幕上的物理尺寸(灰色区域)的确是相同的,不同的其实是屏幕上最小的物理显示单元,即物理像素,所以对于一条直线,iphone5它能显示的最小宽度其实是图中的红线圈出来的灰色区域,用css来表示,理论上说是0.5px。


设计师想要的 Retina 下 1px边框,其实是1物理像素。对于CSS而言,相当于0.5px边框,这是 Retina下(dpr=2)下能显示的最小单位。


然而,无奈并不是所有手机浏览器都能识别border: 0.5px; ios7以下,android等其他系统里,0.5px会被当成为0px处理,那么如何实现这0.5px呢?



CSS中仍然指定1px,但页面进行了1/dpr缩放,实际的物理像素仍是1px。

该特性最先由Apple引入,用于解决移动端的页面展示问题,后续被越来越多的厂商跟进。 http://www.quirksmode.org/mobile/viewports.html http://www.quirksmode.org/mobile/viewports2.html http://www.quirksmode.org/mobile/metaviewport http://weizhifeng.net/viewports.html http://weizhifeng.net/viewports2.html https://segmentfault.com/a/1190000004403527

viewport


先聊聊 PC 浏览器下的 viewport


缩放



Original & Zoom Out & Zoom In



在进行缩放时,元素的 CSS 像素不变,只是它占据的物理像素变化了,例如:

CSS 像素为100×100px的元素,放大到200%时,其 CSS 像素仍为100×100px,但其占用的物理像素为 200×200px,缩小亦同理。

思考 有没有遇到过,手机浏览器访问网站时,出现页面显示不完整 或 全部显示但元素非常小、看不清楚的情况。(有没有思考过背后的原因和解决方法)

屏幕尺寸





窗口尺寸





放大后(右图),window.innerWidth/Height 变小了,因为可放入这个浏览器窗口中的元素减少了。

滚动距离




用户向上滚动,然后放大(右图),浏览器保持相同的元素位于可见页面的顶部,window.pageYOffset 并没有真正的更改:被滚动出窗口的CSS像素的数量仍然(大概)是相同的。经实际验证,是有改变的!!

viewport的概念



viewport的功能是用来约束网站中最顶级包含块元素(containing block)<html> 的。


思考 假设页面上一个 div 具有 width: 10% 属性,现在这个 div 会随着浏览器窗口大小的调整而恰好的放大和收缩。但是这到底是如何工作的呢?

从技术上来说






所有web开发者都很直观的知道并且在使用这种技术。

从理论上来说




viewport 在桌面环境下只是拥有浏览器窗口的宽度和高度,在移动环境下它会有一些复杂。

思考 对这个 div 进行缩放会是什么效果?

度量 viewport




document.documentElement实际上指的是<html>元素,viewport 要比它更高一层,它是包含<html>元素的元素。

如果你给<html>元素设置width属性(如右图),document.documentElement.clientWidth/Height 给出的仍然是viewport的尺寸,,而不是<html>元素的。

两个属性对




事实上两个属性对的存在是浏览器战争的产物。最初Netscape只支持 inner、IE 只支持 client,后来所有其他浏览器都开始支持 client,但是 IE 却没有支持 inner。

在 PC 上拥有这两个属性对是有一些累赘的,BUT,就像我们将要看到的,在移动端这将会得到祝福。

度量<html>元素




事件中的坐标




pageX/Y提供了相对于<html>元素的以CSS像素度量的坐标。 clientX/Y提供了相对于viewport的以CSS像素度量的坐标。 screenX/Y提供了相对于屏幕的以物理像素进行度量的坐标。
90%的时间你将会使用pageX/Y,其他的10%时间你将会使用clientX/Y,但你永远不需要知道事件相对于屏幕的坐标。

媒体查询



你可以声明「只在页面宽度大于,等于或者小于一个特定尺寸的时候才会被执行」的特殊的CSS规则。

div.sidebar {
    width: 300px;
}

@media screen and (max-width: 400px) {
    // styles assigned when width is smaller than 400px
    div.sidebar {
        width: 100px;
    }
}
  • width/height使用 viewport 宽高(CSS 像素)。
  • device-width/device-height使用屏幕宽高(物理像素)。
  • 在桌面环境下去使用width而去忘记device-width吧。
  • IE 浏览器不支持媒体查询。

移动浏览器的问题



屏幕尺寸小,为 PC 设计的网站在移动浏览器中显示的内容明显要少。



viewport 太窄,以至于不能正常展示你的 CSS 布局(不管是固定宽度、百分比 或 流式布局)。

两个 viewport



明显的解决方案是使 viewport 变宽一些,这样就产生了两个视口:



George Cummins在Stack Overflow上给出了最佳解释:


layout viewport 想像成为一张不会改变大小和形状的大图。
在想像你有一个小一些的框子,你通过它来看这张大图。(可以理解为「管中窥豹」),这个小框子的周围被不透明的材料所包围,遮挡住了你的视线,你只能看到这张大图的一部分。
通过这个框子所能看到的大图的部分就是 visual viewport。
过远离(缩小)/ 靠近(放大)这种方式,你可以调整所能看大图的内容多少,但是大图(layout viewport)的大小和形状永远不会变。

layout viewport





由于宽度趋近,CSS 只需要像在 PC 上那样渲染页面就行,原有的页面结构不会被破坏。

visual viewport



无论怎样,CSS 布局,尤其是百分比宽度,是以 layout viewport 做为参照系来计算的,它被认为要比 visual viewport 宽。

缩放




如果不是这样,缩放时layout viewport尺寸发生变化,那你的页面将会像百分比宽度被重新计算一样而经常被重新布局。

理解这两个 viewport








度量 layout viewport



朝向会对高度产生影响,但对宽度不会产生影响。

度量 visual viewport




滚动距离 scrolling offset




度量<html>元素




事件坐标



媒体查询



viewport meta标签











viewport meta 标签的6个属性




比如:<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"/>

ideal viewport(完美视口)



<meta name="viewport" content="width=device-width, initial-scale=1.0"/>


device-width将 layout viewport 设置为屏幕的宽度,单位是 CSS 像素。

在指定了initial-scale=1.0时,可省略width=device-width

前端开发模式演变

简单明快的早期时代(Web 1.0 时代)






后端为主的 MVC 时代







2004 年 Gmail 像风一样的女子来到人间,很快 2005 年 Ajax 正式提出,加上 CDN 开始大量用于静态资源存储,于是出现了 JavaScript 王者归来的 SPA (Single Page Application 单页面应用)时代。

Ajax 带来的 SPA 时代








Backbone的Model把服务器端的数据模型映射到浏览器端,绑定数据验证机制,并与相应的REST操作绑定。
Backbone的Model没有与UI视图数据绑定,而是需要在JS中自行操作DOM来更新或读取UI数据。

前端为主的 MV* 时代







Node 带来的全栈时代







Bigpipe:

http://www.ifeenan.com/javascript/2014-07-20-%E5%89%8D%E7%AB%AF%E9%A1%B5%E9%9D%A2%E4%BC%98%E5%8C%96%E4%B9%8BBigpipe/





一些软件设计的原则:http://coolshell.cn/articles/4535.html

前端开发模式总结






思考


页面性能优化

Best Practices and Rules



Content


Make Fewer HTTP Requests



减少 http 请求数


DNS系统是从域名到 IP 地址映射系统,DNS 解析一次大约20-120毫秒,在这个时间内浏览器不能下载任何东西。

Reduce DNS Lookups



减少 DNS 解析时间




Avoid Redirects



避免使用重定向


使用重定向会降低用户体验,它会使加载延时。

Make Ajax Cacheable



缓存 ajax

Post-load/Preload Components



延迟/提前 加载模块



Reduce the Number of DOM Elements



减少dom的element数量

查看dom的元素数 document.getElementsByTagName('*').length

Split Components Across Domains



把资源分散到多个域名下


把资源分散到多个域名下可以尽量多的并行下载,但是不要超过2-4个域名,因为 DNS 解析会也有一定的损耗。

Avoid 404s



避免404


Server


Use a Content Delivery Network



使用 CDN 内容分发网络系统



Cache-Control: https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=zh-cn#cache-control

Add an Expires or a Cache-Control Header



为http头添加Expires或Cache-Control


Gzip Components



使用 Gzip 传输部件




Use GET for Ajax Requests



Ajax 请求使用 Get 方法



Avoid Empty Image src



避免 src 为空的 Image 元素


Cookie


Reduce Cookie Size



减小 Cookie 长度


客户端在跟服务端通信时,会带上Cookie信息,所以减小 Cookie 长度,可以一定程度的降低响应时间。

Use Cookie-free Domains for Components



把静态资源放在无 Cookie 的域名上



CSS


Put Stylesheets at Top



把 css 放在 head 里



Avoid CSS Expressions



避免在 css 中使用 js 表达式

Background-color:expression((new Date).getHours()%2?'#000':'#fff');


Choose over @import



加载 css 时,要用 link,不要用 @import


在 IE 上用 @import 的作用与把 放入网页底部的作用是一样的,所以不要使用。

Javascript


Put Scripts at Bottom



把 js 放在底部


如果一个 js 脚本可以被推迟加载,那就把它放到页面最下面吧,这样会让你的页面以最快的速度展现给用户。

Make JavaScript and CSS External



把 js 和 css 放入外部文件




Minify JavaScript and CSS



最少化(压缩) js/css 源码



Remove Duplicate Scripts



去除重复的 js 脚本



Minimize DOM Access



减少 DOM 查询

Image


Optimize Images



优化图片 - 无损压缩

Optimize CSS Sprites



优化雪碧图


Do Not Scale Images in HTML



不要在html里缩放图片


当你需要100×100的图片时,不要使用500×500的图片进行缩放,直接提供一张100×100图片就好。

Make favicon.ico Small and Cacheable



让 favicon.ico 小一点儿(1K以下),并且可缓存(缓存几个月)



检测工具


PageSpeed Insights


YSlow


Chrome DevTools


Frontend Knowledge Structure



Frontend Books



Commit message




了解更多

huhuhuHR commented 6 years ago

磊哥这个写的真好,膜拜