JavaScript原型

原型概述

JavaScript中的对象有一个特殊的[[Prototype]]内置属性,它是对于其他对象的引用。当查找对象的属性和方法时,如果在该对象本身不存在时,就会去[[Prototype]](浏览器的实现为属性proto)所指的对象(也成为对象原型)中查找,正是对象和对象原型的这种单向的关系,将对象与对象有层次地关联起来。由于几乎每个对象都有原型(通过Object.create(null)创建的对象没有原型,一般的原型链的终点Object.prototype没有原型),这些对象通过proto就形成了我们常说的原型链。原型链是JavaScript中非常重要的一环。

原型链中属性查找规则

由于对象与对象原型之间是由单向的引用关联的,所以,如果对象原型的属性或方法发生改变时,对象取得的处于原型对象的该属性或方法也将发生改变。对象读取属性和方法的规则为:如果对象中不存在,则去对象原型中寻找,还不存在,再去对象原型的对象原型中查找,直至原型链的终点Object.prototype(一般都是它)。

上述查找方法适用于一般的获取操作,如:.操作获取、for(var property in obj)(该方法只能获取属性,不能获取方法)。另外,如果只想在对象本身查找,而不去原型链中查找的话,就要使用Object.getOwnPropertyNames(o)返回非继承属性的名字,或者使用Object.hasOwnProperty(propname)检查该属性是否是继承的。

原型链中属性的赋值规则

考虑到对象和对象原型的关系,我们在给对象属性和方法赋值时,不应该去修改对象原型中对象的属性和方法。所以,在给对象属性和方法赋值时,一般会在对象中添加相应的属性和方法,而不是去修改原型对象中的属性和方法,这样就保证了原型的改变不会影响原型对象(原型对象改变倒是可能影响原型),实例如以下代码。为什么说一般呢?因为假如给对象a添加属性name,其原型对象中也有name属性,但是却被标记为只读(writable:false)时,那么就无法给a添加name属性,严格模式下会报错。

1
2
3
4
var a = { name: 'a' };
var b = Object.create(a, { name: { value: 'b', writable: true, enumerable: true, configurable: true }}); //
b.name; //'b' b对象有name属性和__proto__(指向a)
a.name; //'a' a对象有name属性和__proto__(指向Object.prototype)

获取/检查原型链

  • Object.create(proto,descriptions),创建指定原型对象的对象。
  • Object.getPrototypeOf(o),返回对象o的原型,一般都等于o.proto
  • a.isPrototypeOf(b),检查a是不是b的原型链中的一员。
  • o.proto,指向o的原型对象,也可以使用o.proto.proto获取o原型的原型,依次类推。也可以通过该属性修改原型对象。但不同的浏览器实现不同。
  • a instanceof Object,检查a的原型链中是否有Object.prototype。a和Object只是示例,此方法常用于检查前者是否是后者使用new构造出的函数。

  • Object.setPrototypeOf(b,a),ES6中的新方法,将b的原型对象设为a,相当于b.proto = a;

原型继承

上面讲了很多关于原型和原型链的基础知识,熟悉原型链并熟练应用原型链是JavaScript的基本功。特别是使用运行原型链实现“继承”。
对象o的原型对象是o.proto,而函数Foo有一个prototype属性,指向使用new Foo()产生的对象f的f.proto

函数/prototype/new

每个函数都有一个属性prototype(且只有函数有,对象没有),该属性指向一个对象,虽然很多地方都称其为该函数的原型,但是它的真正作用是:在var f = new Foo()时,会产生一个对象f(不是函数),f.proto指向Foo.prototype。在我看来,将Foo.prototype说为Foo函数的原型是不准确的,会造成很多误会,而Foo的原型对象(即Foo.proto)其实为Funciton.prototype原型对象。所以,此处应该多加注意。

那么,接下来,我们看看var f = new Foo()到底做了什么。在我们执行new操作时,会调用Foo.prototype.constructor()函数,而该函数指向Foo,即:Foo.prototype.constructor 等于 Foo,调用Foo函数本身。并在调用完成后返回this对象,所以在Foo函数中指定this.name = 'f'的话,返回的对象就会拥有name属性,且name属于对象本身。另外,new操作会将f.proto指向Foo.prototype对象。

一般情况下,Foo.prototype中由一个构造constructor和一个指向Objcet.prototype的原型对象组成(不考虑多层原型链的情况下)。

原型’继承’

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function A(name){
this.name = name;
}
A.prototype.myName = function(){
return this.name;
}

function B(name, label){
this.name = name;
this.label = label
}

B.prototype = Object.create(A.prototype); //此处使用B.prototype = new A();并不好,会调用A()函数,可能产生副作用。

B.prototype.myLabel = function(){
return this.label;
}

var b = new B('b', 'label b');
b.myName(); //'a'
b.myLabel(); //'label b'

b.__proto__ === B.prototype; //true
B.prototype.__proto__ === A.prototype; //true
b.__proto__.__proto__ === A.prototype; //true
A.prototype.__proto__ === Object.prototype; //true
b.__proto__.__proto__.__proto__ === Object.prototype; //true

上面例子中将B的prototype的原型指向A的prototype,这样b就形成了原型链,依次指向B.prototype、A.prototype、Object.prototype。而B的prototype的原型指向A的prototype的方法主要有;

  1. B.prototype = Object.create(A.prototype); B.prototype中只有一个原型属性proto,很干净纯粹;
  2. B.prototype = new A(); 会执行A()函数。另外,如果使用B.prototype = new A(‘a’); 会造成b.proto不等于B.prototype。因为b.proto没有name属性。
  3. Object.setPrototypeOf( B.prototype, A.prototype ); 这个是ES6中提供的原生方法,当然是最好的。

原生函数

前面说过,只有函数才有prototype属性,然而为什么Object、Array、Function等都有prototype呢,是的因为他们也是函数,并且可以使用new操作符来构造相应的对象。我们都知道Array、Function这些静态对象也都是对象,他们的原型链中自然也有Object.prototype。其关系为:Array.prototype.proto = Function.prototype.proto = Object.prototype;

1
2
3
4
5
6
Array.prototype; //[]
Function.prototype; //function () {}
Object.prototype; // Object {} 对象,包含constructor、toString等
Function.prototype.__proto__; // Object.prototype,包含constructor、toString等

Object.__proto__; //function () {}, 即Function.prototype;因为Object也是函数

JavaScript计时

JavaScript与时间相关的函数或对象

在JavaScript中,与时间相关的对象为Date类型对象,可以获取现在的时间等。如果要对使一个函数延迟执行,可以使用setTimeout();如果要让一个函数周期性执行,可以使用setInterval()。同时在最新的Web Timing API中,可以使用window.performance来获取页面在被加载到浏览器都经历了哪些阶段,以及这些阶段的时间,从而找出性能瓶颈并解决。

setTimeout()与setInterval()

JavaScript是单线程语言,但它允许通过超时值setTimeout()和间歇时间值setInterval()来调用代码在特定的时刻执行。由于JavaScript的单线程特性,这两个方法对于时间的控制都是不精确的。这两个方法都是有window对象提供的,所以其具体实现与浏览器相关,故对时间控制的准确程度因为浏览器不同而不同。通过测试,火狐对于时间的控制更加精确一些。

setTimeout()与clearTimeout()

setTimeout接受两个参数:要执行的代码和以毫秒表示的时间。第一个参数可以是一个包含JavaScript代码的字符串(和在eval()中一样,所以不推荐此方式),也可以是一个函数。通过setTimeout()设置后,会返回一个数值ID,是这个将要执行代码的唯一标示;要取消,就需要调用clearTimout(ID)来取消它。示例如下:

1
2
3
4
5
setTimout("alert('hello')",1000)l;//不推荐此参数类型。一秒后调用
var timeoutId = setTimout(function(){
alert('hello');
},1000);//推荐此参数类型。
clearTimeout(timeoutId);//取消这个超时调用。

setInterval()与clearInterval()

setInterval与setTimeout一样都接受两个参数。不同之处在于:setInterval为间歇调用。下面实例中,展示了一个倒计时如下:

1
2
3
4
5
6
7
8
9
10
var num=10, max=10, interval=null;
intervalId = setInterval(function(){
if(num>0){
console.log(num);
}else{
clearInterval(intervalId);
console.log('Time Up!');
}
num--;
},1000)

实现原理

暂时略,以后补上~

Date

ECMAScript中的Date类型是在早期Java的java.util.Date类基础上构建的。Date类型使用UTC1970年1月1日零时开始经过的毫秒数来保存日期。

Date创建

要创建一个日期对象,可以使用new来新建,并传入相应的日期参数(如果为空,则表示当前时刻)。另外,如果只是为了获取当前时间,也可以使用Date.now()方法。Date提供了Date.parse()和Date.UTC()来将你输入的日期转化为UTC格式的Date。
Date.parse解析的格式:将地区设置为美国的浏览器接受的日期格式为:月/日/年、 英文月名 日,年、 英文星期几 英文月 日 年 时:分:秒 时区(如TUE May 25 2004 00:00:00 GMT-0700)、YYYY-MM-DDTHH:mm:ss.sssZ
Date.UTC()的参数分别为年份、月份(0表示1月)、日、小时、分钟、秒、毫秒。上述参数中,只有年份和月份是必须要有的。示例如下:

1
2
3
4
5
6
var now = new Date();//现在时刻,格式与浏览器行为有关。
var now = Date.now();//格式为一系列数字
var someDate = new Date(Date.parse('May 25,2004'));//2004年5月25日零时
var someDate = new Date('May 25,2004');//同上,会自动调用Date.parse
var otherDate = new Date(Date.UTC(2005,4,5,17,55,55));//GMT时间2005年5月5日下午5:55:55
var otherDate = new Date(2005,4,5,17,55,55);//*本地时间*2005年5月5日下午5:55:55

在不同的浏览器中,时间的表示格式可能不同,对于时区的处理也不相同。如上面的例子中,chrome在处理otherDate都是按照本地时区,而火狐则是根据上述行为处理的。由于这些时间都是对象,所以不能通过==来判断两个时间是否相同(对象引用相同才会相等),而应该判断两个时间插值是否为0。

日期表示

Date类型有重写了toLocaleString()、toString()方法,其可以返回特定格式的时间,但浏览器之前可能存在不同。菜外还有些其他专门用于将日期格式化为字符串的方法。如下:

  • toLocaleString(),按照与浏览器设置的地区相适应的格式返回日期和时间。
  • toString(),通常返回带有时区信息的日期和时间
  • toDateString(),星期几 月 日 年(特定于格式)
  • toTimeString(),时:分:秒 时区(特定于格式)
  • toLocalDateString(),星期几 月 日 年(特定于本地区)
  • toLocalTimeString(),时:分:秒 时区(特定于本地区)
  • toUTCString(),与toGMTString()方法等价

日期相关的方法

特别注意,日期中的月份都是从0开始的。下面的方法也是如此。

  • getTime(),返回表示日期的毫秒数;与valueOf()返回值相同
  • getMonth(),略。还有返回getFullYear、getDate、getHours、getMinutes、getSeconds、getMilliseconds等。
  • setMonth(),略。可以set的也同上。

Web计时

Web Timing API让开发人员通过JavaScript就能使用浏览器内部的度量结果,从而进行分析。通过浏览器开始请求页面的时间、发生load事件的时间等一系列过程的时间值,就可以知道哪些阶段可能是影响性能的瓶颈的。当然,也可以使用chrome的强大的调试工具,这是一种非常方便的方法。该API不再进行内容介绍。

作用域

作用域的概念

存储变量中的值,并能对其进行访问和修改,是几乎所有的编程语言最基本的功能之一。而正是这种功能是程序具有了状态。那些随之而来就有很多问题了,这些变量如何存储,程序如何访问和修改。如此一来,就需要一系列的规则来指明如何存储变量,如何更方便的寻找访问变量。我们可以将这一系列的规则称为作用域。然而,在我看来,我们通常所说的一个个作用域,可以认为是包含了变量的存储访问等一系列规则的变量的集合。

JavaScript编译原理

传统的编译语言的流程中,一般可以分为三个步骤:分词/词法分析(分解为词法单元)、解析/语法分析(构建抽象语法树)、代码生成(生成可执行代码)。与传统的编译语言不同,JavaScript的编译过程不发生在构建之前,而是通常发生在代码执行前的几微妙时间内(也可称为预编译)。故,JavaScript常称为动态解释性语言。

JavaScript的处理

在处理过程中,涉及到的有引擎、编译器和作用域。

  • 引擎:负责整个JS程序的编译和执行过程
  • 编译器:负责语法分析及代码生成等dirty work
  • 作用域:负责收集和维护变量组成的一系列查询(值查询RHS、位置查询LHS),并通过一套严格的规则,确定当前执行的代码对这些变量的访问权限。

JavaScript处理实例

1
2
3
4
function foo(a)  {
console.log( a ); // 2
}

foo(2);

在上述代码中,编译器先将函数foo的定义放入作用域中;在执行foo(2)时,通过RHS查询foo变量的值,并执行;然后,对参数a进行LHS查询其位置,并将2赋值给a;通过RHS查询console变量,及其子变量log;通过RHS查询a的值,并运行。

  • 在JS执行前会先进行预编译,此时会提取var定义的变量(不进行赋值操作,故都为undefined)和函数声明,将其加到作用域。然后进行赋值操作、函数执行、条件判断等。
  • LHS为左查询,获取其位置,进行赋值操作。RHS为右查询,获取变量的值。
  • 在作用域中,定义一个变量var a时,会先在当前作用域中查找是否存在,存在则忽略,否则新建。
  • 在进行LHS时,如果在全局作用域中仍然无法找到:“严格模式”下,抛出ReferenceError,“非严格模式”下,在全局作用域中新建这个全局变量。
  • 在进行RHS时,当前作用域中找不到,则去上一层作用域找,直到在全局作用域中也找不到时,抛出ReferenceError。
  • ReferenceError同作用域判断失败有关。如果查到到相应的值,但是在其值上进行了不合理的操作,如引用null类型的值的属性,会抛出TypeError错误。

词法作用域

作用域有两种主要的工作模式,第一种为比较普遍的词法作用域,另一种为动态作用域(如bash脚本等)。JavaScript采用此法作用域。词法作用域意味着作用域由书写代码时函数声明的位置来决定,即JavaScript的嵌套作用域机制。
词法作用域是由写代码时将变量和块作用域写在哪里来决定的,因此当词法分析器处理代码时会保持作用域不变。然而使用eval和with可以欺骗词法(但这两种方法都对性能不好,轻易不要使用)。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function foo(str, a) {
eval( str ); // 欺骗!
console.log( a, b );
}
var b = 2;
foo( "var b = 3;", 1 ); // 1, 3

function foo(obj) {
with (obj) { //修改作用域
a = 2;
}
}
var o2 = {
b: 3
};
foo( o2 ); //o2.a未定义,a泄漏到了全局作用域

函数作用域与块作用域

在JavaScript中,函数是最常见的作用域单元。从ES3开始,try/catch结构在catch语句中具有块级作用域。另外,with也是块级作用域的一个例子。但是目前在for选好语句、if/else语句、while语句等控制语句中,任然不存在块级作用域,但是可以使用let声明在这些控制语句中声明块级变量。因为let在ES6以上版本中才可以使用,所以要使用let,就要对代码进行转换,从而使其可以转换为与let效果相同的可以在ES5中运行的代码(使用try/catch)。

闭包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
for (var i=1; i<=5; i++) {
setTimeout( function timer() {
console.log( i );
}, i*1000 );
}//连续输出5个6

for (var i=1; i<=5; i++) {
(function(j) {
setTimeout( function timer() {
console.log( j );
}, j*1000 );
})( i );
}//1、2、3、4、5

//let每次迭代都会声明,随后的每个迭代都会使用上一个迭代结束时的值来初始化这个变量
for (let i=1; i<=5; i++) {
let j = i; // 是的,闭包的块作用域!
setTimeout( function timer() {
console.log( j );
}, j*1000 );
}//1、2、3、4、5

上面的例子为经常出现的问题。ES6的let可以更好的解决这个问题。在上面的例子中,timer()依然持有对该作用域的引用,而这个引用就称为闭包,如果作用域中还有这种引用,那么这个作用域包含其外层作用域就不会被回收。所以,在闭包使用完成后,要对闭包进行回收,就要将其设置为null引用。
根据《你不知道的JS》一书的定义,Closure is when a function is able to remember and access its lexical scope even when that function is executing outside its lexical scope.
用任何形式的函数类型的值进行传递,且该函数在别处调用时就会产生闭包。另外,在定时器、事件监听器、AJAX请求等任务中,只要使用了回调函数,其实就是在使用闭包。

模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//现代的模块机制
var MyModules = (function Manager() {
var modules = {};

function define(name, deps, impl) {
for (var i=0; i<deps.length; i++) {
deps[i] = modules[deps[i]];
}
modules[name] = impl.apply( impl, deps );
}

function get(name) {
return modules[name];
}

return {
define: define,
get: get
};
})();

CSS盒模型与布局

CSS盒模型

盒模型

CSS盒模型(CSS2.1盒模型标准)从内到外依次为:

  1. content边或内边。content边环绕着盒的width和height指出的矩形,通常取决于元素的呈现内容。
  2. padding边。如果padding宽为0,则与content边相同。定义了内容与border的距离。
  3. border边。边框,可以通过定义border显示需要的元素的边框。
  4. margin边。定义了border与包含块的距离。margin的背景总是透明的,所以不能通过本元素的background指定背景样式。

box-sizing

box-sizing:content-box|border-box|inherit
在现在的浏览器中,widht和height默认指的是content的宽和高。设为border-box时,动态改变border和padding宽度,就不会撑破包含块了。

margin赋值

margin: {1,4}|inherit

  • 初始值为0,不使用继承,但可以通过继承关键字继承。可以为负值。
  • margin-top、margin-bottom对不可替换的内联元素无效。
  • margin-width可能为length(如px、em、rem、vm等)、百分比(参照包含块的宽度)、auto。
  • margin的值为一个时,四个边都使用这一个值。为两个值时,第一个指定上下,第二个左右。四个值,上右下左。
  • margin合并:正常流中两个或多个相邻的竖直方向上的margin会被合并。根元素的margin不合并,浮动元素绝对定位的元素不合并。

padding赋值

padding: {1,4}|inherit
与margin不同在于,padding-width不能为auto,padding不会合并。

border赋值

因为对边框的设计的需求,border属性也越来越多。

  • border-width:{1,4} | inherit
  • border-color: |transparent|inherit。在css3中,可以设置多种颜色,一种颜色对应一个像素。
  • border-style: | inherit。如none、hidden、dotted、solid等
  • border-image: 可以使用图片,比较复杂,参考标准。
  • border-radius: none|{1,4}[/{1,4}]?

块级元素与内联级元素

块级元素

p元素、div元素等许多html元素都是块级元素。diaplay设为block、list-item、table可以是一个元素变为块级的。块盒是参与块格式化上下文的盒,每个块级元素生成一个主块级盒,用来包含后代盒和生产的内容。

内联级元素

内联级元素是源文档中那些不会为其内容形成新块的元素,内容分布于多行,如内联图片等。display设为inline、inline-block、inline-table,可以使一个元素变成内联级。

决定元素的布局的属性主要有:position(staic|relative|absoluted|fixed|inherit,不继承)、float(left|right|none|inherit)。

常规流

默认情况下,或者position为static|relative时,元素属于常规流。
常规流中的盒属于一个格式化上下文,可能是块或内联。在一个格式化上下文中,盒在竖直方向上一个接一个放置,同一个块格式化上下文中的相邻块级盒之间的竖直margin会合并。浮动、绝对定位元素、非块盒的块容器(inline-block等)和overflow不为visible的块盒会为他们的内容建立一个新的块格式化上下文。内联格式化上下文中,盒是一个接一个水平放置的。
相对定位:和根据常规流摆放好后,进行相对原位置的移动。如top:1em,则向下移动1em。

浮动

将float设为left或right时,盒子会相对当前位置向左浮动或者向右浮动,直到其外边挨到包含块或者另一个浮动盒的外边。如果没有足够的水平空间来浮动,它会向下移动,直到空间合适。浮动元素会推开内联内容,如果希望浮动元素不浮于某个块级元素之上,该元素可以使用clear(left|right|both)。

绝对定位

当position设为absolute或者fixed时,元素脱离常规流,即不影响后面兄弟的布局,不进行margin合并。absolute是相对包含块的绝对定位,而fixed是相对视口的绝对定位。
同时使用绝对定位来处理网页的导航栏、页脚等。在处理导航栏时,经常希望导航栏一致在视口顶部,而主要内容从导航栏处往下排列。为了使导航栏为绝对定位,并且占据布局空间,可以使用伪元素::before、::after生产与导航栏高度相同的块,这样就更好的实现了既达到绝对定位的效果,又使其应用兄弟节点。

堆叠

可以使用z-index来指定堆叠顺序,其数越大,离用户越近。
绘制顺序(详细)大致为:

  1. 根元素
  2. 块级元素的背景(先颜色后图片)
  3. 具有负z-index的后代形成的堆叠上下文
  4. 流内非定位的块级后代
  5. 所有非定位的浮动后代
  6. 内联元素
  7. 所有定位具有z-index:auto或0的后代
  8. 定位大于等于1的后代形成的堆叠上下文。

CSS伸缩布局与居中

文字居中

垂直居中

可以将margin、padding设置为固定的值,从而达到文字居中的目的。由于长度一般不确定,所以此方法可以实现文字垂直居中。
或设置段落line-height,则文字自动实现相对段落垂直居中。
当一个内联行中元素高度不同时,可以使用vertical-align设置行内元素对齐方式。

水平居中

设置text-align为center可以实现文字水平方向居中

块级元素居中

给所有的包含块以及子块设置margin可以实现水平与垂直居中,但是要求知道这些块的大小,此处省略此方法。

margin设为auto

可以实现垂直排列,水平居中。
如果一个块outer包含一个块inner,在水平方向居中。则可以将inner的margin-left和margin-right设为auto,则可以完成水平居中(此为css2.1标准规定的特性,但不能垂直居中)。如果对一个块中的所有子块应用此方法,所有子块都可以实现水平方向居中,且从上到下排列。

1
2
3
.inner {
margin: 10px auto;
}

转为内联元素

可以实现元素水平排列,且处于水平/垂直居中位置。将子元素转为内联元素后,与上述文字居中表现相同。

1
2
3
4
5
6
7
.outer {
text-align: center;
vertical-align: middle;//指定所有子元素对其方式,不能相对父容器垂直居中
}
.inner {
display: inline-block;
}

转为绝对定位

可以实现绝对的居中。
由于使用绝对定位,所以脱离正常文档流,不受其他元素影响。由于使用绝对定位,所有有一定的副作用。

1
2
3
4
5
6
7
8
9
10
11
.outer {
positon: relative;
}
.inner {
positon: absolute;
margin: auto;
top: 0;
bottom: 0;
left: 0;
right: 0;
}

其他

此外,还可以将display设为table,然后进行相关配置,或者使用百分比、浮动等,虽然都可以实现,但并不好。比较好的方法,还是转为可伸缩盒。

flexbox

flexbox是一个很好的实现布局的盒子模型。此处仍然以outer为父容器,inner为子元素为例。

1
2
3
4
5
6
7
8
9
.outer {
display: flex;
flex-flow: row wrap;
justify-content: center;
align-items: center;
}
.inner {
flex: 1;
}
  • 伸缩容器display:flex|inline-flex。
  • 伸缩流方向flex-direction:row|row-reverse|column|column-reverse
  • 伸缩换行flex-wrap: nowrap|wrap|wrap-reverse
  • flex-flow: <’flex-direction’> || <’flex-wrap’>
  • 主轴对齐justify-content:flex-start|flex-end|center|space-between|space-around
  • 侧轴对齐align-items:flex-start|flex-end|center|baseline|stretch
  • 侧轴对齐align-self:flex-start|flex-end|center|baseline|stretch ——inner子容器
  • 堆栈伸缩行align-content:flex-start|flex-end|center|space-between|space-around|stretch
  • 伸缩性flex:none|[<’flex-grow’><’flex-shrink’>?||<’flex-basic’>] ——inner子容器
  • 显示顺序order: ——inner子容器

资源

测试例子
一个获取居中代码的网站

SASS学习

安装及使用

安装gem

先按照ruby,在安装gem,过程略。

安装sass

1
gem install sass

sass转换为css

编译模式有四种:nested(默认)、expanded、compact、compressed

1
sass input.scss output.css

监视修改watch

1
sass --watch input.scss:output.css

也可以使用gulp、grunt等自动化构建工具

Sass基础

  • 变量:以美元符号$开头,在SCSS中,可以代替值进行加减乘除、也可以代替名值对的名称等。Data类型有:strings、lists、maps、colors。
  • 嵌套:属性可以嵌套,选择器也可以嵌套。在嵌套代码块中,&可以引用父元素,常用于a:hover伪类等。
  • 注释://编译后不保留,/编译为CSS后保留/,/! 重要注释,压缩模式编译也会保留 /
1
2
3
4
5
6
7
8
9
10
11
12
13
//编译后不保留
$color : #333;

/*编译后保留*/
#main {
background-color: $color + #555;
a {
color: $color;
&:hover {
color: $color - #222;
}
}
}

操作符

颜色可以相加、相减,也可以乘以或除以某个数。对于变量,scss在可以进行运算时会进行运算,所以如果将要作为plain CSS的话,使用#{}插入(在拼接选择器或样式名称时,也用#{}插入)。

1
2
3
4
5
6
7
8
9
p {
$font-size: 12px;
$line-height: 30px;
font: #{$font-size}/#{$line-height};
}
//编译为以下代码:
p {
font: 12px/30px;
}

对于string变量,可以使用+进行字符串连接,如sans- + “serif”编译后为sans-serif

混入、继承、引入

合理使用混入和继承可以是SASS代码可重用、可读性以及可维护性更高。混入适合将某一类的特征提取出来,可以考虑将布局样式(如居中)、边框样式(三维边框圆角、阴影)提取出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//可以为scss格式(默认),也可以为css格式
@import "path/foo.scss"

@mixin left {
float: left;
}
div {
@include left;
margin: 10px;
}
//可以添加参数,这是mixin最强大的地方
@mixin sexy-border($color, $width) {
border: {
color: $color;
width: $width;
style: dashed;
}
}
.class1 {
border: 1px solid #ddd;
}
.class2 {
@extend .class1;
font-size: 120%;
}

此外,还有@media、@debug、@warn、@error、@at-root等

条件控制

条件控制有:if @if @for @each @while

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
if(true, 1px, 2px) //1px
if(false, 1px, 2px) //2px

$type: monster;
p {
@if $type == ocean{
color: blue;
} @else if $type == monster {
color: green;
}
}

@for $i from 1 through 3 {
.item-#{$i} { width: 2em * $i; }
}

@each $animal in puma, egret, salamander {
.#{$animal}-icon {
background-image:url('/images/#{$animal}.png');
}
}
@each $animal, $color, $cursor in (puma, black, default),
(sea-slug, blue, pointer),
(egret, white, move) {
.#{$animal}-icon {
background-image: url('/images/#{$animal}.png');
border: 2px solid $color;
cursor: $cursor;
}
}
@each $header, $size in (h1: 2em, h2: 1.5em, h3: 1.2em) {
#{$header} {
font-size: $size;
}
}

$i: 6;
@while $i > 0 {
.item-#{$i} { width: 2em * $i; }
$i: $i - 2;
}

资源

Sass官网文档
SASS用法指南-阮一峰

个人网站搭建系列——选型及基本框架搭建

个人网站搭建初衷

在搭建这个个人网站前,我已经有了一个由hexo+github page搭建的静态博客。但由于那个博客是静态页面,所以功能多有限制。一方面,我想拥有更多的功能,比如可以加载脑图,并可以通过点击脑图中的节点选择相应的文章等功能,从而真正构建更加满足我个人需求的学习中心、实验中心、分享中心。另一方面,又想以个人网站为个人的试验田,积极尝试各种有趣的技术。因此,我认为有必要自己建一个动态的个人网站了。

网站内容

目前来说,个人网站主要分为主页、博客(附带脑图及脑图选择功能)、实验室(个人的一些demo)以及关于该网站的介绍。

关于网站后端的技术选型

后端语言及服务器

鉴于node使用的语言为JavaScript,并且node是一个单线程的基于事件驱动的异步无阻塞I/O的服务器,非常适合处理高并发,高I/O操作,所以就选择了node。还有一个很重要的原因,node很火呀!并且单线程、基于事件驱动、异步I/O等这些概念太有趣了。

node框架

由于node有许多很好用的框架,就省去了很多初级的框架搭建事件,并且可以保证稳定性,所以选择使用node框架,并选择express。

数据库

由于个人网站中,文档性质的东西偏多,使用文档型数据库很好,并且,node对mongoDB数据库的支持很好,所以就选用了mongoDB作为数据库。

前端

编程规范及插件等

编程规范采用了腾讯Alloy团队制定的编程规范

JS中两个值是否相等&类型转换

类型转换

也可以参考官方版类型转换

转为String

  1. undefined null true false 均转换为相应的字符串:“undefined”等
  2. 数值:+0和-0转为“0”;NaN、Infinity、-Infinity均转换为相应的字符串
  3. 对象对有toString()方法,则直接调用。否则,尝试调用valueOf()。否则返回类型错误异常。
    toString()方法:数组返回逗号隔开的数组字符串;函数可以返回函数的字符串;Date、TegExp也可以返回。普通的object({x:1,y:2})返回[object object]。

转为Number

  1. undefined转为NaN,null转为+0
  2. true为1,false为0
  3. “”为0;”1.2”转为1.2;”one”转为NaN;其他的看下面具体规则
  4. 如果为对象。先转换为原始值,再转换为数值。[]转为0;[9]转为0,其他基本都是NaN;

转为Boolean

  1. undefined null “” 0 -0 NaN 转为false
  2. 其他的转为true(包括[]、{})

ES5和ES6中的比较相等的运算符

在ES5中,只有两个运算符:相等运算符(==)和严格相等运算符(===)。在ES6中,则新增了一个中比较相等的方法Object.is()。
相等运算符会自动转换数据类型,而严格相等运算符不会。但是,根据严格相等运算符,NaN不等于NaN,+0等于-0。
ES6的Object.is()提出“Same-value equality”(同值相等)算法,以解决上述问题。其用来比较两个值是否严格相等,与严格比较运算符的行为基本一致。

三个方法的比较及结果

下列的步骤,是有顺序的,如满足1,则不会使用2.

相等比较步骤

  1. 如果两个值类型相同,返回严格相等(===)的结果。
  2. null和undefined返回true
  3. 一个为Number,一个为String,将String转为Number;
  4. 布尔类型转换为Number比较
  5. 一个为String/Number/Boolean,另一个为object,则将object转换,进行比较;
    总结:有Number类型,则将另外一个值转为Number进行比较。Boolean转为Number。一个为object则转换为原始值进行比较。

严格相等比较步骤

  1. 类型不相同,返回false
  2. null等于null,undefined等于undefined
  3. NaN不等于任何数,包括自身;两个数值相同,返回true;+0等于-0;其他false
  4. 为String,完全相等,返回true
  5. true等于true,false等于false;
  6. 相同的Symbol值(Symbol.for(‘f’),Symbol.for(‘f’)),返回true;
  7. 相同的object,则返回true。
  8. 返回false;

Object.is()比较步骤

  1. Type(x)和Type(y)不同,返回false
  2. Type(x)为null或undefined,返回true(null等于null,undefined等于undefined)
  3. Type(x)为数字。NaN等于NaN;+0不等-0;相同数字相等;正/负无穷不等于正/负无穷;
  4. Type(x)为string,两个值为相同的序列单元返回true,否则返回false;
  5. Type(x)为Boolean,true等于true,false等于false;
  6. Type(x)为Symbol,相同的Symbol值,返回true;
  7. x和y是相同的object值,返回true,否则返回false({}不等于{})。

一些特殊值的比较

值1 值2 == === Object.is()
类型不同 转换 false false
null undefined true false false
null null true true true
undefined undefined true true true
NaN NaN false false true
+0 -0 true true false
-Infinity -Infinity true true true
“12345” “12345” true true true
false false true true true
Symbol(‘f’) Symbol.for(‘f’) false false false
Symbol.for(‘f’) Symbol.for(‘f’) true true true
object object(引用相同) true true true
{} {} (引用不同) false false false

参考链接及书籍

ECMAScript2015 官方语法
JavaScript中文版

HTTP协议

HTTP(HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议,是一个客户端和服务器端进行资源请求和应答的标准。

HTTP方法

get 获取资源

GET方法用来请求访问已被URI识别的资源。指定的资源经服务器端解析后返回响应内容。如果请求的资源是文本,那就保持原样返回;如果是像CGI那样的程序,则返回经过执行后的输出结果。

post 传输实体主体

POST方法用来传输实体的主体。虽然用GET方法也可以传输实体的主体,但一般不用GET方法进行传输,而是用POST方法。虽说POST的功能与GET很相似,但POST的主要目的并不是获取相应的主体内容。

head 获得报文首部

HEAD方法与GET方法一样,只是不返回报文主体部分。用于确认URI的有效性及资源更新的日期时间等。

option 询问支持的方法

用来查询这对请求URI指定的资源支持的方法。

GET与POST的区别

参考:GET请求和POST请求的区别 HTTP方法:GET对比POST

  • 在HTTP/1.1中,get被强制服务器支持,即服务器必须实现get方法
  • 浏览器对URL的长度有限制,所以GET请求不能代替POST请求发送大量数据
  • POST请求相对GET请求是相对安全的。因为get请求会将数据添加到url后,普通人都可以看到。而post则需要进行抓包才可以看到。所以说相对安全。
  • 在规范的定义下,GET操作不会修改服务器的数据

HTTP首部字段

HTTP报文主要由报文首部和报文主体构成(中间以空行分界)。请求报文首部由请求行(方法 URI HTTP版本)、请求首部字段、通用首部字段、实体首部字段和其他首部组成。响应报文首部由状态行(HTTP版本 状态码)、响应首部字段、通用首部字段、实体首部字段和其他首部组成。

通用首部字段

  • Cache-Control 控制缓存的行为,更多请查看百度百科
Cache指令 说明
public 所有内容都将被缓存(客户端和代理服务器都可缓存)
private 内容只缓存到私有缓存中(仅客户端可以缓存,代理服务器不可缓存)
no-store 所有内容都不会被缓存到缓存或 Internet 临时文件中
must-revalidation/proxy-revalidation 如果缓存的内容失效,请求必须发送到服务器/代理以进行重新验证
max-age=xxx (xxx is numeric) 缓存的内容将在 xxx 秒后失效, 这个选项只在HTTP 1.1可用, 并如果和Last-Modified一起使用时, 优先级较高
no-cache 必须先与服务器确认返回的响应是否被更改,然后才能使用该响应来满足后续对同一个网址的请求。因此,如果存在合适的验证令牌 (ETag),no-cache会发起往返通信来验证缓存的响应,如果资源未被更改,可以避免下载。
  • Connection:管理持久连接、控制不再转发给代理的首部字段。Keep-Alive:持久连接,HTTP/1.1版本和2.0版本都默认持久连接,在遇到Connection为close时,才会断开连接。
  • Date:表明创建HTTP报文的日期和时间。
  • Pragma:与HTTP/1.0兼容的字段,设为no-cache要求所有的中间服务器不返回缓存的资源。
  • Transfer-Encoding:规定传输报文主体时采用的编码方式。如gzip、chunked等
  • Upgrade:用于检测HTTP协议及其他协议是否可使用更高的版本进行通信。使用方法为:Upgrade:TLS/1.0 Connection:Upgrade

请求首部字段

  • Accept:能够处理的媒体类型及媒体类型的相对优先级,如text/html、text/css、image/png等
  • Accept-Encoding:支持的内容编码及内容编码的优先级,如gzip、compress等
  • Accept-Language:支持的自然语言集及相对优先级,如zh-cn、en-us等
  • Host:虚拟主机运行在同一个IP上时,使用Host加以区分
  • If-Modified-Since:如果在该字段指定的日期后,资源发生了更新,服务器会接收请求
  • If-None-Match:如果没有该Etag相匹配,处理该请求。可在get或head方法中使用它可获取最新的资源。
  • Referer:会告知服务器请求的原始资源的URI,即哪个web页面发起的请求。
  • User-Agent:创建请求的浏览器和用户代理名称等信息

响应首部字段

  • Etag:每个资源的唯一性标识,资源更新,则Etag更新。
  • Location:配合3**重定向响应,提供重定向的URI。
  • Server:服务器上安装的HTTP服务器应用程序的信息。

实体首部字段

  • Allow:服务器支持的所有HTTP方法,使用option方法可返回该字段
  • Content-Encoding:实体的主体部分选用的内容编码方式。
  • Content-Language:如zh-CN
  • Content-Length:实体主体部分的大小(单位为字节)
  • Content-Type:实体主体内对象的媒体类型。如text/html等
  • Expires:资源失效的日期
  • Last-Modified:资源最后修改的时间

其他

  • Set-Cookie:status=enable;expires:Tue,05 Jul 2011 07:26:31 GMT;path=/;domain:.hackr.jp;(NAME=VALUE以及失效期、目录、域名等)Secure表示仅在HTTPS下才发送Cookie,HttpOnly,表示Cookie不能被JavaScript脚本访问。
  • Cookie:发送cookie名称及相应的值
  • X-XSS-Protection:针对跨站脚本攻击(XSS)的一种对策。0:将XSS过滤设置成无效状态;1:有效状态

常用TTTP码

1xx

  • 101 转换协议

    2xx成功

  • 200:服务器已成功处理了请求并提供了请求的网页。
  • 204:服务器成功处理了请求,但没有返回任何内容。

    3xx重定向

  • 301:请求的网页已永久移动到新位置。当URLs发生变化时,使用301代码。搜索引擎索引中保存新的URL。
  • 302:请求的网页临时移动到新位置。搜索引擎索引中保存原来的URL
  • 304:如果网页自请求者上次请求后没有更新,则用304代码告诉搜索引擎机器人,可节省带宽和开销。

    4xx客户端错误

  • 400:服务器不理解请求的语法。
  • 403:服务器拒绝请求。
  • 404:服务器找不到请求的网页。服务器上不存在的网页经常会返回此代码。
  • 410:请求的资源永久删除后,服务器返回此响应。该代码与404(未找到)代码相似,但在资源以前存在而现在不存在的情况下,有时用来替代404页面代码。如果资源已永久删除,应当使用301指定资源的新位置。

    5xx服务器错误

  • 500:服务器遇到错误,无法完成请求。
  • 503:服务器目前无法使用(由于超载或停机维护)。
  • 505:HTTP版本不受支持。

域名、URL、网站名

http://mail.163.com/index.html

  • http:// 这是个协议,即http超文本传输协议,也就是网页在网上传输的协议。
  • mail 这个是服务器名,代表一个邮箱服务器
  • 163.com 这个是域名,是用来定位网站的独一无二的名字
  • mail.163.com 这个是网站名,由服务器名+域名组成
  • index.html 这个是根目录下得默认网页
  • 整体叫做URL,统一资源定位符,定位网上资源。由协议 + 服务器 + 域名 + 网页html组成

HTTP1.1

1997年,HTTP1.1问世。HTTP1.0中出现的最大问题使连接无法复用和队头阻塞问题。针对连接无法复用问题,HTTP1.1通过connection实现长连接,针对队头阻塞问题,HTTP1.1提出管线化(然而存在很多缺陷,所以并没有被广泛使用)。

客户端/服务器端

仅从一条通信路线来说,应用HTTP协议时,必定一端担任客户端角色,另一端担任服务器角色。客户端发送请求,服务器端才能响应,不能实现服务器端推送功能。为了实现服务器端推送功能,就必须使用Comet等解决方法(保留响应直到服务器内容更新)。

不保存状态的协议

HTTP是一种不保存状态的协议,但为了实现期望的保持状态功能,于是引入了Cookie技术,但是该技术在很大程度上使首部膨胀,增加了数据传输压力,进而增加延迟。

采用明文传输

首部采用明文传输,所以首部数据量大,且不安全。为保证安全,要使用HTTPS协议。

仍然存在队头阻塞问题

由于http1.1管线技术的缺陷,使用率较小。为了解决这个问题,主要使用图片合并、文件合并、内容内嵌等技术来减小请求次数,但是会导致文件粒度变大。

SPDY

SPDY针对HTTP1.1的痛点——延迟和安全性,从协议层次进行了技术改革,SPDY位于HTTP之下,TCP和SSL之上,实现了多路复用、请求优先级、header压缩、server推送、server暗示等功能,为了减少使用spdy的协商过程,SPDY规定,协商过程放到SSL协商过程中,从而避免增加延时。

HTTP2.0

由于SPDY的诞生和表现,催生了HTTP2.0的诞生。HTTP2.0最大的特点:不会改动HTTP的语义、方法、状态码、URI 及首部字段等。却能致力于突破上一代标准的性能限制,改进传输性能,实现低延迟和高吞吐量,其核心在于新增的二进制分帧层。

首部压缩

由于cookie和user agent以及其他明文传输的头部,很容易让头部膨胀,增大了不必要的传输,所以HTTP2.0对头部进行了压缩。实现的方法为:通讯双方各自cache一份header fields表,既避免了重复header的传输,又减小了需要传输的数据的大小。

多路复用

在HTTP2.0中,多个请求和响应共用一个tcp通道。一个request对应一个stream并分配一个id,这样一个连接上可以有多个stream,每个stream的frame可以随机的混杂在一起,接收方可以根据stream id将frame再归属到各自不同的request里面。同时,每个stream都可以设置优先级和依赖,以保证优先级高的stream被优先处理。

服务器推送

HTTP/2允许服务端针对客户端一个单独的请求,主动的发送(或推送)一个或者多个相关的响应。服务端推送语义上等同于服务端响应一个请求;然而,这种情况下请求也是由服务端发送的,作为一个PUSH_PROMISE帧。

HTTPS

HTTPS=HTTP+加密+认证+完整性保护,HTTPS是身披SSL外壳的HTTP,只是HTTP通信接口部分用SSL和TLS协议代替而已。
HTTPS采用混合加密机制,即使用公开密匙加密方式安全地交换在稍后的共享密匙加密中要使用的密匙,在确保交换的密匙是安全的前提下,使用共享密匙加密方式进行通信。公开密匙加密使用一对非对称的密匙——私有密匙和公开密匙,常用的公开密匙加密方法有:RSA算法、ElGamal算法等。除了加密外,还需要证明公开密匙正确性的证书,来证明公开密匙本身就是货真价实的公开密匙。
HTTPS通信步骤:

  1. 客户端发送Client Hello报文,报文中包含客户端支持的SSL的指定版本、加密组件等。
  2. 服务器可进行SSL通信时,会以Server Hello报文作为应答(包含SSL版本等),并发送证书以及公开密匙。随后发送Server Hello Done报文。
  3. 客户端验证证书,然后使用公开密匙加密共享密匙(Pre-master secret),并将其包含包Client Key Exchange报文中发送,随后发送Change Cipher Spec报文,提示服务器,此后采用Pre-master secret密匙加密。最后发送Finished报文。
  4. 服务器同样发送Change Cipher Spec报文,并发送Finished报文。
  5. SSL连接建立完成。

相关资源:
HTTP1.0 RFC
HTTP1.1 RFC
HTTP2.0 RFC
HTTP2.0那些事
HTTP2.0 中英文对照
《图解HTTP》 上野宣 著 于均良 译