Web basis summary

basic of front-end development

Posted by Chaoran on February 27, 2018

“To be is to be perceived. ”

1. JavaScript

1.1. 基础语法

包括:变量声明、数据类型、函数、控制语句、内置对象等

1.1.1. 变量声明

ES5:

var         //普通变量 
function    //函数

ES6新增:

let         //普通变量
const       //静态变量
import      //模块
class       //类
—数据类型
– 类型介绍

ES5: String、Number、Boolean、Null、Undefined、Object、Array ES6增: Symbol

其中,object为引用,其他为基本类型

  • 基本类型

    • 占据空间固定,是简单的数据段,将其存储在(stack)中(按值访问)

    • 便于提升变量查询速度

    • 为了便于操作这类数据,ECMAScript提供了 3 个 基本包装类型 :Boolean、Number 和 String

      • 基本包装类型

        一种特殊的引用类型,每当读取一个基本类型值的时候,JS内部就会创建一个对应的包装对象,从而可以调用一些方法来操作这些数据

  • 引用类型

    • 由于其值的大小会改变,所以不能将其存放在栈中,否则会降低变量查询速度
    • 将其存储在(heap)中,存储在变量处的值是一个指针,指向存储对象的内存处(按址访问)
    • 可以为其添加、改变和删除属性和方法;但基本类型不可以添加属性和方法
– 类型判断
  • 原始类型
    • typeof
  • 引用类型
    • isinstanceof – 判断已知对象
    • constructor – 根据对象的constructor判断 constructor本来是原型对象上的属性,指向构造函数。但是根据实例对象寻找属性的顺序,若实例对象上没有实例属性或方法时,就去原型链上寻找,因此,实例对象也是能使用constructor属性的
    • Object.prototype.toString.call
    • $.type() – 万能判断
      var a = new Array();
      console.log(a instanceof Array) // a是否Array的实例  true
      console.log(a.constructor == Array)  //  a实例所对应的构造函数是否为Array  true
    
      // constructor属性是可以被修改的,会导致检测出的结果不正确
      function Dog(){
        
      }
      function Cat(){
        
      }
      Cat.prototype = new Dog();
      var m= new Cat();
      console.log(m.constructor==Cat); // false
      console.log(John.constructor==Person); // true
      // instanceof 对于直接或间接引用都是true
      console.log(m instanceof Cat); // true
      console.log(John instanceof Person); // true
    
      //Object.prototype.toString.call 
      console.log(Object.prototype.toString.call(123)) //[object Number]
      console.log(Object.prototype.toString.call('123')) //[object String]
      console.log(Object.prototype.toString.call(undefined)) //[object Undefined]
      console.log(Object.prototype.toString.call(true)) //[object Boolean]
      console.log(Object.prototype.toString.call({})) //[object Object]
      console.log(Object.prototype.toString.call([])) //[object Array]
      console.log(Object.prototype.toString.call(function(){})) //[object Function]
    
      //$.type
      jQuery.type( undefined ) === "undefined"          // true
      jQuery.type() === "undefined"                     // true
      jQuery.type( null ) === "null"                    // true
      jQuery.type( true ) === "boolean"                 // true
    
—函数 & 声明
  • 普通函数 – 直接调用
  • 构造函数 – new 创建对象
  • 对象方法 – 对象调用

关于函数声明

//函数声明
function add(a,b){
    c = a + b;
    return c;
}
 
//函数表达式
var expression = function (a,b){
    c = a + b;
    return c;
}
  • 函数声明和函数表达式
    • 函数声明:提升,代码执行前先读取函数声明,可以把函数声明放在调用它的语句之后
    • 函数表达式使用前要先赋值
    • 原因 解析器在向执行环境中加载数据时,解析器会率先读取函数声明,并使其在执行任何代码前可用; 至于函数表达式,则必须等到解析器执行到它的所在的的代码行,才会真正的被解析

更多了解:javascript中函数声明、函数表达式以及匿名函数和自执行函数

—内置对象
  • window
    • 全局对象,主要描述浏览器窗口相关的属性和状态
  • Date
  • Array
  • JSON
    • 主要用于对象的序列化和反序列化
    • 实现对象的深拷贝
  • RegExp
—浅复制与深拷贝
  1. 浅复制 对对象地址的复制,并没有开辟新的栈,复制的结果是两个对象指向同一个地址,修改其中一个对象的属性,则另一个对象的属性也会改变
  2. 深拷贝 开辟新的栈,两个对象对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性

方法

  1. 递归
     var china = {
         nation : '中国',
         birthplaces:['北京','上海','广州'],
         skincolr :'yellow',
         friends:['sk','ls']
     }
     //深复制,要想达到深复制就需要用递归
     function deepCopy(o,c){
         var c = c || {}
         for(var i in o){
             if(typeof o[i] === 'object'){
                 //要考虑深复制问题了
                 if(o[i].constructor === Array){
                     //这是数组
                     c[i] =[]
                 }else{
                     //这是对象
                     c[i] = {}
                 }
                 deepCopy(o[i],c[i])
             }else{
                 c[i] = o[i]
             }
         }
         return c
     }
     var result = {name:'result'}
     result = deepCopy(china,result)
     console.dir(result)
    

    递归

  2. JSON
    var test ={
     name:{
         xing:{ 
             first:'',
             second:''
         },
         ming:'老头'
     },
     age :40,
     friend :['隔壁老王','宋经纪','同事']
    }
    var result = JSON.parse(JSON.stringify(test))
    result.age = 30
    result.name.xing.first = ''
    result.friend.push('fdagldf;ghad')
    console.dir(test)
    console.dir(result)
    

    json

1.2. 函数原型链

JS是一种基于对象的语言,但在ES6 之前是不支持继承的,为了具备继承的能力,Javascript 在 函数对象上建立了原型对象prototype,并以函数对象为主线,从上至下,在JS内部构建了一条 原型链

Object 是所有对象的祖宗, 任何对象所建立的原型链最终都指向了Object

简单来说: 建立了变量查找机制,当访问一个对象的属性时,先查找对象本身是否存在,如果不存在就去该对象所在的原型连上去找,直到Object对象为止,如果都没有找到该属性才会返回undefined。因此,我们可以通过原型链来实现JS继承

1.2.1. prototyp & constructor

prototype

function

  • 首先Object和Function都是构造函数,而所有的构造函数的都是 Function 的实例对象

    所有构造器的constructor都指向Function Object.constructor === Function;//true

  • Function.prototype 是 Object 的实例对象
  • 实例对象的原型(我们以 __proto__ 来表示)会指向其构造函数的 prototype 属性

    Object它是最顶层的对象

    因此

    • Object.__proto__ === Function.prototype
    • Function.__proto__ ===Function.prototype
    • Function.prototype.__proto__ === Object.prototype
  • 访问一个属性值的时候, 它会沿着原型链向上查找, 直到找到或者到 Object.prototype.__proto__(为null) 截止.
var foo = {},
    F = function(){};

Object.prototype.a = 'value a';
Function.prototype.b = 'value b';

console.log(foo.a)    // value a
console.log(foo.b)    // undefined
console.log(F.a)      // value a
console.log(F.b)      // value b

Function

  • Function是最顶层的构造器。它构造了系统中所有的对象,包括用户自定义对象,系统内置对象,甚至包括它自已。这也表明Function具有自举性(自已构造自己的能力)。这也间接决定了Function的[[call]]和[[constructor]]逻辑相同

Object

  • 对于Object它是最顶层的对象,所有的对象都将继承Object的原型,但是你也要明确的知道Object也是一个函数对象,所以说Object是被Function构造出来的

参考:

1.3. 函数作用域

变量在声明它们的函数体以及这个函数体嵌套的任意函数体

JS中没有块级作用域,只有函数作用域 导致JS中出现了变量提升的问题 —— 将变量声明提升到它所在作用域的最开始的部分 为了解决变量提升带来的副作用,ES6新增了let 命令来声明变量,let 所声明的变量只在 let 命令所在的代码块内有效,所以不存在变量提升问题

1.4. this 指针

this 指针存在于函数中,用以标识函数运行时所处的上下文

  • 普通函数 始终指向全局对象window
  • 构造函数 指向新创建的对象
  • 方法 指向调用该方法的对象
  • call、apply 和 bind

    共同点

    • 都改变函数的 this 指向
    • 第一个参数都是 this 要指向的对象——指定的上下文
    • 都可以利用后续参数传参

    不同点

    • call 和 apply 主动执行函数——立即调用
    • bind一般在事件回调中使用——返回对应函数,便于以后调用
    • call 和 apply的区别只是参数的传递方式不同

总结

  • JavaScript 中一切皆为对象( 除了 undefined & null ),运行环境也是对象,所以函数都是在某个对象之中运行,this就是这个对象(环境)
    • 函数作为对象属性运行时指向该对象
    • 作为函数运行时,指向 window,严格模式 undefined

推荐:

1.5. new 操作符

函数的创建有三种方式,即 显式声明、匿名定义 和 new Function()

JS将新对象的原型链指向了构造函数的原型对象,于是就在新对象和函数对象之间建立了一条原型链,通过新对象可以访问到函数对象原型prototype中的方法和属性

1.6. 闭包

具有独立作用域的静态执行环境

和函数作用域不同的是:

  • 闭包的作用域 静态的,可以永久保存局部资源
  • 函数作用域 只存在于运行时,函数执行结束后立即销毁

因此,闭包可以形成一个独立的执行过程

应用场景

  • 结合立即执行函数

    点击每一个 li 的时候弹出其下标

      <ul>
          <li>index  00000</li>
          <li>index  11111</li>
          <li>index  22222</li>
      </ul>
    
      <script type="text/javascript">
          window.onload = function(){
              var oLi = document.getElementsByTagName('ul')[0].children;
              for (var i = 0; i < oLi.length; i++){
                  (function(index){
                      oLi[index].onclick = function(){
                          console.log(index);
                      };
                  })(i);
              }        
          }
      </script>
      // 事件监听为全局异步函数
    
  • 创建私有变量

      // 1.2.闭包可以用于在对象中创建私有变量
      function MyObject(){
          // 私有变量和私有函数
          var privateVariable = 10;
          function privateFunction(){
              return false;
          }
          // 特权方法,调用私有方法、函数
          this.publicMethod = function(){
              privateVariable++;
              return privateFunction();
          }
      }
    

更多了解:

1.7. 单线程和异步

JavaScript

  • 单线程语言,在浏览器中,当JS代码被加载时,浏览器会为其分配一个主线程来执行任务(函数)

    主线程会形成一个全局执行环境,执行环境在栈中采用后进先出(LIFO)的顺序来执行代码块,以保证所有的函数能按照正确的顺序被执行

  • 执行环境中维护了一个异步队列(也叫工作线程),并将这些耗时任务放入队列中进行等待

    • 如ajax请求、定时器、事件等
    • 这些任务的执行时机并不确定,只有当主线程的任务执行完成以后,主线程才会去检查异步队列中的任务是否需要开始执行。
    • JS中的 setTimeout 和 setInterval 就是典型的异步操作,它们会被放入异步队列中等待,即使 setTimeout(0)也不会被立即执行,需要等到当前同步任务结束后才会被执行。

1.8. 异步通信

1.8.1. Ajax

浏览器专门用来和服务器进行交互的异步通讯技术

  • Ajax是浏览器专门用来和服务器进行交互的异步通讯技术
  • 其核心对象是XMLHttpRequest,通过该对象可以创建一个Ajax请求
  • Ajax请求是一个耗时的异步操作,当请求发出以后,Ajax提供了两个状态位来描述请求在不同阶段的状态,这两个状态位分别是
    • readyState
    • status

readyState 通过5个状态码来描述一个请求的5个阶段:

  • 0 - 请求未发送,初始化阶段
  • 1 - 请求发送中,服务器还未收到请求
  • 2 - 请求发送成功,服务器已收到请求
  • 3 - 服务器处理完成,开始响应请求,传输数据
  • 4 - 客户端收到请求,并完成了数据下载,生成了响应对象

status

  • 1xx(临时响应)表示临时响应并需要请求者继续执行操作的状态码。
  • 2xx(成功)
    • 2xx(成功)表示成功处理了请求的状态码。
    • 200(成功):服务器已成功处理了请求。通常,这表示服务器提供了请求的网页。
  • 3xx(重定向)要完成请求,需要进一步操作。
    • 301(永久移动):请求的网页已永久移动到新位置。
    • 302(临时移动):服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来响应以后的请求。
    • 304(未修改):自从上次请求后,请求的网页未修改过。
  • 4xx(请求错误)这些状态码表示请求可能出错,妨碍了服务器的处理。
    • 400(错误请求):服务器不理解请求的语法。
    • 404(未找到):服务器找不到请求的网页。
  • 5xx(服务器错误)这些状态码表示服务器在处理请求时发生内部错误。
    • 500(服务器内部错误):服务器遇到错误,无法完成请求。
    • 503(服务不可用):服务器目前无法使用(由于超载或停机维护)

常见问题:

  • timeout 只会影响readyState,而不会影响status,因为超时只会中断数据传输,但不会影响服务器的处理结果。 如果 timeout 设置的不合理,就会导致响应码status 是200,但 response里却没有数据,这种情况就是服务器正确响应了请求,但数据的下载被超时中断了。

HTTP 相关请见:

1.8.2. JSONP && CORS

浏览器设置了安全限制——JavaScript或Cookie只能访问同域下的内容——同源策略

只允许请求和当前地址同域的服务器资源。但不限制脚本和标签发送跨域请求,比如script 和 img 标签,因此可以利用脚本跨域能力来实现跨域请求,即JSONP 的原理。

JSONP虽然可以解决跨域问题,但只能是get请求,并且没有有效的错误捕获机制 为了解决这个问题,XMLHttpRequest Level2 提出了CORS 模型,即 跨域资源共享, 它不是一个新的API,而是一个标准规范,当浏览器发现该请求需要跨域时,就会自动在头信息中添加一个 Origin字段,用以说明本次请求来自哪个源。服务器根据这个值,决定是否同意这次请求。

随着移动端的快速发展,Web技术的应用场景正在变得越来越复杂, 关注点分离 原则在系统设计层面就显得越来越重要,而XMLHttpRequest 是 Ajax 最古老的一个接口,因而不太符合现代化的系统设计理念。因此,浏览器提供了一个新的 Ajax 接口,即 Fetch API ,Fetch API 是基于Promise 思想设计的,更符合关注点分离原则。

更多请见:

1.9. 模块化

模块加载方案,最主要有 CMD 和 AMD 两种,分别以commonjs 和 requirejs为代表

ES6 在语言标准的层面上,实现了模块化编程,其设计思想是,尽量静态化,使得编译时就能确定模块的依赖关系,即编译时加载

CMD和AMD是在运行时确定依赖关系,即运行时加载

详情: AMD && CMD

ES6 模块化

每一个ES6模块都是一个包含JS代码的文件,模块本质上就是一段脚本,而不是用module关键字定义一个模块,但是模块与脚本还是有两点区别:

  • 在ES6模块中,无论你是否加入“use strict;”语句,默认情况下模块都是在严格模式下运行。
  • 在模块中你可以使用import和export关键字。

默认情况下,你在模块中的所有声明相对于模块而言都是寄存在本地的。如果你希望公开在模块中声明的内容,并让其它模块加以使用,你一定要导出这些功能。想要导出模块的功能有很多方法,其中最简单的方式是添加export关键字,可以导出所有的最外层函数、类以及var、let或const声明的变量。

es6中 代码就是模块,不是一段脚本,所以所有的声明都被限定在模块的作用域中,对所有脚本和模块全局不可见。你需要做的是将组成模块公共API的声明全部导出。

CommonJS

  • 对于基本数据类型,属于复制。即会被模块缓存。同时,在另一个模块可以对该模块输出的变量重新赋值。
  • 对于复杂数据类型,属于浅拷贝。由于两个模块引用的对象指向同一个内存空间,因此对该模块的值做修改时会影响另一个模块。
  • 当使用require命令加载某个模块时,就会运行整个模块的代码。
  • 当使用require命令加载同一个模块时,不会再执行该模块,而是取到缓存之中的值。也就是说,CommonJS模块无论加载多少次,都只会在第一次加载时运行一次,以后再加载,就返回第一次运行的结果,除非手动清除系统缓存。
  • 循环加载时,属于加载时执行。即脚本代码在require的时候,就会全部执行。一旦出现某个模块被”循环加载”,就只输出已经执行的部分,还未执行的部分不会输出。

ES6模块

  • ES6模块中的值属于【动态只读引用】。
  • 对于只读来说,即不允许修改引入变量的值,import的变量是只读的,不论是基本数据类型还是复杂数据类型。当模块遇到import命令时,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。
  • 对于动态来说,原始值发生变化,import加载的值也会发生变化。不论是基本数据类型还是复杂数据类型。
  • 循环加载时,ES6模块是动态引用。只要两个模块之间存在某个引用,代码就能够执行。

webpack 在编译时计算所有依赖并将所有模块打包成一个文件,通过网络一次传输所有模块 减少加载模块时的网络往返时间

深入浅出ES6(模块):http://www.infoq.com/cn/articles/es6-in-depth-modules

1.10. Node.js

一个基于 Chrome V8 引擎的 JavaScript运行环境

Node.js在服务端的优势是,它采用单线程和异步I/O模型,实现了一个高并发、高性能的运行时环境。相比传统的多线程模型,Node.js实现简单,并且可以减少资源开销

1.11. ES6

目标是让JS能够方便的开发企业级大型应用程序

变化

  • 新增 let、const 命令 来声明变量 和var 相比,let声明的变量不存在变量提升问题,但没有改变JS弱类型的特点,依然可以接受任意类型变量的声明;const 声明的变量不允许在后续逻辑中改变,提高了JS语法的严谨性。
  • 新增解构赋值、rest语法、箭头函数 这些都是为了让代码看起来更简洁,而包装的语法糖。
  • 新增模块化 这是JS走向规范比较重要的一步,让前端更方便的实现工程化。
  • 新增类和继承的概念 配合模块化,JS也可以实现高复用、高扩展的系统架构。
  • 新增模板字符串功能 高效简洁,结束拼接字符串的时代。
  • 新增Promise对象 解决异步回调多层嵌套的问题 使得原本的多层级的嵌套代码,变成了链式调用 让代码更清晰,减少嵌套数

promise

  • 容器:里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果
  • 对象:从它可以获取异步操作的消息

特点

  • 对象的状态不受外界影响 Promise对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称 Fulfilled)和Rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
  • 一旦状态改变,就不会再变,任何时候都可以得到这个结果 Promise对象的状态改变,只有两种可能:从Pending变为Resolved和从Pending变为Rejected。

缺点

  • 无法取消Promise 一旦新建它就会立即执行,无法中途取消
  • 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部
  • 当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)

更多:

2. CSS

2.1. CSS 选择器

  • 类选择器
  • 标签选择器
  • ID选择器
  • 后代选择器
  • 群组选择器
  • 伪类选择器(before/after)
  • 兄弟选择器(+~)
  • 属性选择器等等

2.2. 盒子模型

  • 块级盒子(block)
  • 行内盒子(inline-block)

相关属性 margin、border、padding、content

注意

  • 只有普通文档流中块级盒子的垂直外边距才会发生合并,而行内盒子、浮动盒子或绝对定位之间的外边距不会合并 根据规范,一个盒子如果没有上补白(padding-top)和上边框(border-top),那么这个盒子的上边距会和其内部文档流中的第一个子元素的上边距重叠 为父元素增加一个border-top或者padding-top即可解决这个问题
  • box-sizing 属性的设置会影响盒子width和height的计算

margin

  • 在块级元素下,他的性能可以完全体现
  • 也能用于内联元素,这是规范所允许的,但是 margin-top 和 margin-bottom 对内联元素(对行)的高度没有影响,margin-left/margin-right还是能够对内联元素产生影响 内联元素的行高即类似文本的行间距,只能使用这三个属性:line-height,fong-size,vertical-align

更多:

2.3. 浮动布局

设置元素的 float 属性,能使该元素脱离普通文档流 如果子元素全部设置为浮动,则父元素是塌陷的

  • 清除浮动 clear:both,
  • BFC 浮动元素的父元素 + overflow:hidden 样式
  • 行内盒子(inline-block)
  • table也可以实现同样的效果。

更多:见 Topics of FE 中的 topic 清除浮动

2.4. 定位布局

脱离文档流:position 值为 relative/absolute/fixed

  • relative 相对定位,它以自己原来的位置进行偏移,偏移后,原来的空间不会被其他元素占用
  • absolute 绝对定位,它以离自己最近的定位父容器作为参照进行偏移 常用的方式就是设置父容器的poistion:relative
  • fixed 固定定位,以浏览器窗口为参照物 PC网页底部悬停的banner一般都可以通过fixed定位来实现,但fixed属性在移动端有兼容性问题,因此不推荐使用,可替代的方案是:绝对定位+内部滚动。

更多:

2.5. 弹性布局

即Flex布局,定义了flex的容器一个可伸缩容器

  • 容器本身会根据容器中的元素动态设置自身大小
  • 当Flex容器被应用一个大小时(width和height),将会自动调整容器中的元素适应新大小
  • Flex容器也可以设置伸缩比例和固定宽度,还可以设置容器中元素的排列方向(横向和纵向)和是否支持元素的自动换行 容器的属性
    • flex-direction属性
    • flex-wrap属性
    • flex-flow
    • justify-content属性
    • align-items属性
    • align-content属性

    项目的属性

    • order属性
    • flex-grow属性
    • flex-shrink属性
    • flex-basis属性
    • flex属性
    • align-self属性
  • 注意,设为Flex布局以后,子元素的float、clear和vertical-align属性将失效。

更多: Flex 布局

2.6. CSS3 动画

  • transition 让元素的CSS属性值的变化在一段时间内平滑的过渡 CSS3引入了transfrom属性,它可以通过对元素进行 平移(translate)、旋转(rotate)、放大缩小(scale)、倾斜(skew) 等操作,来实现2D和3D变换效果 transiton 还有一个结束事件 transitionEnd,该事件是在CSS完成过渡后触发,如果过渡在完成之前被移除,则不会触发transitionEnd
  • animation 需要设置一个@keyframes,来定义元素以哪种形式进行变换 然后再通过动画函数让这种变换平滑的进行,从而达到动画效果
    • 动画可以被设置为永久循环演示
    • animation-play-state:paused可以暂停动画
    • animation-fill-mode:forwards 可以让动画完成后定格在最后一帧
    • 可以通过JS监听animation的开始、结束和重复播放时的状态,分别对应三个事件,即 animationStart、animationEnd、animationIteration 注意,当播放次数设置为1时,不会触发 animationIteration

对比

  • animation 设置动画效果更灵活更丰富
  • transition 只能通过主动改变元素的css值才能触发动画效果,而animation一旦被应用,就开始执行动画

2.7. BFC

BFC—Block Formatting Context

是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面元素

比如:内部滚动就是一个BFC,当一个父容器的overflow-y设置为auto时,并且子容器的长度大于父容器时,就会出现内部滚动,无论内部的元素怎么滚动,都不会影响父容器以外的布局,这个父容器的渲染区域就叫BFC。

IFC(Inline Formatting Context)即行内格式化上下文

BFC 特点

  • 盒子们自所在的containing block顶部一个接一个垂直排列
  • 水平方向上撑满整个宽度(除非内部盒子自己建立了新的BFC)
  • 两个相邻的BFC之间的距离由margin决定
  • 在同一个BFC内部,两个垂直方向相邻的块级元素的margin会发生“塌陷”

触发BFC 根元素或其它包含它的元素

  • float的值不为none
  • display的值为inline-block、table-cell、table-caption
  • position的值为absolute或fixed
  • flex boxes (元素的display: flex或inline-flex)

应用

  • 清除内部浮动 对子元素设置浮动后,父元素会发生高度塌陷,也就是父元素的高度变为0。解决这个问题,只需要把把父元素变成一个BFC就行了。常用的办法是给父元素设置overflow:hidden
  • 垂直margin合并 属于同一个BFC的两个相邻元素的margin会发生重叠 —— 创建 BFC
  • 创建自适应两栏布局 —— 解决侵占浮动元素的问题

参考: CSS: 潜藏着的BFC

2.8. Sprite,Iconfont,@font-face

  • Sprite图 为了减少http请求的次数,一般会将常用的小图标排到一个大图中,页面加载时只需请求一次网络,在css中通过设置background-position来控制显示所需要的小图标
  • Iconfont 即字体图标,就是将常用的图标转化为字体资源存在文件中,通过在CSS中引用该字体文件,然后可以直接用控制字体的css属性来设置图标的样式 字体图标的好处是节省网络请求、其大小不受屏幕分辨率的影响,并且可以任意修改图标的颜色
  • @font-face 是CSS3中的一个模块 通过@font-face可以定义一种全新的字体,通过css属性font-family来使用这个字体

3. HTML

3.1. BOM

Browser Object Model 浏览器对象模型 为了控制浏览器的行为而出现的接口 比如跳转到另一个页面、前进、后退等等,程序还可能需要获取屏幕的大小之类的参数。

当一个浏览器页面初始化时,会在内存创建一个全局的对象,用以描述当前窗口的属性和状态,这个全局对象被称为浏览器对象模型,即BOM

BOM的核心对象就是window,window对象也是BOM的顶级对象,其中包含了浏览器的 6个核心模块:

  • document
    • 即文档对象,渲染引擎在解析HTML代码时,会为每一个元素生成对应的DOM对象,由于元素之间有层级关系,因此整个HTML代码解析完以后,会生成一个由不同节点组成的树形结构,俗称DOM树
    • document用于描述DOM树的状态和属性,并提供了很多操作DOM的API。
  • frames
    • HTML 子框架,即在浏览器里嵌入另一个窗口
    • 父框架和子框架拥有独立的作用域和上下文。
  • history
    • 以栈(FIFO)的形式保存着页面被访问的历史记录
    • 页面前进即入栈,页面返回即出栈。
  • location
    • 提供了当前窗口中加载的文档相关信息以及一些导航功能
  • navigator
    • 用来描述浏览器本身,包括浏览器的名称、版本、语言、系统平台、用户特性字符串等信息
  • screen
    • 提供了浏览器显示屏幕的相关属性,比如显示屏幕的宽度和高度,可用宽度和高度。

3.2. DOM 系统

Document Object Model 文档对象模型,是所有浏览器公共遵守的标准 为了能以编程的方法操作这个 HTML 的内容

  • DOM将HTML和XML文档映射成一个由不同节点组成的树型结构,俗称DOM树
  • 其核心对象是document,用于描述DOM树的状态和属性,并提供对应的DOM操作API

它本身和里面的所有东西比如 <div></div> 这些标签都看做一个对象,每个对象都叫做一个节点(node)

作用

为了操作 HTML 中的元素,比如说我们要通过 JS 把这个网页的标题改了,直接这样就可以了: document.title = 'how are you'

这个 API 使得在网页被下载到浏览器之后改变网页的内容成为可能。

dom

3.3. 事件系统

事件是用户与页面交互的基础,到目前为止,DOM事件从PC端的 鼠标事件(mouse) 发展到了 移动端的 触摸事件(touch) 和 手势事件(guesture),touch事件描述了手指在屏幕操作的每一个细节,guesture 则是描述多手指操作时更为复杂的情况

总结如下:

  • 第一根手指放下,触发 touchstart,除此之外什么都不会发生
  • 手指滑动时,触发touchmove
  • 第二根手指放下,触发 gesturestart
  • 触发第二根手指的 touchstart
  • 立即触发 gesturechange
  • 任意手指移动,持续触发 gesturechange
  • 第二根手指弹起时,触发 gestureend,以后将不会再触发 gesturechange
  • 触发第二根手指的 touchend
  • 触发touchstart (多根手指在屏幕上,提起一根,会刷新一次全局touch)
  • 弹起第一根手指,触发 touchend

DOM2.0 模型将事件处理流程分为三个阶段,即 事件捕获阶段 、 事件处理阶段 、 事件冒泡阶段

  • 事件捕获 当用户触发点击事件后,顶层对象document 就会发出一个事件流,从最外层的DOM节点向目标元素节点传递,最终到达目标元素。
  • 事件处理 当到达目标元素之后,执行目标元素绑定的处理函数。如果没有绑定监听函数,则不做任何处理。
  • 事件冒泡 事件流从目标元素开始,向最外层DOM节点传递,途中如果有节点绑定了事件处理函数,这些函数就会被执行。

利用事件冒泡原理可以实现 事件委托

所谓事件委托,就是在父元素上添加事件监听器,用以监听和处理子元素的事件,避免重复为子元素绑定相同的事件

  • 方式 当目标元素的事件被触发以后,这个事件就从目标元素开始,向最外层元素传递,最终冒泡到父元素上,父元素再通过event.target获取到这个目标元素
  • 好处 父元素只需绑定一个事件监听,就可以对所有子元素的事件进行处理了,从而减少了不必要的事件绑定,对页面性能有一定的提升。
addEventListener()
//方法来实现。如果该方法传入的最后一个参数值为true,表示事件处理程序被注册在捕获阶段,如果为false表示件处理程序被注册在冒泡阶段
parentEle.addEventListener("click", parentDoSomething, true);

更多: 事件委托和 this

3.4. HTML 解析过程

浏览器加载 html 文件以后,渲染引擎会从上往下,一步步来解析HTML标签

过程如下:

  • 请求服务器返回HTML文件 用户输入网址,浏览器向服务器发出请求,服务器返回html文件;
  • 生成dom 树 渲染引擎开始解析 html 标签,并将标签转化为DOM节点,生成 DOM树;
  • css文件请求 如果head 标签中引用了外部css文件,则发出css文件请求,服务器返回该文件,该过程会阻塞后面的解析;
  • js 请求 如果引用了外部 js 文件,则发出 js 文件请求,服务器返回后立即执行该脚本,这个过程也会阻塞html的解析;
  • 生成渲染树 引擎开始解析 body 里面的内容,如果标签里引用了css 样式,就需要解析刚才下载好的css文件,然后用css来设置标签的样式属性,并生成渲染树;
  • 下载图片资源 如果 body 中的 img 标签引用了图片资源,则立即向服务器发出请求,此时引擎不会等待图片下载完毕,而是继续解析后面的标签;
  • 重新渲染
    • 服务器返回图片文件,由于图片需要占用一定的空间,会影响到后面元素的排版,因此引擎需要重新渲染这部分内容;
    • 如果此时 js 脚本中运行了 style.display=”none”,布局被改变,引擎也需要重新渲染这部分代码;
  • 直到 html 结束标签为止,页面解析完毕。

3.5. 重绘 和 回流

  • 回流 / 重排 当渲染树中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建 如上面的img文件加载完成后就会引起回流,每个页面至少需要一次回流,就是在页面第一次加载的时候
  • 重绘 当渲染树中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如 background-color

从上面可以看出,回流必将引起重绘,而重绘不一定会引起回流

会引起重绘和回流的操作如下:

  • 添加、删除元素(回流+重绘)
  • 隐藏元素,display:none(回流+重绘),visibility:hidden(只重绘,不回流)
  • 移动元素,比如改变top,left的值,或者移动元素到另外一个父元素中。(重绘+回流)
  • 对style的操作(对不同的属性操作,影响不一样)
  • 还有一种是用户的操作,比如改变浏览器大小,改变浏览器的字体大小等(回流+重绘)
  • 另外,transform操作不会引起重绘和回流,是一种高效率的渲染。这是因为transform属于合成属性,对合成属性进行 transition/animation 动画时将会创建一个合成层,这使得动画元素在一个独立的层中进行渲染,当元素的内容没有发生改变,就没必要进行重绘,浏览器会通过重新复合来创建动画帧。

最小化重绘和回流

  • 需要要对元素进行复杂的操作时
    • 可以先隐藏(display:”none”),操作完成后再显示
  • 需要创建多个DOM节点时
    • 使用DocumentFragment创建完后一次性的加入document
  • 缓存Layout属性值
    • 如:var left = elem.offsetLeft; 这样,多次使用 left 只产生一次回流
  • 尽量避免用table布局
    • (table元素一旦触发回流就会导致table里所有的其它元素回流)
  • 避免使用css表达式(expression)
    • 因为每次调用都会重新计算值(包括加载页面)
  • 尽量使用 css 属性简写
    • 如:用 border 代替 border-width, border-style, border-color
  • 批量修改元素样式:elem.className 和 elem.style.cssText 代替 elem.style.xxx

3.6. 本地存储

避免取回数据前页面空白,减少请求服务器次数

  • cookie
    • 本地存储最原始的方式 cookie 是存放在本地浏览器的一段文本,数据以键值对的形式保存,可以设置过期时间。
    • 不适合大量数据的存储 因为每请求一次页面,cookie 都会发送给服务器,这使得 cookie速度很慢而且效率也不高。因此cookie的大小被限制为4k左右(不同浏览器可能不同,分HOST)
  • html5 提供了两种在客户端存储数据的新方法:
    • localStorage 永久存储
    • sessionStorage 存储期限仅限于浏览器会话(session),即当浏览器窗口关闭后,sessionStorage中的数据被清除

    都是以key/value的形式来存储数据 localStorage的存储空间大约5M左右(不同浏览器可能不同,分 HOST),这个相当于一个5M大小的前端数据库,相比于cookie,可以节约带宽,但localStorage在浏览器隐私模式下是不可读取的,当存储数据超过了 localStorage 的存储空间后会抛出异常。 此外,H5还提供了逆天的 websql 和 indexedDB,允许前端以关系型数据库的方式来存储本地数据

cookie作用是与服务器交互,作为HTTP规范的一部分,web storage仅仅为本地存储而生

更多:常用的 web 客户端存储

3.7. 浏览器缓存机制

浏览器缓存机制是指通过 HTTP 协议头里的 Cache-Control (或 Expires) 和 Last-Modified (或 Etag) 等字段来控制文件缓存的机制。

强制缓存优先于协商缓存进行,若强制缓存(Expires和Cache-Control)生效则直接使用缓存,若不生效则进行协商缓存(Last-Modified / If-Modified-Since和Etag / If-None-Match),协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,重新获取请求结果,再存入浏览器缓存中;生效则返回304,继续使用缓存,主要过程如下:

缓存

  • 彻底理解浏览器的缓存机制
  • []

  • Cache-Control
    • 用于控制文件在本地缓存有效时长
    • 比如服务器回包:Cache-Control:max-age=600 表示文件在本地应该缓存,且有效时长是600秒 (从发出请求算起)。在接下来600秒内,如果有请求这个资源,浏览器不会发出 HTTP请求,而是直接使用本地缓存的文件。
  • Last-Modified
    • 标识文件在服务器上的最新更新时间 下次请求时,如果文件缓存过期,浏览器通过 If-Modified-Since 字段带上这个时间,发送给服务器,由服务器比较时间戳来判断文件是否有修改。如果没有修改,服务器返回304告诉浏览器继续使用缓存;如果有修改,则返回200,同时返回最新的文件。
  • Cache-Control 通常与 Last-Modified 一起使用 一个用于控制缓存有效时间,一个在缓存失效后,向服务查询是否有更新。
    • Cache-Control 还有一个同功能的字段:Expires。Expires 的值为一个绝对的时间点 如:Expires: Thu, 10 Nov 2015 08:45:11 GMT,表示在这个时间点之前,缓存都是有效的。
  • Etag 也是和 Last-Modified 一样,对文件进行标识的字段 不同的是,Etag 的取值是一个对文件进行标识的特征字串。在向服务器查询文件是否有更新时,浏览器通过 If-None-Match 字段把特征字串发送给服务器,由服务器和文件最新特征字串进行匹配,来判断文件是否有更新。没有更新回包304,有更新回包200。Etag 和 Last-Modified 可根据需求使用一个或两个同时使用。两个同时使用时,只要满足基中一个条件,就认为文件没有更新。

另外有两种特殊的情况:

  • 手动刷新页面(F5) 浏览器会直接认为缓存已经过期(可能缓存还没有过期),在请求中加上字段:Cache-Control:max-age=0,发包向服务器查询是否有文件是否有更新。
  • 强制刷新页面(Ctrl+F5) 浏览器会直接忽略本地的缓存(有缓存也会认为本地没有缓存),在请求中加上字段:Cache-Control:no-cache (或 Pragma:no-cache),发包向服务重新拉取文件

3.8. History

用户访问网页的历史记录通常会被保存在一个类似于栈的对象中,即history对象,点击返回就出栈,跳下一页就入栈

它提供了以下方法来操作页面的前进和后退:

  • window.history.back( ) 返回到上一个页面
  • window.history.forward( ) 进入到下一个页面
  • window.history.go( [delta] ) 跳转到指定页面

HTML5 对History Api 进行了增强,新增了两个Api 和一个事件,分别是pushState、replaceState 和 onpopstate:

  • pushState是往history对象里添加一个新的历史记录,即压栈。
  • replaceState 是替换history对象中的当前历史记录。
  • 当点击浏览器后退按钮或 js调用history.back 都会触发 onpopstate 事件。 与其类似的还有一个事件:onhashchange,onhashchange是老API,浏览器支持度高,本来是用来监听hash变化的,但可以被利用来做客户端前进和后退事件的监听,而onpopstate是专门用来监听浏览器前进后退的,不仅可以支持hash,非hash的同源 url 也支持。

3.9. HTML5离线缓存

HTML5离线缓存又叫Application Cache,是从浏览器的缓存中分出来的一块缓存区,如果要在这个缓存中保存数据,可以使用一个描述文件(manifest file),列出要下载和缓存的资源。

manifest 文件是简单的文本文件,它告知浏览器被缓存的内容(以及不缓存的内容)。manifest 文件可分为三个部分:

  • CACHE MANIFEST - 在此标题下列出的文件将在首次下载后进行缓存
  • NETWORK - 在此标题下列出的文件需要与服务器的连接,且不会被缓存
  • FALLBACK - 在此标题下列出的文件规定当页面无法访问时的回退页面(比如 404 页面)

离线缓存为应用带来三个优势:

  • 离线浏览 - 用户可在应用离线时使用它们
  • 速度 - 已缓存资源加载得更快
  • 减少服务器负载 - 浏览器将只从服务器下载更新过或更改过的资源。

3.10. 前端 SEO

  • 合理的title、description、keywords:搜索对着三项的权重逐个减小,title值强调重点即可,重要关键词出现不要超过2次,而且要靠前,不同页面title要有所不同;description把页面内容高度概括,长度合适,不可过分堆砌关键词,不同页面description有所不同;keywords列举出重要关键词即可
  • 语义化的HTML代码,符合W3C规范:语义化代码让搜索引擎容易理解网页
  • 重要内容HTML代码放在最前:搜索引擎抓取HTML顺序是从上到下,有的搜索引擎对抓取长度有限制,保证重要内容一定会被抓取
  • 重要内容不要用js输出:爬虫不会执行js获取内容(目前chrome浏览器可以了)
  • 少用iframe:搜索引擎不会抓取iframe中的内容
  • 非装饰性图片必须加alt
  • 提高网站速度:网站速度是搜索引擎排序的一个重要指标

参考: