关于变量提升和作用域相关的题目

2017
22/06

1.变量提升,且提升的是申明,赋值不会提升

变量提升是js的预解析过程,在代码块执行前会进行隐性操作。

提升只针对var申明的变量和function具名函数。

console.log(a) // undefined,如果后面没有申明变量a,这里就不是undefined而是直接报错:is not defined
var a = 1
// 同理如下
var a1 = a2+1
var a2 = 10
console.log(a1) // NaN

// 上面代码等同于--------------------------------------

var a
var a1
var a2
console.log(a)  // undefined
a = 1
a1 = a2+1
a2 = 10
console.log(a1) // NaN

2.var会忽略后续同一个变量申明,只认可先前的申明

var a = 10
var a 
console.log(a) // 10
var a  = 5
console.log(a) // 5

// 上面代码等同于--------------------------------------

var a = 10
var a // 被忽略,相当于没有这一行
var a // 被忽略
console.log(a) // 10
a  = 5
console.log(a) // 5

// 上面代码等同于--------------------------------------

var a = 10
console.log(a) // 10
a  = 5
console.log(a) // 5

3.变量申明与赋值为`undefined`不同

// 赋值
var a = 'hello world'; 
var a = undefined;
console.log(a); // undefined

// 申明
var a = 'hello world'; 
var a;
console.log(a); // 'hello world‘

4.函数提升优先于变量提升,且全部提升

题目1

console.log(a) // ƒ a() {}
// 这就是为什么函数可以放到下面,在上面依然能调用的原因
// 函数会被提升,且优先于变量,且为全部提升,不只是申明
var a = 1
function a() {}
console.log(a) // 1  在这里a被重新赋值为1

// 上面代码等同于--------------------------------------

function a() {}
var a // 第二次申明被忽略
console.log(a) // ƒ a() {}
a = 1
console.log(a) // 1  在这里a被重新赋值为1

// 上面代码等同于--------------------------------------

function a() {}
console.log(a) // ƒ a() {}
a = 1
console.log(a) // 1  在这里a被重新赋值为1

题目2(重要)

fn() // 5
function fn(){ console.log(1) }
fn() // 5
function fn(){ console.log(2) }
fn() // 5
var fn = function(){ console.log(3) }
fn() // 3
function fn(){ console.log(4) }
fn() // 3
function fn(){ console.log(5) }
fn() // 3

// 上面代码等同于--------------------------------------

function fn(){ console.log(1) }
function fn(){ console.log(2) }
function fn(){ console.log(4) }
function fn(){ console.log(5) }
var fn 
fn() // 5
fn() // 5
fn() // 5
fn = function(){ console.log(3) }
fn() // 3
fn() // 3
fn() // 3

5.局部变量优先于全局变量

var a = 1;
fn();
function fn(){
    console.log(a); // undefined
    var a = 2
    console.log(a) // 2
}

// 第一个打印 undefined 是由于
// 函数申明也会被提升,且优先于变量
// 函数作用域中的变量的申明也会提升到函数最顶部
// 函数作用域中变量为局部变量,作用域链优先取局部值,局部没有才会找外层
// 上面代码等同于--------------------------------------

function fn(){
    var a
    console.log(a); // undefind 局部变量优先
    a = 2
    console.log(a) // 2
}
var a
a = 1
fn()

6.预编译在执行前进行,执行结果不影响预编译

var a = 1;
fn();
function fn(){
    console.log(a); // undefined
    return
    var a = 2
    console.log(a) // 不执行
}

// 上面代码等同于--------------------------------------

function fn(){
    var a
    console.log(a); // undefined
    return
    a = 2  // 不执行
    console.log(a) // 不执行
}
var a
a = 1;
fn();

7.函数作用域

function fn(){
   var aaa = 1
}
fn()
console.log(window.aaa) // undefined 对象上不存在的属性返回undefined
console.log(aaa) // 报错,未定义

注意:对象上找属性是原型链的查找,直接找变量是作用域链的查找。

8.局部找不到会到外层找,且函数在执行时变量才会查找

题目1

fn() // 在这里执行时候,a只是提升申明,还没有赋值
var a = 1;
function fn(){
    console.log(a); // undefind
    a = 2
    console.log(a) // 2
}

// 以上代码等同于----------------------------

function fn(){
    console.log(a); // undefind
    a = 2
    console.log(a) // 2
}
var a
fn() // 在这里执行时候,a已经申明,还没有赋值
a = 1;

题目2

var a = 1;
fn() // 在这里执行,a已经被赋值
function fn(){
    console.log(a); // 1
    a = 2
    console.log(a) // 2
}

// 以上代码等同于----------------------------

function fn(){
    console.log(a); // 1
    a = 2
    console.log(a) // 2
}
var a
a = 1
fn() // 在这里执行,a已经被赋值

题目3(重要)

var n = 100
function fn(){
    n = 200
}
fn()
console.log(n) // 200

// 以上代码等同于----------------------------

function fn(){
    n = 200
}
var n
n = 100
fn()  // 在执行时,全局n为100,执行之后n被重新赋值为200
console.log(n) // 200

9.函数的作用域链外层要看函数申明位置,而不是在哪调用(重要)

函数执行时入栈开辟执行上下文,入栈位置时申明的位置。申明在全局执行上下文就在全局。

只不过时调用(执行)的时候才会去查找变量(第8条),局部没有就向外层查找。外层要看申明在哪里,而不是调用在哪里。

题目1(重要)

var a = 1;

function fn1(){
    console.log(a); // 1
}

function fn2(){
    var a = 2;
    console.log(a); // 2
    fn1()
}

fn2()
console.log(a) // 1

题目2(重要)

var a = 1;

function fn2(){
    function fn1(){
        console.log(a); // 2
    }
    var a = 2;
    console.log(a); // 2
    fn1()
}

fn2()
console.log(a) // 1

10.全局变量只要被申明,就会被挂到window上

只要申明,不需要赋值,就会挂到window

console.log('a' in window)  // false
console.log('sad' in window)  // true
var sad = 10
// in 可以判断对象自身的原型属性以及自身原型属性和方法(不可枚举的也能判断)

// 以上代码等同于----------------------------

var sad
// window.sad = sad  隐式操作
console.log('a' in window)  // false
console.log('sad' in window)  // true
sad = 10

11.var 不存在块级作用域

题目1

由于没有块级作用域,那么块级中的变量是全局变量,同样被提升

console.log('i:',i) // undefined
for(var i=0;i<3;i++){
    console.log("a:",i)
}
console.log("b:",i)
// i: undefined
// a: 0
// a: 1
// a: 2
// b: 3

// 以上代码等同于----------------------------

var i
console.log('i:',i) // undefined
i=0
for(;i<3;){
    console.log("a:",i)
    i++
}
console.log("b:",i)

// 以上代码等同于----------------------------

var i
console.log('i:',i) // undefined
i = 0
while(i<3){
    console.log("a:",i)
    i++
}
console.log("b:",i)

题目2(重要)

请注意,没有块级作用域,不管var变量在哪里申明,都会被提升。

变量提升是预编译的过程,预编译时候代码还未执行,不会有判断的操作。

console.log(a, b)  // undefined undefined
if(true){
  var a = 1
}else{
  var b = 2
}
console.log(a, b) // 1 undefined

// 以上代码等同于----------------------------

var a
var b
console.log(a, b)  // undefined undefined
if(true){
  a = 1
}else{
  b = 2
}
console.log(a, b) // 1 undefined

题目3(重要)

注意:函数在块{}中只提升申明,不提升整个函数

console.log(fn1)  // undefined
console.log(fn2)  // undefined
console.log('fn1' in window)  // true
// fn1()  // 报错:fn1 is not a function

{
    // 进了块中第一件事就是给fn1赋值
    fn1()  // 可运行

    function fn1(){
        console.log(1);
    }
}

if('fn2' in window){
    function fn2(){
        console.log(1);
    }
}

fn2() // 可运行

题目4

console.log(a) // undefined
if('a' in window){
  var a = 110
}
console.log(a)  // 110

// 以上代码等同于---------------------------

var a
// window.a = a 隐式操作
console.log(a) // undefined
if('a' in window){ // true
  a = 110
}
console.log(a)  // 110

12.没有加var的变量都是全局变量,不论在哪赋值,在任何位置都能访问

没有用关键字var赋值的变量会被隐性申明到全局。只有赋值之后才会被申明,不存在提升。

题目1

console.log(abc) // 报错 Uncaught ReferenceError: abc is not defined
fn()
function fn(){
 abc = 109
}

// 以上代码等同于----------------------------

function fn(){
 window.abc = 109
}
console.log(abc) // 在这里fn未执行, abc还未赋值
fn()

题目2

fn()
console.log(abc)  // 109
function fn(){
 abc = 109
}

// 以上代码等同于----------------------------

function fn(){
 window.abc = 109
}
fn()
console.log(abc)  // 这里已执行函数,abc 被赋值

题目3

fn()()
console.log(abc) // 109 
function fn(){
  return function(){
     abc = 109
  }
}
// 不管嵌套多少次都是挂到全局

13.var 连等,只有第一个变量是var申明且赋值,后面变量都是全局变量

题目1

console.log(aaa) // undefined
console.log(bbb) // 报错bbb is not defined
var aaa = bbb = ccc = 10
console.log(aaa, bbb, ccc) // 10, 10, 10

// 以上代码等同于----------------------------

console.log(aaa) // undefined
console.log(bbb) // 报错bbb is not defined
var aaa = 10
bbb = 10
ccc = 10
console.log(aaa, bbb, ccc)

// 以上代码等同于----------------------------

var aaa
console.log(aaa) // undefined
console.log(bbb) // 报错bbb is not defined
aaa = 10
bbb = 10
ccc = 10
console.log(aaa, bbb, ccc)

题目2

bbb = ccc = 10
console.log(bbb, ccc) // 10, 10

// 以上代码等同于----------------------------

bbb = 10
ccc = 10

题目3

function fn(){
    var aaa = bbb = ccc = 10 
    console.log(aaa, bbb, ccc) // 10, 10, 10
}
fn()
console.log(bbb, ccc) // 10, 10

// 以上代码等同于----------------------------

function fn(){
    var aaa =10 
    bbb = 10 // 挂到全局
    ccc = 10
    console.log(aaa, bbb, ccc) // 10, 10, 10
}
fn()
console.log(bbb, ccc) // 10, 10

题目4

var aaa = bbb = {a: 1, b: 2}
console.log(aaa, bbb) // {a: 1, b: 2}, {a: 1, b: 2}
console.log(aaa===bbb)  // true

// 等式后面先执行
// {a: 1, b: 2} 先在堆中开辟地址
// aaa 和 bbb 都指向这个地址

// 以上代码等同于----------------------------

0x666 = {a: 1, b: 2}  // 0x666 代表内存地址
var aaa = 0x666
bbb = 0x666 

14.关于形参和函数返回值(重要)

形参相当于函数内部申明的一个局部变量。

函数返回值默认是undefined。注意是需要执行过后。

var a = 12; b = 13; c = 14;  // 这里注意分号分开和逗号不同,逗号会全部使用var申明
function fn(a){
	console.log(a, b, c); // 10 13 14
	a = 100; // a已经被申明
	b = 200; // b在全局已经被申明
	console.log(a, b, c); // 100 200 14
}
b = fn(10); // 这里注意赋值给b的不是函数,而是函数执行后的返回值
console.log(a, b, c); // 12 undefined 14

// 以上代码等同于----------------------------

var a
function fn(x){
	var a = x
	console.log(a, b, c); // 10 13 14
	a = 100; // 在调用时a已经被申明,在函数作用域,赋值只修改了局部变量
	b = 200; // 在调用时b在全局已经被申明
	console.log(a, b, c); // 100 200 14
}
a = 12; 
b = 13; 
c = 14;
// 这里调用时函数内部的变量才会去查值
b = fn(10); // 这里注意赋值给b的不是函数,而是函数执行后的返回值
console.log(a, b, c); // 12 undefined 14

Access-Control-Allow-前后端设置跨域CORS详解

2017
15/04

跨域访问的项目常在过滤器或者拦截器中添加Access-Control-Allow-(访问权限控制,access-访问)前缀的响应头配置。

response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST,OPTIONS,GET");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "accept,x-requested-with,Content-Type");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Origin", "http://192.168.10.118:8070");

CORS是一个W3C标准,全称是”跨域资源共享”(Cross-origin resource sharing)。

它允许浏览器向跨域(非同源)服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。

本文详细介绍CORS的内部机制。

一、简介

CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。

整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。

因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。

二、两种请求

浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。

只要同时满足以下两大条件,就属于简单请求。

(1) 请求方法是以下三种方法之一:

  1. HEAD
  2. GET
  3. POST

(2)HTTP的头信息不超出以下几种字段:

  1. Accept
  2. Accept-Language
  3. Content-Language
  4. Last-Event-ID
  5. Content-Type:application/x-www-form-urlencoded、multipart/form-data、text/plain

凡是不同时满足上面两个条件,就属于非简单请求。

浏览器对这两种请求的处理,是不一样的。

三、简单请求

3.1 基本流程

对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段。

下面是一个例子,浏览器发现这次跨源AJAX请求是简单请求,就自动在头信息之中,添加一个Origin字段。

GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

上面的头信息中,Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。

如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段(详见下文),就知道出错了,从而抛出一个错误,被XMLHttpRequest的onerror回调函数捕获。注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。

如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。

Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8

上面的头信息之中,有三个与CORS请求相关的字段,都以Access-Control-开头。

(1)Access-Control-Allow-Origin

该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。

(2)Access-Control-Allow-Credentials

该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。

(3)Access-Control-Expose-Headers

该字段可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。上面的例子指定,getResponseHeader(‘FooBar’)可以返回FooBar字段的值。

3.2 withCredentials 属性

上面说到,CORS请求默认不发送Cookie和HTTP认证信息。如果要把Cookie发到服务器,一方面要服务器同意,指定Access-Control-Allow-Credentials字段。(Credentials-资格/证书/凭证)

Access-Control-Allow-Credentials: true

另一方面,开发者必须在AJAX请求中打开withCredentials属性。

var xhr = new XMLHttpRequest();
xhr.withCredentials = true;

否则,即使服务器同意发送Cookie,浏览器也不会发送。或者,服务器要求设置Cookie,浏览器也不会处理。

但是,如果省略withCredentials设置,有的浏览器还是会一起发送Cookie。这时,可以显式关闭withCredentials。

xhr.withCredentials = false;

需要注意的是,如果要发送Cookie,Access-Control-Allow-Origin就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页代码中的document.cookie也无法读取服务器域名下的Cookie。

四、非简单请求

4.1 预检请求

非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。

非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为”预检”请求(preflight)。

浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。

下面是一段浏览器的JavaScript脚本。

var url = 'http://api.alice.com/cors';
var xhr = new XMLHttpRequest();
xhr.open('PUT', url, true);
xhr.setRequestHeader('X-Custom-Header', 'value');
xhr.send();

上面代码中,HTTP请求的方法是PUT,并且发送一个自定义头信息X-Custom-Header。

浏览器发现,这是一个非简单请求,就自动发出一个”预检”请求,要求服务器确认可以这样请求。下面是这个”预检”请求的HTTP头信息。

OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

“预检”请求用的请求方法是OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是Origin,表示请求来自哪个源。

除了Origin字段,”预检”请求的头信息包括两个特殊字段。

(1)Access-Control-Request-Method

该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,上例是PUT。

(2)Access-Control-Request-Headers

该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,上例是X-Custom-Header。

4.2 预检请求的回应

服务器收到”预检”请求以后,检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应。

HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain

上面的HTTP回应中,关键的是Access-Control-Allow-Origin字段,表示http://api.bob.com可以请求数据。该字段也可以设为星号,表示同意任意跨源请求。

Access-Control-Allow-Origin: *

如果浏览器否定了”预检”请求,会返回一个正常的HTTP回应,但是没有任何CORS相关的头信息字段。这时,浏览器就会认定,服务器不同意预检请求,因此触发一个错误,被XMLHttpRequest对象的onerror回调函数捕获。控制台会打印出如下的报错信息。

XMLHttpRequest cannot load http://api.alice.com.
Origin http://api.bob.com is not allowed by Access-Control-Allow-Origin.

服务器回应的其他CORS相关字段如下。

Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 1728000

(1)Access-Control-Allow-Methods

该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次”预检”请求。

(2)Access-Control-Allow-Headers

如果浏览器请求包括Access-Control-Request-Headers字段,则Access-Control-Allow-Headers字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在”预检”中请求的字段。

(3)Access-Control-Allow-Credentials

该字段与简单请求时的含义相同。

(4)Access-Control-Max-Age

该字段可选,用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是20天(1728000秒),即允许缓存该条回应1728000秒(即20天),在此期间,不用发出另一条预检请求。

4.3 浏览器的正常请求和回应

一旦服务器通过了”预检”请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。

下面是”预检”请求之后,浏览器的正常CORS请求。

PUT /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
X-Custom-Header: value
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

上面头信息的Origin字段是浏览器自动添加的。

下面是服务器正常的回应。

Access-Control-Allow-Origin: http://api.bob.com
Content-Type: text/html; charset=utf-8

上面头信息中,Access-Control-Allow-Origin字段是每次回应都必定包含的。

五、与JSONP的比较

CORS与JSONP的使用目的相同,但是比JSONP更强大。

JSONP只支持GET请求,CORS支持所有类型的HTTP请求。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。

图片懒加载实现方式

2017
04/04

懒加载原理

当浏览器滚动到相应的位置时再渲染相应的图片。主要利用图片src属性的重新赋值来实现。

img标签中的src的路径设置为空或默认图片(空白图片、Loading图),缓存真正的图片路径,当浏览器滚动到相应的位置时,再给src属性重新赋值。

懒加载目的

优化前端压力,减少首屏请求数,延迟请求。对服务器压力有一定缓解作用。

但是若图片较大,则会带来较差的用户体验。

方案一:使用滚动事件scroll实现

img标签中的src的路径设置为同一张图片(空白图片),将真正的图片路径保存在data-src属性中,通过scrollTop方法获取浏览器窗口顶部与文档顶部之间的距离,通过clientHeight方法获取可视区高度。scroll触发时,执行事件载入data-src(对每个img标签DOM来求其offsetTop,如果距离顶部的高度 >=可视区高度+窗口距离文档顶部的距离 )。

.lazy-image { 
    background: url('../img/loading.gif') no-repeat center; 
} 
document.addEventListener('scroll', inViewShow)

function inViewShow() {     
    let imageElements = Array.prototype.slice.call(document.querySelectorAll('img.lazy-image'))    
    let len = imageElements.length     
    for(let i = 0; i < len; i++) {         
        let imageElement = imageElements[i]        
        const rect = imageElement.getBoundingClientRect() // 出现在视野的时候加载图片         
        if(rect.top < document.documentElement.clientHeight) {             
            imageElement.src = imageElement.dataset.src // 移除掉已经显示的             
            imageElements.splice(i, 1)             
            len--             
            i--         
        }     
    } 
}

方案二:使用IntersectionObserver实现

IntersectionObserver可以异步监听 目标元素 与其 祖先元素文档视窗 (可视区viewport) 交叉状态的方法。文档详见Intersection Observer – Web API 接口参考 | MDN (mozilla.org)

class LazyImage {    
    constructor(selector) {      
    // 懒记载图片列表,将伪数组转为数组,以便可以使用数组的api      
        this.imageElements = Array.prototype.slice.call(document.querySelectorAll(selector))
        this.init()    
    }      
    inViewShow() {      
        let len = this.imageElements.length      
        for(let i = 0; i < len; i++) {        
            let imageElement = this.imageElements[i]        
            const rect = imageElement.getBoundingClientRect()        
            // 出现在视野的时候加载图片        
            if(rect.top < document.documentElement.clientHeight) {          
                imageElement.src = imageElement.dataset.src          
                // 移除掉已经显示的          
                this.imageElements.splice(i, 1)          
                len--          
                i--          
                if(this.imageElements.length === 0) {            
                   // 如果全部都加载完 则去掉滚动事件监听            
                    document.removeEventListener('scroll', this._throttleFn)         
                 }        
            }      
        }    
    }      
    throttle(fn, delay = 15, mustRun = 30) {      
        let t_start = null     
        let timer = null      
        let context = this      
        return function() {        
            let t_current = +(new Date())        
            let args = Array.prototype.slice.call(arguments)        
            clearTimeout(timer)        
            if(!t_start) {          
                t_start = t_current        
            }        
            if(t_current - t_start > mustRun) {          
                fn.apply(context, args)          
                t_start = t_current        
            } else {          
                timer = setTimeout(() => {            
                    fn.apply(context, args)          
                }, delay)        
            }      
        }    
    }      
    init() {      
       // 通过IntersectionObserver api判断图片是否出现在可视区域内,不需要监听Scroll来判断      
       if ("IntersectionObserver" in window) {        
            let lazyImageObserver = new IntersectionObserver((entries, observer) => { 
                 entries.forEach((entry, index) => {            
                    // 如果元素可见            
                    if (entry.isIntersecting) {              
                        let lazyImage = entry.target              
                        lazyImage.src = lazyImage.dataset.src              
                        lazyImage.classList.remove("lazy-image")              
                        lazyImageObserver.unobserve(lazyImage)              
                        // this.lazyImages.splice(index, 1)            
                    }          
                })        
            })        
            this.lazyImages.forEach(function(lazyImage) {          
                lazyImageObserver.observe(lazyImage);        
            })      
    } else {        
        this.inViewShow()        
        this._throttleFn = this.throttle(this.inViewShow)        
        document.addEventListener('scroll', this._throttleFn.bind(this))      
    }
  }  
}
// 调用例子
new LazyImage('img.lazy-image')

html、js、css渲染顺序

2017
20/03

浏览器通过http或者本地文件收到http响应数据报文后,根据主体中content-type字段值,判断是否为html文档。为html文档时,新建渲染进程,网络进程和渲染进程通过管道通信共享数据。渲染进程一边从管道获取数据一边进行解析(HtmlParse)。

如果此时遇到外部资源,会再次启动网络接口(http)获取外部资源,对于相应的外部资源会给对应的解析器处理,如js会交给js引擎处理,css会交给CSS解析器处理。

正常情况下, html解析器和css解析器同时进行解析, html解析成一个dom树,解析成dom树的同时,css解析器也会解析成一个css 规则树( CSSOM )。html解析器在遇到脚本文件时,默认会停下来去获取脚本(不考虑资源预加载优化),然后执行,期间阻塞DOM构建。

然后dom树和css规则树生成了附件(attachment),附件形成之后,就可以生成一个渲染树(renderTree)了。

然后渲染树进行layout(布局:计算元素的位置信息,从而进行排版)就可以绘制(Painting)了。Padding过程会遍历Render树的各个节点并调用操作系统的图形接口绘屏,最后通过显示器进行呈现(display)。而页面中图片会在呈现之后再进行加载。

值得注意的是,display是一个渐进的过程。为达到更好的用户体验,display呈现引擎会力求尽快将内容显示在屏幕上。它不必等到整个 HTML 文档解析完毕之后,就会开始构建呈现树和设置布局。在不断接收和处理来自网络的其余内容的同时,呈现引擎会将部分内容解析并显示出来。

文件解码成功后会正式开始渲染流程,先会根据 HTML 构建 DOM 树,有 CSS 的话会去构建 CSSOM 树。如果遇到 script 标签的话,会判断是否存在 async 或者 defer ,前者会并行进行下载并执行 JS,后者会先下载文件,然后等待 HTML 解析完成后顺序执行,如果以上都没有,就会阻塞住渲染流程直到 JS 执行完毕。遇到文件下载的会去下载文件,这里如果使用 HTTP 2.0 协议的话会极大的提高多图的下载效率。

初始的 HTML 被完全加载和解析后会触发 DOMContentLoaded 事件

CSSOM 树和 DOM 树构建完成后再通过Layout生成 Render 树,这一步就是确定页面元素的布局、样式等等诸多方面的东西。

在生成 Render 树的过程中,浏览器就开始调用 GPU 绘制,合成图层,将内容显示在屏幕上了

css阻塞

css其实也会阻塞,不要只以为js会阻塞。

CSS会阻塞渲染树的构建,但是不会阻塞DOM构建,但是在CSSOM构建完成之前,页面不会开始渲染(一片空白),会等待css规则树。也就是说css并不会阻塞DOM树的解析(构建),但是会阻塞DOM树渲染。

如果css加载不阻塞DOM树渲染的话,那么当css加载完之后,DOM树可能又得重新重绘或者回流了,这就造成了一些没有必要的损耗。所以我干脆就先把DOM树的结构先解析完,把可以做的工作做完,然后等你css加载完之后,在根据最终的样式来渲染DOM树,这种做法性能方面确实会比较好一点。

如何优化CSS阻塞

与js不一样,js虽然会阻塞后续DOM构建,但是前面已经就绪的内容会进行渲染。CSS虽然不阻塞DOM构建,但是会阻塞后面js的执行,从而间接阻塞完整DOM的构建。

结论:

  1. css加载不会阻塞DOM树的解析
  2. css加载会阻塞DOM树的渲染
  3. css加载会阻塞后面js语句的执行

js阻塞

JS默认也是会阻塞DOM和渲染树的构建的。

HTML解析器在遇到脚本文件时,默认会停下来去获取脚本(不考虑资源预加载优化),然后执行,期间阻塞DOM构建。

也就是说每当浏览器解析到<script>标签(无论内嵌还是外链)时,浏览器会(一根筋地)优先下载、解析并执行该标签中的javaScript代码,而阻塞了其后所有页面内容的下载和渲染。

所以加载顺序是自上而下,但是渲染流程却是如图.所以最好将js放在最下面。

高版本浏览器上已经实现了脚本并行下载。所以外链多个js文件都是并行下载的(下载也会阻塞),但是其执行顺序依然是顺序执行,阻塞执行。

那么如何避免js阻塞呢?如下文章详细介绍。

js外部脚本异步加载方式

渲染的关键词

  • 关键资源: 可能阻止网页首次渲染的资源。
  • 关键路径长度 获取所有关键资源所需的往返次数或总时间。
  • 关键字节: 实现网页首次渲染所需的总字节数,它是所有关键资源传送文件大小的总和。

进程线程

为什么js加载执行会阻塞渲染树构建

这就得从浏览器来说起来,现在的浏览器都是多进程的浏览器,以谷歌来说,他就是多进程的,每当你打开一个新的tab页的时候,他就会去开一个进程来处理这个tab页面。在这个tab里,是单进程,但是却是多线程的。

脚本加载、脚本执行时都会对其他GUI渲染线程进行阻塞。尤其是在HTML页面初次解析时,它们对性能的影响较大。

  • JavaScript引擎线程
  • GUI渲染线程
  • 事件触发线程
  • 定时触发器线程
  • 异步http请求线程

javascript线程

毫无疑问,这东西就是处理js代码的。例如V8引擎,就是解析运行代码。但是为什么是单线程的呢?因为线程存在上下文切换,可能多个线程操作一个dom,这就产生了资源竞争,索性用了单线程。

GUI线程

这个是负责渲染页面,每当页面重绘的时候这个线程都会调用。js引擎在执行的时候,GUI线程会停止执行,处于挂起的一个状态。

js阻塞GUI引擎

因为js是可以操作dom,GUI也是操作dom, 如果同时运行会存在问题,脚本执行和渲染DOM的并发可能会引发严重的冲突,可能同时修改dom,导致渲染前后不一致。所以js执行时,GUI渲染线程会被挂起。

定时触发器器线程

处理一些异步的web api的时候,碰到定时的,会去处理这种定时,再放入事件队列等待放入栈里。

事件触发线程

一些事件被触发时就会调用该线程,比如一些ui事件,定时事件,ajax请求等,都会放到队列里准备执行。

异步http请求线程

一些http请求连接开启之后,都会新开一个线程去处理。

前端重点知识总结—CSS3

2017
12/02

1.CSS3有哪些新属性,举例

  1. 边框属性:border-radius、box-shadow
  2. 背景属性:background-size、background-origin
  3. 2D、3D转换:transform
  4. 动画属性:animation

2.nth-child和:nth-of-type的区别

  1. ele:nth-of-type(n)是指父元素下第n个ele元素,
  2. ele:nth-child(n)是指父元素下第n个元素且这个元素为ele,若不是,则选择失败。
<ul class="demo"> 
        <p>zero</p> 
        <li>one</li> 
        <li>two</li> 
</ul>

上面这个例子,.demo li:nth-child(2)选择的是<li>one</li>节点,而.demo li:nth-of-type(2)则选择的是<li>two</li>节点。

3.:is、:where伪类函数让选择器更简洁

简化选择

使用前:

ul li,
ol li {}

使用后:

:is(ul, ol) li {}

避免 CSS 错误

假如我们的 CSS 中有错误,将导致整个选择器不生效。比如下面的 .content 写成 :content

<div class="container-1">
  <p class="title">I am Gopal</p>
  <div class="content">我是锅巴</div>
</div>

<div class="container-2">
  <p class="title">I am Gopal</p>
  <div class="content">我是锅巴</div>
</div>

写错,将导致都不生效:

.container-1 .title, .container-1 :content {
  color: #885c5c;
}

但假如使用:is().title选择器依然可以生效,如下:

/* content 写错,title 还可以生效 */
.container-2 :is(.title, :content) {
  color: #885c5c;
}

4.css父选择器:has伪类函数

/* 匹配包含 <img> 子元素的 <a> 元素 */
a:has(img) { … }

/* 匹配包含<img>直接后代子元素的<a>元素 */
a:has(> img) { … }

/* 匹配不包含任何H元素的 <section> 元素:: */
section:not(:has(h1, h2, h3, h4, h5, h6))

/* 仅当 <p> 元素紧随其后时才匹配 <h1> 元素 */
h1:has(+ p) { … }

CSS clip:rect 矩形剪裁功能,截取图片某一块

2016
14/12

最近我在制作一款主题的时候,在自适应css设计中,为了调整图片大小,又不愿意改变图片比例的情况下,用到了CSS剪裁功能。

说实话,这个功能在国内运用的比较少。CSS中有一个属性叫做clip,为修剪,剪裁之意。配合其属性关键字rect可以实现元素的矩形裁剪效果。此属性安安稳稳地存在于CSS2.1中,且使用上基本上没有类似于max-height/display:table-cell等浏览器的兼容性问题。但是,貌似大家很少使用此属性。我总结了三点原因:首先是理解上有些门槛;二是其他人使用的不多;三是此属性功能效果有不少替代方案。

我们使用overflow可以实现块内容的剪裁,而图片剪裁我们却很少用到。我们往往是将图片进行等比例缩小。所以很少用到clip剪裁。但是等比例缩小问题来了,他很可能不能将图片缩小为我们想要的尺寸。那么用clip属性就省事不少。

相关CSS代码如下:

.hidden{
 position:absolute;
 clip: rect(1px 1px 1px 1px); /* IE6, IE7 */
 clip: rect(1px, 1px, 1px, 1px);
 }

其中方向含义为rect(top right bottom left),就顺序上而言,top → right → bottom → left,在CSS中是统一相承的,就像是margin的四个值的顺序,border-width等等的四个值顺序——从头顶上开始,顺时针旋转的说~~不过这里的四个值是不可以缩写的。

其中top right bottom left表示各个位置的属性值,就像是width:200px;中的200px,所以,我们会有类似下面的使用:rect(30px 200px 200px 20px)

那这里的top right bottom left究竟指什么的?我们该如何理解呢?

其实是这样的,top right bottom left分别指最终剪裁可见区域的上边,右边,下边与左边。而所有的数值都表示位置,且是相对于原始元素的左上角而言的。于是rect(30px 200px 200px 20px)表示的含义就是:最终剪裁的矩形的上边距离原始元素的上边缘30像素;剪裁矩形的右边缘距离原元素左边缘的距离是200像素;剪裁矩形的下边缘距离原元素顶部的距离为200像素;剪裁矩形的左边缘距离原元素左边缘的距离时20像素。如下图(300像素*300像素)所示:

2011-04-02_000416

这样就不难理解了。但是在实际使用过程中可能与我们想向中的不符。那么就需要不断的改变其四个值去进行尝试。其实我在使用marginpadding属性的时候就经常改变四个值进行微调。

当然,这个属性比较糟心的前提是,图片需要在绝对定位之下才能使用,且使用后可能会改变图片位置。这时候就需要用left right margin-left:-xx margin-right:-xx来调节位置了。当然也可以设置其父元素为绝对定位。而且父元素使用clip对子元素同样有效。如下Html:

<div id="123">
<img src="http://www.xszzz.com/xxx.jpg" >
</div>

我们可以这样写css:

#123{
 position:absolute;
 clip: rect(1px 1px 1px 1px);
 }

好了,此文到此结束,大家不妨试试~

HTTP/2 的优势及性能优化

2016
06/10

历史悠久的超文本传输协议,即HTTP标准。HTTP/2在2015年5月被批准,目前浏览器已经支持HTTP/2。

HTTP/2构建在Google SPDY协议基础之上。NGINX是最早支持SPDY的,也同样率先支持了HTTP/2。NGINX还发布了详尽的白皮书(PDF),介绍了HTTP/2以及它如何基于SPDY构建,并展示了如何实现这个新协议。

SPDY是HTTP/2的上一代,总体性能相同。因为它已经出现好多年了,所以先前有很多浏览器支持SPDY不支持HTTP/2。不过,现在浏览器都开始支持HTTP/2。

HTTP/2的重要特性完全源自SPDY。

  • HTTP/2是二进制(而文本)协议,因此更简洁高效;
  • 它针对每个域只使用一个多路复用的连接,而不是每个文件一个连接;
  • 首部使用特制的HPACK协议(而非SPDY中使用的gzip)压缩;
  • HTTP/2设计了复杂的优先级排定规则,帮助浏览器首先请求最急需的文件,而NGINX已经支持(SPDY的方案要简单一些)。

注意:严格来讲,SPDY和HTTP/2都不需要TLS,但它们在使用SSL/TLS的时候用处最大,而且浏览器只在使用SSL/TLS时才支持SPDY或HTTP/2。

现在是否需要迁移到HTTP/2

HTTP/2并不是万能的,它只对某些Web应用有用,对另外一些则没那么有用。

如果你使用SSL/TLS(以后简称TLS),那么HTTP/2可以提升网站性能。如果你没有,那在使用HTTP/2之前要先支持TLS。这时候,使用TLS的性能损耗大致可以被使用HTTP/2的性能提升抵销。

HTTP2与HTTP1.1最重要的区别就是解决了线头阻塞的问题!其中最重要的改动是:多路复用 (Multiplexing)。

如何升级:要求nginx的最低版本是1.10.0,openssl的最低版本是1.0.2,http/2在浏览器实现上基本只支持https,也就是说当前所有实现 HTTP/2的 Web 浏览器都只支持加密。。

HTTP/2有五大优势

  1. 每个服务器只用一个连接。HTTP/2对每个服务器只使用一个连接,而不是每个文件一个连接。这样,就省掉了多次建立连接的时间,这个时间对TLS尤其明显,因为TLS连接费时间。
  2. 加速TLS交付。HTTP/2只需一次耗时的TLS握手,并且通过一个连接上的多路利用实现最佳性能。HTTP/2还会压缩首部数据,省掉HTTP/1.x时代所需的一些优化工作,比如拼接文件,从而提高缓存利用率。
  3. 简化Web应用。使用HTTP/2可以让Web开发者省很多事,因为不用再做那些针对HTTP/1.x的优化工作了。
  4. 适合内容混杂的页面。HTTP/2特别适合混合了HTML、CSS、JavaScript、图片和有限多媒体的传统页面。浏览器可以优先安排那些重要的文件请求,让页面的关键部分先出现,快出现。
  5. 更安全。通过减少TLS的性能损失,可以让更多应用使用TLS,从而让用户信息更安全。

HTTP/2的多路复用示意图

HTTP/2不足

  1. 单连接开销比较大。HPACK数据压缩算法会更新两端的查找表。这样可以让连接有状态,而破坏状态就意味着要重建查找表,另外单连接占用内存较多。
  2. 你可能不需要SSL。如果你的数据不需要保护,或者已经使用DRM或其他编码进行保护了,那么TLS的安全性对你可能无所谓。
  3. 需要抛弃针对HTTP/1.x的优化。HTTP/1.x优化在支持HTTP/2的浏览器中会影响性能,因此可能需要花时间把它们推倒重来。
  4. 对下载大文件不利。如果你的应用主要提供大文件下载或者流媒体播放,那可能不想用TLS,而且在只有一个流的情况下,多路复用也体现不出什么优势。
  5. 你的客户也许不在乎。你的客户很可能不在乎他分享的自家猫咪的视频是否受到TLS和HTTP/2的保护。

总之,一切要看性能。这方面,有好消息也有坏消息。

好消息是我们在内部对NGINX做过测试,结果从理论上能够得到印证:对于要通过典型网络延迟请求的混合内容网页,HTTP/2的性能好于HTTP/1.x和HTTPS。基于连接的RTT(延迟),结果可以分三种情况。

  • 很低的RTT(0-20ms):HTTP/1.x、HTTP/2和HTTPS基本无差别。
  • 典型网络RTT(30-250ms):HTTP/2比HTTP/1.x快,而且它们都比HTTPS快。美国两个相邻城市间的RTT约为30 ms,而东西海岸间(约3000英里)则约为70 ms。东京到伦敦间最短路径的RTT大约240 ms。
  • 高RTT(300ms及以上):HTTP/1.x比HTTP/2快,后者又比HTTPS快。

终止HTTP/2和TLS(已有环境不必改动)

终止协议意味着客户端使用期望的协议连接代理服务器,比如TLS或HTTP/2,然后代理服务器再去连接应用服务器、数据库服务器等,但不需要使用相同的协议,如下图所示。

使用独立的服务器终止协议意味着使用多服务器架构。多服务器可能是多个物理服务器、多个虚拟服务器,或者AWS这样的云环境中的多个虚拟服务器实例。多服务器就比单服务器复杂,或者比应用服务器/数据库服务器的组合复杂。不过,多服务器架构有很多好处,而且很多流量大的网站也必须用这种架构。

配置了服务器或者虚拟服务器之后,很多事情都成为可能。新服务器可以分担其他服务器的负载,可用于负载平衡、静态文件缓存和其他用途。另外,也可以让添加和替换应用服务器或其他服务器更容易。

NGINX和NGINX Plus经常被用来终止TLS和HTTP/2协议、负载平衡。已有环境不必改动,除非要把NGINX服务器挪到前端。

找出为HTTP/1.x优化的代码

在决定采用HTTP/2之前,首先得知道你的代码有哪些是针对HTTP/1.x优化过的。大概有四方面的优化。

  1. 分域存储。为了实现并行请求文件,你可能把文件分散到了不同的域里,CDN会自动这么做。但分域存储会影响HTTP/2的性能,建议使用HTTP/2友好的分域存储(建议七),只针对HTTP/1.x用户分域。
  2. 雪碧图。雪碧图把很多图片拼成一个文件,然后通过代码按需取得每个图片。雪碧图在HTTP/2的环境下没太大用处,但还是有点用的。
  3. 拼接的代码文件。与使用雪碧图的原因类似,很多独立的文件也会被弄成一个,然后浏览器再从其中找到并运行需要的文件。
  4. 插入行内的文件。CSS代码、JavaScript代码,甚至图片等被直接插到HTML文件中的内容。这样可以减少文件传输,代价是初始HTML文件较大。

后面三种优化都涉及把小文件塞进一个较大的文件里,目的是减少新建连接的初始化和握手,这些操作对TLS而言非常费时间。

第一种优化即分域存储恰恰相反,强制打开多个连接,目的是并行地从不同的域获取文件。这两种看似矛盾的技术对于HTTP/1.x下的站点却十分有效。然而,要用好这两种技术,必须投入大量时间、精力和资源,用于实现、管理和运维。

在采用HTTP/2之前,需要找出应用了这些优化的代码,分析一下它们会不会影响你的应用设计和工作流程。这样在迁移到HTTP/2之后,就可以着手改造它们,甚至撤销某些优化。

部署HTTP/2

事实上,部署HTTP/2或SPDY并不难。如果你使用NGINX,只要在配置文件中启动相应的协议就可以了,参见这里了解如何启用HTTP/2(PDF)。浏览器和服务器会协商采用什么协议,如果浏览器支持HTTP/2(而且也在使用TLS),就会使用HTTP/2。

配置完服务器后,使用支持HTTP/2浏览器的用户就会基于HTTP/2运行你的应用,而使用旧版本浏览器的用户则会继续使用HTTP/1.x运行你的应用,如下图所示。如果你的网站流量非常大,那么应该监测改变前后的性能,对于性能降低的情况,可能就得撤销更改。

注意:使用HTTP/2及其单连接之后,NGINX某些配置的重要性会很明显,特别要注意的是output_buffersproxy_buffersssl_buffer_size等指令,多测试一下。参见general configuration notes,特定的SSL建议(在这里 and here),以及NGINX关于SSL性能的白皮书(PDF)。

注意:使用HTTP/2传输密文要格外注意。HTTP/2的RFC中有一个长长的列表,列出了要避免的加密套件。

再谈HTTP/1.x优化

撤销和修改针对HTTP/1.x优化的代码居然是实现HTTP/2最有创意的部分。这里面有几个问题要注意。

在开始运作之前,必须考虑旧版本浏览器用户是否好过。之后,可以采取三个策略撤销和修改HTTP/1.x的优化。

  • 什么也不用做。假如你并没有针对HTTP/1.x做过优化,或者只做过少量优化,那么你几乎什么也不用做,就可以直接迁移到HTTP/2。
  • 有选择地去做。第二种情况是减少合并某些文件,而不是完全不合并。比如,牵扯到很多场景的雪碧图就不用动,而被塞得满满的HTML可能就要分离出来一些。
  • 完全撤销HTTP/1.x优化

缓存还是普适的。理论上,缓存操作非常适合小文件特别多的情况。但是,小文件多也意味着文件I/O多。因此一些相近文件的合并还是必要的,一方面要考虑工作流程,另一方面要考虑应用性能。建议多关注一下其他人在过渡到HTTP/2过程中的一些经验。

实现智能分域

分域存储可能是最极端但也最成功的HTTP/1.x优化策略。它能够提升HTTP/1.x下的应用性能,但在HTTP/2之下,其性能提升可以忽略不讲(因为只有一个连接。)

对HTTP/2友好的分域,要保证以下两点。

  • 让多个域名解析到同一个IP。
  • 确保证书包含通配符,以便所有分域名都可以使用,适当的多域证书当然也可以。

具体细节,请参考这里

有了这些保障,分域还会继续对HTTP/1.x有效,即域名仍然可以触发浏览器创建更多连接,但对HTTP/2则无效,因为这些域名会被看成同一个域,一个连接就可以访问所有域名了。

小结

HTTP/2和TLS组合可以提升你的站点性能,并且让用户觉得你的网站很安全。

前端重点知识总结—CSS基础

2016
13/09

1.什么是盒子模型?

在网页中,一个元素所占空间类似于一个盒子,包括元素的内容(content),元素的内边距(padding),元素的边框(border),元素的外边距(margin)四个部分。4个部分一起构成了元素的盒模型。

box-sizing 属性控制盒模型的尺寸。默认情况下,box-sizing的值为content-box。代表一个盒子的整体宽高由设置的width和height再加上内边距和边框的尺寸。也就是设置width

和height只决定了元素内容content的尺寸。,而内边距和边框尺寸是另算的。

若值为border-box,则为怪异模式的盒模型。设置元素的宽高包含了内容以及内边距、边框的整体尺寸。

2.相邻的两个inline-block节点为什么会出现间隔,该如何解决

原因:元素被当成行内元素排版的时候,原来HTML代码中的回车换行被转成一个空白符,在字体不为0的情况下,空白符占据一定宽度。这个宽度会随着字体大小变化而变化。

解决办法:

  1. font-size:父元素字体为0,子元素设置字体大小。
  2. 改变书写方式,子元素书写代码时不要换行。
  3. 使用margin负值将子元素向左偏移。
  4. 父元素display: table;letter-spacing: 0

3.display有哪些取值

  1. none 此元素不会被显示,并且不占据页面空间,这也是与visibility:hidden不同的地方,设置visibility:hidden的元素,不会被显示,但是还是会占据原来的页面空间。
  2. inline 行内元素会在一行内显示,超出屏幕宽度自动换行,不能设置宽度和高度,元素的宽度和高度只能是靠元素内的内容撑开。示例元素:span,b,i,a,u,sub,sup,strong,em
  3. block 块级元素 会独占一行,如果不设置宽度,其宽度会自动填满父元素的宽度,可以设置宽高,即使设置了宽度,小于父元素的宽度,块级元素也会独占一行。示例元素:div,h1-h6,ul,ol,dl,p
  4. inline-block 行内块元素 与行内元素一样可以再一行内显示,而且可以设置宽高,可以设置margin和padding。示例元素:input,button,img
  5. list-item 列表元素。示例元素:li
  6. table 会作为块级表格来显示(类似于<table>),表格前后带有换行符。
  7. inline-table 会作为内联表格来显示(类似于<table>),表格前后没有换行符。
  8. flex 弹性布局,火狐可以直接使用,谷歌和欧朋需要在属性值前面加-webkit-前缀(当前已支持),比较适合移动端开发使用。

4. CSS选择符有哪些?优先级?内联和important哪个优先级高?

标签选择符、class类选择符、id选择符、属性选择符[herf^='http']、伪类选择符:hover/::before/::after、相邻选择符+/~、子级选择符>、继承选择器,通配符*

优先级:!important > 内联样式>ID选择器 > 类选择器 > 标签 > 通配符 > 继承 > 浏览器默认属性

5.清除浮动的几种方式,各自的优缺点

  1. 使用空标签清除浮动 clear:both,但会增加无意义的标签
  2. 父级元素使用overflow:auto,可以清除子元素的浮动。(overfolow形成bfc,可以清除浮动)
  3. 是用::afert伪元素清除浮动(用于非IE浏览器,兼容ie67用zoom:1)

6. CSS首行缩进

设置text-indent属性

7.字间距

设置letter-spacing属性

8.行内元素和块级元素的转换

使用display:blockdisplay:inline

9.隔行变色的两种方法

  1. 使用JS循环所有li标签,对其index取模运算(如i%2==0),值为0或不为0设置两种背景色
  2. 使用css3的结构伪类选择器:nth-child(even):nth-child(odd):nth-child(2n):nth-child(2n-1)等设置不同背景色。Even\2n代表偶数行,odd/2b-1代表奇数行

10.css文字竖排显示的方法

① 使用writing-mode属性

  • 语法:writing-mode:lr-tbwriting-mode:tb-rl
  • 参数:lr-tb:从左向右,从上往下;tb-rl:从上往下,从右向左。

② 限制宽度,使用换行实现

width: 30px;
word-break: break-word;

11.CSS实现垂直、水平居中

1) 文字内容居中

水平居中:text-align设置行高line-height

2) 块级元素居中

  1. 设置元素宽高,使用左右margin值为auto实现水平居中,然后使用css3的calc语法设置上边距为父级元素高度的一半减去元素高度的一半。缺点是必须支持css3,且元素宽高固定。
  2. 设置元素宽高,父级相对定位,当前元素绝对定位,设置top和left分别为父级元素宽高的50%,然后在使用负的margin回退自身宽高的一半。缺点是元素宽高必须固定。
  3. 利用定位及margin:auto实现,父级相对定位,当前元素绝对定位,设置然后设置定位的上下左右都为0,magin:auto自动适应。可以实现水平垂直居中。缺点是当前元素宽高固定。
  4. 父级相对定位,当前元素绝对定位,设置top和left分别为父级元素宽高的50%,然后使用transform:translate(-50%,-50%)。优点是解决元素宽高不固定情况。缺点是浏览器需支持css3
  5. 使用flex伸缩布局,父级元素添加display: flex; justify-content: center;//使子项目水平居中align-items: center;//使子项目垂直居中,缺点是兼容性较差,不支持IE低版本。
  6. 使用table布局,父级元素添加display:table-cell;vertical-align:middle;//实现垂直居中,text-align:center;//水平居中。子元素设置为内联元素,添加display:inline-block;优点是元素宽高可以不固定。
  7. 使用浮动实现水平居中,需要在元素外嵌套两层元素。最外层元素设置左浮动,宽度100%(即float:left;width:100%)。中间层元素设置左浮动、相对定位,左边距为50%(即float:left;position:relative;left:50%)。当前元素设置左浮动、相对定位,右边距为50%(即float:left;position:relative;right:50%)。

12.CSS相邻兄弟选择器

  1. 选择下一个元素 `h1 + p {margin-top:50px;}`(可以选取h1之后的第一个p节点)
  2. 选择后续的所有兄弟 `h1 ~ p {background: #aaf;}`(可以选取h1之后的所有p节点)

13.pointer-events 指定在什么情况下 (如果有) 某个特定的图形元素可以成为鼠标事件的 target

  1. 常用值为none`pointer-events:none;`,默认值为`auto`,其它值针对SVG。
  2. 常用`none`来取消遮罩块的鼠标事件,避免遮罩块影响主元素层的事件。

14.触发BFC的条件

BFC:上下文块状格式化,是一个独立的布局环境,其中的元素布局是不受外界的影响,有独立的渲染区域,他的子元素可以被BFC定义位置。

说明:BFC与其他块状元素不同的是他脱离文档流,隔离开不受其他元素的影响,自己定义自己的样式位置,以及其他属性。

BFC布局规则

  1. 内部的Box会在垂直方向,一个接一个地放置。
  2. Box垂直方向的距离由margin决定。属于同一个BFC的两个相邻Box的margin会发生重叠
  3. 每个元素的margin box的左边, 与包含块border box的左边相接触(对于从左往右的格式化,否则相反)。即使存在浮动也是如此。
  4. BFC的区域不会与float box重叠。
  5. BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此。
  6. 计算BFC的高度时,浮动元素也参与计算

触发条件:

  1. 当添加了float属性,且float不为none
  2. 添加定位,position值为absolutefixed
  3. 将元素强制转换类型(display:inline-blocktable-celltable-captionflexinline-flex
  4. 块元素添加overflow属性,且属性不能为visible

注:浮动元素不会影响BFC的页面布局,且在清除浮动时只能将BFC前面的浮动元素清除掉。

前端重点知识梳理—DOM

2016
13/06

文档对象模型(Document Object Model,简称DOM),是W3C组织推荐的处理可扩展标记语言的标准编程接口。 通过 DOM 提供的接口可以对页面上的各种元素进行操作(大小、位置、颜色等)。DOM 通过创建树来表示文档,从而使开发者对文档的内容和结构进行操作。

1. js怎样添加、移除、移动、复制、创建和查找节点?

1)创建新节点

  1. createDocumentFragment() //创建一个DOM片段
  2. createElement() //创建一个具体的元素
  3. createTextNode() //创建一个文本节点

2)添加、移除、替换、插入

  1. appendChild() //添加
  2. removeChild() //移除
  3. replaceChild() //替换
  4. insertBefore() //插入

3)查找

  1. getElementsByTagName() //通过标签名称
  2. getElementsByName() //通过元素的Name属性的值
  3. getElementById() //通过元素Id,唯一性
  4. getElementsByClassName() //通过class类名称(IE9及以上版本才兼容)
  5. querySelector()  //获取某个元素(IE8及以上)
  6. querySelectorAll() //获取所有相同元素(IE8及以上)

2.DOM位置及尺寸获取有哪些方法,各自区别

  1. clientHeight/clientWidth:可视区尺寸(内容的可见尺寸),不包含border和滚动条。
  2. offsetHeight/offsetWidth:可视区高度(对象的可见尺寸),包含border和滚动条。
  3. scrollHeight/scrollWidth:元素完整尺寸,包含了因滚动被隐藏的部分。
  4. clientTop/clientLeft:边框border尺寸,未指定情况为0。
  5. scrollLeft/scrollTop:已滚动尺寸。计算已经滚动到元素的左边界或上边界的像素数。
  6. offsetLeft/offsetTop:当前元素距浏览器边界的偏移量,以像素为单位。

3.window.onload和DOMContentLoaded的区别

  1. 当 onload 事件触发时,页面上所有的DOM,样式表,脚本,图片,flash,iframe都已经加载完成了。
  2. 当 DOMContentLoaded 事件触发时,仅当DOM加载完成,不包括样式表,图片,flash,iframe。

分享经常用到的公用CSS,让网页兼容各大浏览器

2014
17/12

分享经常用到的公用css,让网页兼容各大浏览器,这样每写一个网页直接引用就可以了,这样省事的代码,还有神马理由不收藏呢?

/*———-通用———-*/

ul,ol,li,p,h1,h2,h3,h4,h5,form,table,td,img,div,a,dl,dt,dd{margin:0;padding:0;border:0;}

body,select,input{padding:0;margin:0;text-align:left;font-size:12px;font-family:”宋体”,”Arial”;}

body{color:#000;}

ul,li,ol{list-style-type:none;}

input{outline:none;}

img{vertical-align:bottom;}

table{border-collapse:collapse;}

a img{border:none;}

a{color:#000;text-decoration:none;}

a:hover{text-decoration:underline;}

.clear{ clear:both;}

.cl:after{ content:””; display:block;visibility:hidden; height:0; clear:both;}

.cl {zoom:1;}

.fl{float:left; display:inline;}

.fr{float:right; display:inline;}