360前端星計劃0411

前端工程化淺析

1.前言:什么是前端工程化

1.1目標

在前端領域的妖,利用技術不斷進步和經(jīng)驗逐步積累帶來的各種方案兑燥,來解決在項目的開發(fā)鱼炒、測試泥彤、維護階段中遇到的種種低效和繁瑣的問題欲芹。

1.2技術

工程化是一種思想,技術是一種實踐吟吝。技術會隨著時代進步不斷地演進和改變菱父,在不同時期,都會有不同地技術來承載和踐行著工程化地思想剑逃。

1.3原因

前端工程化就是為了提效浙宜。這個提效體現(xiàn)在項目地開發(fā)、測試及維護階段蛹磺。
前端工程化的好處
規(guī)范化梆奈、模塊化、組件化称开、自動化

2.規(guī)范化

規(guī)范化是項目可維護的基石

  • 版本管理及開發(fā)流程規(guī)范
  • 編寫規(guī)范
    • 腳本
    • 樣式
    • 目錄結(jié)構(gòu)

版本規(guī)范化的開發(fā)過程

git
版本管理/代碼倉庫
git flow

  • 基于git/簡化了git的操作
  • 活動模型/行為規(guī)范
    git flow的開發(fā)流程如下圖所示


    image.png

    流程如下:

git flow init //初始化一個項目
git branch //生成分支亩钟,一個master乓梨,一個develop
git checkout develop //切換到develop分支進行開發(fā)

切換完成后輸入以下命令

git pull origin develop
//基于develop新建一個叫f1的
feature分支(是git checkout develop git checkout -b feature/f1的縮寫)
git flow festure start f1
//開發(fā)完成后提交代碼
git commit -am 'ADD#PRO-01#new func'
git push origin feature/f1
//將f1上新增的合并到develop上
git flow feature finish f1

開發(fā)完成后可以把develop上的內(nèi)容合并到

git checkout master
git pull origin master
//先將develop分支上的內(nèi)容放到release上,若這時候發(fā)現(xiàn)了錯誤可以進行修改清酥,修改完成提交后會將修改同步到master和develop上
git checkout release/0.0.1
git flow release finish 0.0.1

當線上有一些緊急的bug時可以放到hotfix上去修改

git checkout master
git flow hotfix start fix1
//修改完成后用git finish 可以將修改保存到master和develop

3.模塊化

一般將邏輯相關的代碼放到同一個文件中扶镀,當作一個模塊。
只需關注模塊內(nèi)邏輯的實現(xiàn)焰轻,無需考慮變量污染等問題臭觉,模塊之間可互相調(diào)用。

3.1 CSS模塊化解決方案

核心思想通過樣式生效規(guī)則來避免沖突

scoped

它的原理就是給DOM節(jié)點添加data-v-version屬性
.selector =>.selector[data-v-version]

CSS in JS

這個是一種思想辱志。以腳本模塊來寫樣式蝠筑,甚至有封裝好的樣式模塊可以直接使用。
樣式 => 按規(guī)則生成的唯一selector

CSS MODULES

借助預編譯使樣式成為腳本中的變量
.selector => Object.selector|.selector => .main__sub__hash

BEM(Block__Element-Modifier)

按照規(guī)則揩懒,手寫css什乙,并在模板內(nèi)增加相應class
優(yōu)雅的使用BEM


image.png

Shadow DOM

為元素建宇shadow root ,使內(nèi)部樣式與外部樣式完全隔離

3.2js模塊化解決方案

有兩個成熟的框架。一個是nodejs,帶來了comminJs規(guī)范已球。
還有一個是從二手開始的Moudle-loader規(guī)范

4.組件化

組件化和模塊化的核心思想都在于分治臣镣,實際帶啦的好處就是團隊協(xié)作效率和項目可維護性的提升
組件化開發(fā)時Web開發(fā)的趨勢

4.1什么是組件

4.1.1UI為主

頁面上的一個UI塊可以封裝成一個組件。比如頁面的頭部智亮,封裝成一個Header組件后忆某,我希望它的腳本、樣式和模板可以放在一個文件夾中阔蛉,到時候便于維護弃舒。

4.1.2 邏輯為主

某一個功能邏輯也可以封裝成一個組件。封裝成一個組件后状原,我希望它的腳本棒坏、樣式和模板可以放在一個文件夾中,可以一處封裝遭笋,多處任意使用。
在Web前端領域徒探,可以將由特定邏輯和UI進行的高內(nèi)聚瓦呼,低耦合的封裝體稱為一個組件。
側(cè)重UI進行封裝的組件:代碼結(jié)構(gòu)清晰测暗,組件內(nèi)的模塊就近放置央串,方便進行修改和維護。這種組件具備高內(nèi)聚碗啄,低耦合的特性质和,但普適性不高。
側(cè)重邏輯進行封裝的組件:除了具備上述優(yōu)點外稚字,還有很高的普適性饲宿,更方便組件重用
組件內(nèi)可以包含組件:偏UI的組件往往都是包含有偏邏輯的組件厦酬。

5.自動化

核心思想:能由機器自動完成的事情,絕不讓人來做瘫想。自動化是前端工程化的核心

  • 自動初始化eg.:vue-cli
  • 自動構(gòu)建(打包)eg.:webpack
  • 自動測試 eg.:karma,jest
  • 自動部署eg.:Jenkins

5.1自動化測試

前端測試分類

這個圖當中越往上與邏輯越不相關仗阅,越往下與邏輯越相關

5.2自動化部署

自動化部署方案

5.3自動化初始化

通過腳手架自動完成項目初始化,迅速搭建一個項目国夜。

5.4自動化構(gòu)建

工具有webpack减噪、PARCEL

5.5自動化示例:360搜索專題頁開發(fā)工具

這個工具的訴求如下:

自動化訴求

為實現(xiàn)上述需求,開發(fā)一個CLI车吹,專門負責項目初始化和上線發(fā)布
配置一個支持多項目打包的webpack工程筹裕,滿足預編譯的需求
開發(fā)一個基于webpack4的插件,將靜態(tài)資源上傳至公司CDN
寫一個基于Node.js的CLI
image.png

用以下命令捕獲用戶輸入的參數(shù)和命令窄驹,并獲得參數(shù)觸發(fā)回調(diào)

const programe = require('commander')
program.on('--help',_=>{})
program.command('init').action((name,options) => {})

通過以下代碼觸發(fā)詢問與用戶交互

const inquirer = require('inquirer');
inquirer.prompt({
  type:'confirm',
name:'name',
message:'是否將產(chǎn)品發(fā)布至線上'朝卒,
default:true
}).then(anser =>{})

通過以下代碼幫助執(zhí)行命令,例如發(fā)送HTTP請求

const child_process = require('child_process');
const HTTP = require('http');

增強交互效果

const chalk = require('chalk');
console.log(chalk.redBright('專題名稱已被使用馒吴,請重新輸入'));
const ora = require('ora');
const spinner = ora('正在加載中').start();
setTimeout(_ => {
    spinner.text = '加載完成'扎运;
    spinner.succeed();
},1000);

使用webpack4進行項目構(gòu)建

image.png

webpack4核心參數(shù)配置

建議寫法
  • 將不同環(huán)境的配置進行區(qū)分
  • 集成進來的工具的插件配置單獨放置
  • evn配置使用.browserslistrc文件單獨放置

前端動畫還可以這樣玩

1.JS動畫的基本原理

1.定時器改變對象的屬性
2.根據(jù)新的屬性重新渲染動畫

function update(context) {
   // 更新屬性
}

const ticker = new Ticker();
ticker.tick(update, context);

動畫的種類

1.JavaScript 動畫
- 操作DOM
- Canvas
2.CSS 動畫
- transition
- animation

  1. SVG 動畫
    • SMIL

JS動畫的優(yōu)缺點

優(yōu)點:
- 靈活度
- 可控性
- 性能
缺點:
- 易用性差

簡單動畫

通過以下代碼實現(xiàn)小方塊的旋轉(zhuǎn)

let rotation = 0;
requestAnimationFrame(function update() {
  block.style.transform = `rotate(${rotation++}deg)`;
  requestAnimationFrame(update);
});

這樣存在一個問題不能很好的精確控制速度

另一個版本

let rotation = 0;
let startTime = null;
const T = 2000;
requestAnimationFrame(function update() {
  if(!startTime) startTime = Date.now();
  const p = (Date.now() - startTime)/T;
  block.style.transform = `rotate(${360 * p}deg)`;
  requestAnimationFrame(update);
});

通用化

function update({target}, count) {
  target.style.transform = `rotate(${count++}deg)`;
}

class Ticker {
  tick(update, context) {
    let count = 0;
    requestAnimationFrame(function next() {
      if(update(context, ++count) !== false) {
        requestAnimationFrame(next);
      }
    });
  }
}

const ticker = new Ticker();
ticker.tick(update, {target: block});

通用化2

既可以用target實現(xiàn)又可以用time實現(xiàn)

function update({target}, {time}) {
  target.style.transform = `rotate(${360 * time / 2000}deg)`;
}

class Ticker {
  tick(update, context) {
    let count = 0;
    let startTime = Date.now();
    requestAnimationFrame(function next() {
      count++;
      const time = Date.now() - startTime;
      if(update(context, {count, time}) !== false) {
        requestAnimationFrame(next);
      }
    });
  }
}

const ticker = new Ticker();
ticker.tick(update, {target: block});

通用化3

function update({context}, {time}) {
  context.clearRect(0, 0, 512, 512);
  context.save();
  context.translate(100, 100);
  context.rotate(time * 0.005);
  context.fillStyle = '#00f';
  context.fillRect(-50, -50, 100, 100);
  context.restore();
}

class Ticker {
  tick(update, context) {
    let count = 0;
    let startTime = Date.now();
    requestAnimationFrame(function next() {
      count++;
      const time = Date.now() - startTime;
      if(update(context, {count, time}) !== false) {
        requestAnimationFrame(next);
      }
    });
  }
}

Timing

將上述封裝成一個更強大的類

class Timing {
  constructor({duration, easing} = {}) {
    this.startTime = Date.now();
    this.duration = duration;
    this.easing = easing || function(p){return p};
  }
  get time() {
    return Date.now() - this.startTime;
  }
  get p() {
    return this.easing(Math.min(this.time / this.duration, 1.0));
  }
}

class Ticker {
  tick(update, context, timing) {
    let count = 0;
    timing = new Timing(timing);
    requestAnimationFrame(function next() {
      count++;
      if(update(context, {count, timing}) !== false) {
        requestAnimationFrame(next);
      }
    });

勻速運動

實現(xiàn)2s內(nèi)向右勻速運動200px

function update({target}, {timing}) {
  target.style.transform = `translate(${200 * timing.p}px, 0)`;
}

const ticker = new Ticker();
ticker.tick(update, 
  {target: block}, 
  {duration: 2000}
);

自由落體運動實現(xiàn)

速度從0開始增加的一個加速運動

function update({target}, {timing}) {
  target.style.transform = `translate(0, ${200 * timing.p}px)`;
}

const ticker = new Ticker();
ticker.tick(update, {target: block}, {
  duration: 2000,
  easing: p => p ** 2,
});

摩擦力實現(xiàn)

把速度從一個開始的值減到0

function update({target}, {timing}) {
  target.style.transform = `translate(${200 * timing.p}px, 0)`;
}

const ticker = new Ticker();
ticker.tick(update, {target: block}, {
  duration: 2000,
  easing: p => p * (2 - p),
});

平拋

把x軸和y軸的速度區(qū)分開

class Timing {
  constructor({duration, easing} = {}) {
    this.startTime = Date.now();
    this.duration = duration;
    this.easing = easing || function(p){return p};
  }
  get time() {
    return Date.now() - this.startTime;
  }
  get op() {
    return Math.min(this.time / this.duration, 1.0);
  }
  get p() {
    return this.easing(this.op);
  }
}

function update({target}, {timing}) {
  target.style.transform = 
    `translate(${200 * timing.op}px, ${200 * timing.p}px)`;
}

旋轉(zhuǎn)+平拋

function update({target}, {timing}) {
  target.style.transform = `
    translate(${200 * timing.op}px, ${200 * timing.p}px)
    rotate(${720 * timing.op}deg)
  `;
}

貝塞爾軌跡

function bezierPath(x1, y1, x2, y2, p) {
  const x = 3 * x1 * p * (1 - p) ** 2 + 3 * x2 * p ** 2 * (1 - p) + p ** 3;
  const y = 3 * y1 * p * (1 - p) ** 2 + 3 * y2 * p ** 2 * (1 - p) + p ** 3;
  return [x, y];
}

function update({target}, {timing}) {
  const [px, py] = bezierPath(0.2, 0.6, 0.8, 0.2, timing.p);
  target.style.transform = `translate(${100 * px}px, ${100 * py}px)`;
}

const ticker = new Ticker();
ticker.tick(update, {target: block}, {
  duration: 2000,
  easing: p => p * (2 - p),
});

bezier-easing

  • B(px) 作為輸入, B(py) 作為輸出
  • 通過牛頓迭代饮戳,從B(px)求p豪治,從p求B(py)
function update({target}, {timing}) {
  target.style.transform = `translate(${100 * timing.p}px, 0)`;
}

const ticker = new Ticker();
ticker.tick(update, {target: block}, {
  duration: 2000,
  easing: BezierEasing(0.5, -1.5, 0.5, 2.5),
});

bezier-easing 軌跡

function update({target}, {timing}) {
  target.style.transform =
    `translate(${100 * timing.p}px, ${100 * timing.op}px)`;
}

const ticker = new Ticker();
ticker.tick(update, {target: block}, {
  duration: 2000,
  easing: BezierEasing(0.5, -1.5, 0.5, 2.5),
});

橢圓軌跡

周期運動

class Timing {
  constructor({duration, easing, iterations = 1} = {}) {
    this.startTime = Date.now();
    this.duration = duration;
    this.easing = easing || function(p){return p};
    this.iterations = iterations;
  }
  get time() {
    return Date.now() - this.startTime;
  }
  get finished() {
    return this.time / this.duration >= 1.0 * this.iterations;
  }
  get op() {
    let op = Math.min(this.time / this.duration, 1.0 * this.iterations);
    if(op < 1.0) return op;
    op -= Math.floor(op);
    return op > 0 ? op : 1.0;
  }
  get p() {
    return this.easing(this.op);
  }
}

橢圓周期運動

小球轉(zhuǎn)10周停止

function update({target}, {timing}) {
  const x = 150 * Math.cos(Math.PI * 2 * timing.p);
  const y = 100 * Math.sin(Math.PI * 2 * timing.p);
  target.style.transform = `
    translate(${x}px, ${y}px)
  `;
}

const ticker = new Ticker();
ticker.tick(update, {target: block},
  {duration: 2000, iterations: 10});

連續(xù)運動

返回一個promise,用await來逐步執(zhí)行

class Ticker {
  tick(update, context, timing) {
    let count = 0;
    timing = new Timing(timing);
    return new Promise((resolve) => {
      requestAnimationFrame(function next() {
        count++;
        if(update(context, {count, timing}) !== false && !timing.finished) {
          requestAnimationFrame(next);
        } else {
          resolve(timing);
        }
      });      
    });
  }
}
function left({target}, {timing}) {
  target.style.left = `${100 + 200 * timing.p}px`;
}
function down({target}, {timing}) {
  target.style.top = `${100 + 200 * timing.p}px`;
}
function right({target}, {timing}) {
  target.style.left = `${300 - 200 * timing.p}px`;
}
function up({target}, {timing}) {
  target.style.top = `${300 - 200 * timing.p}px`;
}

(async function() {
  const ticker = new Ticker();
  await ticker.tick(left, {target: block},
    {duration: 2000});
  await ticker.tick(down, {target: block},
    {duration: 2000});
  await ticker.tick(right, {target: block},
    {duration: 2000});
  await ticker.tick(up, {target: block},
    {duration: 2000});
})();

線性插值(lerp)

function lerp(setter, from, to) {
  return function({target}, {timing}) {
    const p = timing.p;
    const value = {};
    for(let key in to) {
      value[key] = to[key] * p + from[key] * (1 - p);
    }
    setter(target, value);
  }
}

可以調(diào)用這個函數(shù)更方便的實現(xiàn)前面的功能

function setValue(target, value) {
  for(let key in value) {
    target.style[key] = `${value[key]}px`;
  }
}

const left = lerp(setValue, {left: 100}, {left: 300});
const down = lerp(setValue, {top: 100}, {top: 300});
const right = lerp(setValue, {left: 300}, {left: 100});
const up = lerp(setValue, {top: 300}, {top: 100});

(async function() {
  const ticker = new Ticker();
  await ticker.tick(left, {target: block},
    {duration: 2000});
  await ticker.tick(down, {target: block},
    {duration: 2000});
  await ticker.tick(right, {target: block},
    {duration: 2000});
  await ticker.tick(up, {target: block},
    {duration: 2000});
})();

彈跳的小球

const down = lerp(setValue, {top: 100}, {top: 300});
const up = lerp(setValue, {top: 300}, {top: 100});

(async function() {
  const ticker = new Ticker();
  
  // noprotect
  while(1) {
    await ticker.tick(down, {target: block},
      {duration: 2000, easing: p => p * p});
    await ticker.tick(up, {target: block},
      {duration: 2000, easing: p => p * (2 - p)});
  }
})();

彈跳的小球2

給彈跳加一個衰減

(async function() {
  const ticker = new Ticker();
  let damping = 0.7,
      duration = 2000,
      height = 300;

  // noprotect
  while(height >= 1) {
    let down = lerp(setValue, {top: 400 - height}, {top: 400});
    await ticker.tick(down, {target: block},
      {duration, easing: p => p * p});
    height *= damping ** 2;
    duration *= damping;
    let up = lerp(setValue, {top: 400}, {top: 400 - height});
    await ticker.tick(up, {target: block},
      {duration, easing: p => p * (2 - p)});
  }
})();

滾動

const roll = lerp((target, {left, rotate}) => {
    target.style.left = `${left}px`;
    target.style.transform = `rotate(${rotate}deg)`;
  },  
  {left: 100, rotate: 0}, 
  {left: 414, rotate: 720});


const ticker = new Ticker();

ticker.tick(roll, {target: block},
  {duration: 2000, easing: p => p});

平穩(wěn)變速

function forward(target, {y}) {
  target.style.top = `${y}px`;
}

(async function() {
  const ticker = new Ticker();

  await ticker.tick(
    lerp(forward, {y: 100}, {y: 200}), 
    {target: block},
    {duration: 2000, easing: p => p * p}); 

  await ticker.tick(
    lerp(forward, {y: 200}, {y: 300}), 
    {target: block},
    {duration: 1000, easing: p => p}); 

  await ticker.tick(
    lerp(forward, {y: 300}, {y: 350}), 
    {target: block},
    {duration: 1000, easing: p => p * (2 - p)}); 
}());

甩球

function circle({target}, {timing}) {
  const p = timing.p;
  const rad = Math.PI * 2 * p;

  const x = 200 + 100 * Math.cos(rad);
  const y = 200 + 100 * Math.sin(rad);
  target.style.left = `${x}px`;
  target.style.top = `${y}px`;
}
function shoot({target}, {timing}) {
  const p = timing.p;
  const rad = Math.PI * 0.2;
  const startX = 200 + 100 * Math.cos(rad);
  const startY = 200 + 100 * Math.sin(rad);
  const vX = -100 * Math.PI * 2 * Math.sin(rad);
  const vY = 100 * Math.PI * 2 * Math.cos(rad);
  
  const x = startX + vX * p;
  const y = startY + vY * p;

  target.style.left = `${x}px`;
  target.style.top = `${y}px`;
}
(async function() {
  const ticker = new Ticker();

  await ticker.tick(circle, {target: block},
    {duration: 2000, easing: p => p, iterations: 2.1}); 
  await ticker.tick(shoot, {target: block},
    {duration: 2000});
}());

逐幀動畫

使用background-position來改變圖片位置
使用SetInterval()每隔一段時間換一次class

<style type="text/css">
.sprite {
  display:inline-block; 
  overflow:hidden; 
  background-repeat: no-repeat;
  background-image:url(https://p.ssl.qhimg.com/t01f265b6b6479fffc4.png);
}

.bird0 {width:86px; height:60px; background-position: -178px -2px}
.bird1 {width:86px; height:60px; background-position: -90px -2px}
.bird2 {width:86px; height:60px; background-position: -2px -2px}

 #bird{
   position: absolute;
   left: 100px;
   top: 100px;
   zoom: 0.5;
 }
</style>
<div id="bird" class="sprite bird1"></div>
<script type="text/javascript">
var i = 0;
setInterval(function(){
  bird.className = "sprite " + 'bird' + ((i++) % 3);
}, 1000/10);
</script>

Web Animation API(Working Draft)

傳入關鍵幀扯罐,與CSS是對應的

element.animate(keyframes, options);
target.animate([
  {backgroundColor: '#00f', width: '100px', height: '100px', borderRadius: '0'},
  {backgroundColor: '#0a0', width: '200px', height: '100px', borderRadius: '0'},
  {backgroundColor: '#f0f', width: '200px', height: '200px', borderRadius: '100px'},
], {
  duration: 5000,
  fill: 'forwards',
});

封裝成promise负拟,達到逐個小球運動的效果

function animate(target, keyframes, options) {
  const anim = target.animate(keyframes, options);
  return new Promise((resolve) => {
    anim.onfinish = function() {
      resolve(anim);
    }
  });
}

(async function() {
  await animate(ball1, [
    {top: '10px'},
    {top: '150px'},
  ], {
    duration: 2000,
    easing: 'ease-in-out',
    fill: 'forwards',
  });

  await animate(ball2, [
    {top: '200px'},
    {top: '350px'},
  ], {
    duration: 2000,
    easing: 'ease-in-out',
    fill: 'forwards',
  });
 
  await animate(ball3, [
    {top: '400px'},
    {top: '550px'},
  ], {
    duration: 2000,
    easing: 'ease-in-out',
    fill: 'forwards',
  });
}());

一起優(yōu)化前端性能

原因
與用戶體驗相關,決定了用戶去留歹河。希望發(fā)現(xiàn)網(wǎng)站的性能瓶頸掩浙,從而提升用戶體驗

1.RAIL模型

1.1 RAIL模型的概念

它是一個以用戶為中心的性能模型,將用戶行為分為4個方面:

  • Response
  • Animation
  • ldle
  • Load
    每個網(wǎng)絡應用都具有與其生命周期相關的4個方面秸歧,而這些方面以不同的方式影響著性能厨姚。
    它的內(nèi)容有兩個部分:
  • 目標
    是一種恒定性的指標,因為人類對外界的感知是恒定的
  • 指導意見
    是一些針對性能的評估標準键菱。這些標準往往依賴當時的硬件等因素谬墙。
    延遲與用戶反應:
    100ms 以內(nèi)用戶會感覺可以立即獲得結(jié)果。
    超過1s用戶注意力會離開他們正在執(zhí)行的任務经备。

響應:50ms處理事件

目標

在100ms內(nèi)響應用戶輸入

指導

  • 50ms內(nèi)處理用戶輸入事件拭抬,確保100ms內(nèi)反饋用戶可視的響應
  • 對于開銷大的任務可分隔任務處理,或放到worker進程中執(zhí)行侵蒙,避免影響用戶交互
  • 處理時間超過50ms的操作造虎,始終給予反饋(進度和活動指示器)

動畫:10ms處理事件

目標

  • 10ms或更短時間內(nèi)生成一幀
  • 視覺平滑

指導

  • 在動畫這樣的高壓點,盡量不要處理邏輯纷闺。提高達到60fps的機會
  • 動畫類型
    • 滾動
    • 視覺動畫
    • 拖拽動畫

空閑時間最大化

目標

最大化空閑時間以增加頁面在100ms內(nèi)響應用戶輸入的幾率

指導

  • 利用空閑時間完成推遲的工作
  • 空閑時間期間用戶交互優(yōu)先級最高
    關鍵指標
    1.響應:在100ms內(nèi)響應用戶輸入
    2.動畫:動畫或滾動時算凿,10ms產(chǎn)生一幀
    3.空閑時間:主線程空閑時間最大化
    4.加載:在1000ms內(nèi)呈現(xiàn)交互內(nèi)容
    5.以用戶為中心

2.工具篇

Lighthouse

可以選擇是移動端還是客戶端份蝴。它進行評估后會給出一些性能優(yōu)化的建議

WebPageTest

是一個在線的網(wǎng)站

Chorme DEvTools

3.實戰(zhàn)篇

3.1瀏覽器渲染場景

瀏覽器渲染場景

csstriggers.com可以查看每個屬性影響的范圍

3.2瀏覽器渲染流程

  • JS(實現(xiàn)動畫,操作DOM)
  • Style(產(chǎn)出渲染樹)
  • Layout(盒模型澎媒,確切的位置和大懈惴Α)
  • Paint(柵格化,完整顯示)
  • Composite(渲染層合并)


    性能面板
  • 在sources中可以查看代碼的耗時情況
  • 優(yōu)化方向:盡量不要在設置樣式之后讀取它的樣式屬性
  • 用transform屬性來移動元素

3.3性能優(yōu)化方向

  • 加載
    • 資源效率優(yōu)化
    • 圖片優(yōu)化
    • 字體優(yōu)化
    • 關鍵渲染路徑優(yōu)化
  • 渲染
    • JS執(zhí)行優(yōu)化
    • 避免大型復雜的布局
    • 渲染層合并
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末戒努,一起剝皮案震驚了整個濱河市请敦,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌储玫,老刑警劉巖侍筛,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異撒穷,居然都是意外死亡匣椰,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進店門端礼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來禽笑,“玉大人,你說我怎么就攤上這事蛤奥〖丫担” “怎么了?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵凡桥,是天一觀的道長蟀伸。 經(jīng)常有香客問我,道長缅刽,這世上最難降的妖魔是什么啊掏? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮衰猛,結(jié)果婚禮上迟蜜,老公的妹妹穿的比我還像新娘。我一直安慰自己啡省,他們只是感情好娜睛,可當我...
    茶點故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著冕杠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪酸茴。 梳的紋絲不亂的頭發(fā)上分预,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天,我揣著相機與錄音薪捍,去河邊找鬼笼痹。 笑死配喳,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的凳干。 我是一名探鬼主播晴裹,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼救赐!你這毒婦竟也來了涧团?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤经磅,失蹤者是張志新(化名)和其女友劉穎泌绣,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體预厌,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡阿迈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了轧叽。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片苗沧。...
    茶點故事閱讀 39,926評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖炭晒,靈堂內(nèi)的尸體忽然破棺而出待逞,到底是詐尸還是另有隱情,我是刑警寧澤腰埂,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布飒焦,位于F島的核電站,受9級特大地震影響屿笼,放射性物質(zhì)發(fā)生泄漏牺荠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一驴一、第九天 我趴在偏房一處隱蔽的房頂上張望休雌。 院中可真熱鬧,春花似錦肝断、人聲如沸杈曲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽担扑。三九已至,卻和暖如春趣钱,著一層夾襖步出監(jiān)牢的瞬間涌献,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工首有, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留燕垃,地道東北人枢劝。 一個月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像卜壕,于是被迫代替她去往敵國和親您旁。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,871評論 2 354

推薦閱讀更多精彩內(nèi)容