JacaScript精粹(蝴蝶書小結)

JavaScript: The Good Parts
JacaScript精粹(小結)

一. Good Parts

JavaScript 是面向瀏覽器的語言,運行的環境是瀏覽器;
操作的對象是HTML DOM(Document object type)的節點.

Good Parts:

函數

基於詞法作用域的頂級對象,第一個成爲主流的lambda語言.披着C外衣的Lisp
詞法作用域的函數中遇到既不是形參也不是函數內部定義的局部變量的變量時,去函數定義時的環境中查詢.
動態域的函數中遇到既不是形參也不是函數內部定義的局部變量的變量時,到函數調用時的環境中查.

弱類型和動態對象

編譯器不能檢測出類型錯誤,但也無須建立複雜的類層次
弱類型語言:可以繞過類型系統
動態類型:在程序運行時會將值分類,每種類型有對應的限制,有衝突時,在運行時報出動態類型錯誤
    靜態類型有自己的類型系統,在運行前的編譯階段(通過解釋器或編譯器)進行校驗

對象字面量表示法 expressive object literal notation

通過列出對象的組成部分,他們就能被創建出來.JSON的靈感

原型繼承

無類別的對象系統, 對象直接從其他對象繼承

壞的想法

全局變量的編程模型

依賴全局變量進行連接.所有編譯單元的所有頂級變量及河道一個公共命名空間-全局對象
全局變量:所有作用域中都可見的變量,這也意味着全局變量可以在任意時間被程序的任意部分改變.而且全局變量與子程序中變量名稱互相沖突會導致程序無法運行.
    定義全局變量的3種方式:
        1, 函數外部的var語句: var foo = value;
        2, web瀏覽器中,全局對象名爲window: window.foo = value;
        3, 隱式的全局變量, 未經聲明的變量: foo = value;

JSLint:編程工具,分析JavaScript並報告包含的缺點

二. Grammer 語法

1,WhiteSpace 空白

用來分隔字符序列
註釋的形式: // , /* */

2,Names 標誌符

以字符開頭,後面加字符/數字/_,不能使用保留字
[letter][letter|digit|_]* - reserves letter

3,Numbers 數字

只有一個單一的數字類型,內部表示爲64位浮點數
沒有整數類型:1和1.0是一致的
NaN是一個數值,表示不能產生正常結果的運算結果,NaN不等於任何值,可以用isNaN檢測
Infinity表示大於1.7976e+308的值
Math是一套javscript作用於數字的方法:Math.floor(8.7)=8

4,Strings 字符串

字符串被單引號或雙引號包圍,可包含0或多個字符.所有字符都是16位的,因爲Unicode是16位的
沒有Char類型,’c’ = “c”
字符串一旦創建就不能改變,但可以通過’+’連接得到一個新的字符串: ‘i’+”s”===’is’

5,Statements 語句

一個編譯單元包含一組可執行的語句,每個script標籤提供一個已編譯且立即執行的編譯單元.
因爲缺少鏈接器,JavaScript將所有代碼拋入一個公共的全局命名空間中
代碼塊:JavaScript中的代碼塊不會創建新的作用域,因此變量應該定義在函數的頂端
var用在函數內部時定義的變量是函數的私有變量,作用域爲函數內部.
語句執行順序,默認是順序的
條件語句: if 和 switch
循環語句: while,for,do
強制跳轉語句: break, return, throw
函數
if()then {} else {}
爲假的值:false,null,undefined,空字符串”,數字0,數字NaN.字符串’false’也被當作真

if (typeof varSample != undefined) then { ... }
switch (){ case: ...; break; ... default: ...; }
while () {}
do {}while()
for(initialization;condition;increment) condition如果被省略則返回真
for(myvar in obj){if (obj.hasOwnProperty(myvar){...})} 枚舉對象的所有屬性名(鍵名)
try{ }catch(){ }
return 使一個函數提前返回,可以指定返回的值.如果沒有指定,返回值是undefined
    不允許在return和表達式之間換行
break 退出循環語句或switch語句.可以指定標籤
continue 退出當前循環語句,進入下一次循環
    cars=["BMW","Volvo","Saab","Ford"];
    list:
    {
        document.write(cars[0] + "<br>");
        document.write(cars[1] + "<br>");
        document.write(cars[2] + "<br>");
        break list;
        document.write(cars[3] + "<br>");
    }

6,Expressions 表達式

包括: 字面量值(字符串或數字),變量,內置的值(true,false,null,undefined,NaN和Infinity)
        new和delete前導的表達式,函數調用,三目運算符
運算符優先級: 操作符在沒有圓括號的情況下決定其執行優先級的屬性
    屬性存取及函數調用 . [] () 
    一元運算符 delete new typeof + - ! 
    乘除,取模,加減 
    不等式運算符,等式運算符
    與,或,三元 && || ?:

7,Literals 字面量

字面量:直接量,用簡單的字面值標記指向數字,字符串,數組,對象,函數等
對象字面量:字面量名不是變量名,所以對象的屬性在編譯時才能知道.屬性的值就是表達式

    var test = 'hello'; 
    test "hello";
    var testarr = ["arr1","arr2"];
    testarr[1] "arr2"
    var testobject = { o1: 'this is o1',fun: function () { return 'return form function'; } };
    testobject.fun  ƒ(){ return 'return form function'; };
    testobject.fun() "return form function"

8, Functions 函數

函數字面量定義了函數值:函數名字,參數列表,函數主體(變量定義,語句)     

三. Objects 對象

JavaScript的簡單類型:數字,字符串,布爾(true/false),null,undefined和對象(數組也是對象).
對象是
1. 可變的鍵控集合
2. 是屬性的容器,每個屬性都擁有名字和值.名字可以是包含空字符串的任意字符串.只可以爲除undefined的任意值
3. 無類別的,對新屬性的名字和值沒有約束.適用於收集和管理數據.對象可以包含其他對象.
4. 原型鏈特性,允許對象繼承另一對象的屬性

1, Object Literals 對象字面量

對象字面量是一種非常方便的創建新對象的表示法,本質上是一對包圍在一對花括號中零或多個鍵/值對.
作用域等同於表達式

var student = {
    "first-name": 'song',
    first_name : 'song2',
    "last-name": "ben",
    "age": 28,
    company:{
        name:'ge',
        strong:'y'
    }
};

2, Research 檢索

檢索不存在的成員元素得到undefined
|| 用來填充默認值
student[‘first-name’];
‘name is ‘||student.company.name;
檢索undefined值導致TyoeError異常,用&&來避免錯誤
student[‘undefined’]//undefined
student[‘undefined’]&& student[‘undefined’].anything//undefined

3, Updates 更新

student[‘first-name’]=’first-name’;
若屬性名不存在則會被擴充到對象中
student[‘undefined’]= ‘nowdefined’;//nowdefined

4, Reference 引用

對象通過引用傳遞,他們永遠不會被拷貝
JavaScript按值傳遞,但當一個變量指向對象時,變量的值是這個指向對象的的地址
改變變量的值不會改變原始的基礎類型或是object的值,只是將變量指向一個新的基礎類型或是object
但是當變量指向對象的屬性,改變變量值,就會改變對象的值

function f(a,b,c) {
    //基礎類型按值傳遞,不會影響原始值,所以x還是4
    a = 3;
    //數組也是object,傳進來的是原始值的地址,所以原始值也改變
    b.push("foo");
    // c指向z, 同數組,c是對象所以會直接改變傳入的實參的值
    c.first = false;
}

var x = 4;
var y = ["eeny","miny","mo"];
var z = {first: true};
f(x,y,z);
console.log(x,y,z.first); // 4,["eeny","miny","mo","foo"],false

var a = ["1","2",{foo:"bar"}];
var b = a[1]; // 按值傳遞, b是"2";
var c = a[2]; // c是{foo:"bar"}
a[1] = "4";   // a is now ["1","4",{foo:"bar"}]; b仍是被聲明時的值 2
a[2] = "5";   // a is now ["1","4","5"];  
console.log(b,c.foo); // "2" "bar"
b=13;
console.log(a); //["1","4","5"]

四. Functions 函數

JavaScript的函數包含一組語句,是基礎模塊單元,用於代碼複用&信息隱藏&組合調用.
函數指定對象的行爲.一般性的,編程就是將需求分解成一組函數與數據結構的技能.

1, Function Objects 函數對象

JavaScript中函數就是對象.對象=名/值對集合+連接到原型對象的隱藏連接
函數對象連接到Function.prototype(該原型本身連接到Object.prototype).
函數對象創建時會設置一個’調用’屬性,調用函數可以理解爲調用此函數的’調用’屬性
函數對象也有一個prototype屬性,其值是擁有constructor屬性的對象,該對象的值就是該函數.不同於Function.prototype
函數是對象,所以也可以被存放在變量,對象和數組中.函數也可以作爲參數,可以返回函數,還可以擁有方法!與衆不同之處是可以被調用

2, FunctionLiteral 函數字面量

組成包括四個部分:保留字 function,函數名 可被省略,參數 逗號分隔,語句 包圍在花括號中 是函數的主題
var add = function (a,b) { return a + b;};
通過函數字面量創建的函數對象包含一個連到外部上下文的連接,這被稱爲閉包

3, Invocation 調用

調用一個函數將暫停當前函數的執行,傳遞控制權和參數給新函數.
實參與形參不一致不會導致運行時錯誤,多的被忽略,少的補爲undefined
每個方法都會收到兩個附加參數:this和arguments.this的值取決於調用的模式,調用模式:方法,函數,構造器和apply調用模式
this被賦值發生在被調用的時刻.不同的調用模式可以用call方法實現

var myObject = {
    value: 0,
    increment: function (inc) {
        this.value += typeof inc === 'number' ? inc : 1;
    }
};
myObject.double = function(){
    var helper = function(){
        console.log(this);// this Point to window
        }
    console.log(this);// this Point to object myObject    
    helper();
}
myObject.double();//myObject  Window 

3.1 The Method Invocation Pattern 方法調用模式

方法:函數被保存爲對象的屬性.當方法被調用時,this被綁定到該對象
公共方法:通過this取得他們所屬對象的上下文的方法

myObject.increment();
document.writeln(myObject.value);    // 1

底層實現: myObject.increment.call(myObject,0);

3.2 The Function Invocation Pattern 函數調用模式

當函數並非對象的屬性時就被當作函數調用(有點廢話..),this被綁定到全局對象(window)
ECMAScript5中新增strict mode, 在這種模式中,爲了儘早的暴露出問題,方便調試.this被綁定爲undefined

var add = function (a,b) { return a + b;};
var sum = add(3,4);// sum的值爲7

底層實現:add.call(window,3,4)
strict mode:add.call(undefined,3,4)
方法調用模式和函數調用模式的區別

function hello(thing) {
console.log(this + " says hello " + thing);
}
person = { name: "Brendan Eich" }
person.hello = hello; 
person.hello("world") // [object Object] says hello world 等價於 person.hello.call(person,“world”)
hello("world") // "[object DOMWindow]world" 等價於 hello.call(window,“world”)

3.3 The Constructor Invocation Pattern

JavaScript是基於原型繼承的語言,同時提供了一套基於類的語言的對象構建語法.
this指向new返回的對象

var Quo = function (string) {
    this.status = string;
}; 
Quo.prototype.get_status = function (  ) {
    return this.status;
};
var myQuo = new Quo("this is new quo"); //new容易漏寫,有更優替換
myQuo.get_status(  );// this is new quo

3.4 The Apply Invocation Pattern

apply和call是JavaScript的內置參數,都是立刻將this綁定到函數,前者參數是數組,後者要一個個的傳遞
apply也是由call底層實現的

apply(this,arguments[]);
call(this,arg1,arg2...);
var person = {  
name: "James Smith",
hello: function(thing,thing2) {
    console.log(this.name + " says hello " + thing + thing2);
}
}
person.hello.call({ name: "Jim Smith" },"world","!"); // output: "Jim Smith says hello world!"
var args = ["world","!"];
person.hello.apply({ name: "Jim Smith" },args); // output: "Jim Smith says hello world!"

相對的bind函數將綁定this到函數和調用函數分離開來,使得函數可以在一個特定的上下文中調用,尤其是事件
bind的apply實現

Function.prototype.bind = function(ctx){
    var fn = this; //fn是綁定的function
    return function(){
        fn.apply(ctx, arguments);
    };
};

bind用於事件中

function MyObject(element) {
    this.elm = element;

    element.addEventListener('click', this.onClick.bind(this), false);
};

//this對象指向的是MyObject的實例

MyObject.prototype.onClick = function(e) { 
    var t=this;  //do something with [t]... 
};

4 Arguments 參數

函數被調用時會附加一個參數arguments,通過它可以訪問函數被調用時傳遞的參數列表,包括多餘的參數.
arguments並不是真正的數組,是類似數組的對象.有length屬性,但沒有所有的數組方法

var sum = function() {
    let i,sum =0;
    for(i=0;i<arguments.length;i++){
    sum += arguments[i];    
}
return sum;
} 
document.writeln(sum(1,23,4,2,3,4,2,3,4543))//4585

5 Return 返回

函數被調用時,從第一個語句開始執行到}結束,將控制權交還給函數的調用部分.
return會立即交還給函數的調用部分
函數總會返回值,默認是undefined
以new方式調用的函數,若返回值不是對象,則返回this(新對象)

6 Exception 異常

throw語句中斷函數的執行,拋出exception對象.該對象包含可識別異常類型的屬性:name和message,及自定義屬性
try代碼塊中拋出異常,則恐是全就跳轉到catch從句

var add = function(a,b){
    if(typeof a != 'number' ||typeof b != 'number')
        throw {
            name : 'TypeError',
            message: 'add needs number'
        }; 
        return a+b;
}

var tryit = function(){
    try{
        add('ss');
    }catch(e){
        document.writeln(e.name + ':' + e.message);
    }
}
tryit();//TypeError:add needs number

7 Augmenting Types給類型增加方法

JavaScript允許給基本類型增加方法.
for in語句在原型時表現很糟糕,可以用hasOwnProperty屬性篩選出繼承而來的屬性
Function.prototype增加方法使得是對所有函數可用.通過給Function.prototype增加method方法,我們就不用鍵入prototype這個屬性名

Function.prototype.method = function (name, func){
    if(!this.prototype[name]){
        this.prototype[name] = func;
    }
}

整數類型的取整函數

Number.method('integer', function (  ) {
    return Math[this < 0 ? 'ceil' : 'floor'](this);
});
document.writeln((-10 / 3).integer(  ));  // −3 

字符串去掉空白

String.method('trim',function(){
    return this.replace(/^\s+|\s+$/g,'');
})

document.writeln(“begin.”+” 3space3 “.trim()+”end”);//begin.3space3end

8 Recursion 遞歸

遞歸函數是直接或間接的調用自身的一種函數.遞歸,將一個問題分解爲一組相似的子問題,每個子問題用一個尋常解去解決.
尾遞歸:如果函數返回自身遞歸調用的結果.那麼調用的過程會被替換爲循環,只是JS不支持尾遞歸優化.
漢諾塔的遞歸解法:理解遞歸兩層之間的交接,以及遞歸終結的條件.
盤分爲最大盤和非最大盤,

var hanoi = function (disc, s, a, d) {
    if (disc > 0) {
        hanoi(disc - 1, s, d, a);//輔助位置作爲目標位
        console.log('move disc' + disc + ' from ' + s + ' to ' + d + "\n");
        hanoi(disc - 1, a, s, d);//輔助位置作爲起始位
    }
}
hanoi(3, 'a1','a2','a3')
move disc1 from a1 to a3
move disc2 from a1 to a2
move disc1 from a3 to a2
move disc3 from a1 to a3
move disc1 from a2 to a1
move disc2 from a2 to a3
move disc1 from a1 to a3

遞歸函數可以非常高效的操作樹形結構.

var walkTheDOM = function walk(node,func){
    func(node);
    node = node.firstChild;
    while(node){
    walk(node,func);
    node = node.nextSibling;    
}
}; 
var getElementsByAttribute = function(att, value){
var results = [];
walkTheDOM(document.body,function(node){
    var actual = node.nodeType === 1 && node.getAttribute(att);
    if(typeof actual === 'string' && (actual === value || typeof value !== 'string')){
        results.push(node);
    }
});
return results;
}

9 Scope 作用域

作用域控制着變量與參數的可見性及生命週期.可以減少名稱衝突,並提供自動內存管理.
JS實際上不支持作用域.
通過var聲明變量,作用域是最近的方法塊內,意味着定義在函數中的參數和變量在函數外部不可見,
但在函數內部任何位置定義的變量在此函數的任何地方都可見.Note:不用var聲明的變量實際是一個全局變量.
ES6中增加let關鍵字,let聲明的變量作用域是最近的封閉塊,具有真正的塊級作用域
let在strict模式(句首加’use strict’;)下重複聲明會報錯

var foo = function(){
    var a =3,b=5;
    var bar = function(){
        var b=7,c=11;
        a += b+c;
        console.log(a+','+b+','+c);//21,7,11
    }; 
console.log(a+','+b);//3,5
bar();
console.log(a+','+b);//21,5
} 

let x = 1;
if (x === 1) {
let x = 2;
console.log(x);// expected output: 2
}
console.log(x);// expected output: 1

10 Closure 閉包

閉包是函數和聲明該函數的詞法環境的組合

1

閉包是一種實現函數作爲參數的方式,是一個表達式,用途有:訪問變量(在變量的作用域內,多在外部函數中聲明),被賦值給一個變量,
作爲函數的實參被傳遞,作爲函數結果被返回
或者說是一個棧幀,在函數開始是被分配到堆,當函數返回後仍然不會被釋放.

function sayHello(name) {
  var text = 'Hello ' + name; // Local variable
  var say = function() { console.log(text); }
  say();
}
sayHello('Joe');//Hello Joe

2

函數作爲引用被返回

function sayHello2(name) {
  var text = 'Hello2 ' + name; // Local variable
  return function() { console.log(text); }
}
sayHello2('Joe')();//Hello2 Joe 等價於 var say2 = sayHello2('Joe');say2();

3

與C指針不同的是,JavaScript中函數引用變量不僅指向函數,還包括隱藏的指針指向閉包,一塊兒分配在堆上的函數所處上下文環境的內存
C等其他語言中在函數返回後,因爲棧幀被銷燬,其局部變量就不再可訪問.相對的,JavaScript中在函數中聲明一個函數,即使調用的函數被返回,外部函數中的局部變量仍舊可用.
論證,sayHello2(‘Joe’)();中的text是局部變量,匿名方法可以訪問text,就是因爲sayHello2()仍舊保存在一個閉包中.
所以神奇的地方在於,JavaScript中函數引用有一個對它的閉包的祕密引用,類似於委託是方法指針加上一個對對象的祕密引用.
本地變量不被複制,它們按引用保存.外部函數退出時,棧幀仍保存在內存中

function sayHello3() {
  // Local variable that ends up within closure
  var num = 42;
  var say = function() { console.log(num); }
  num++;
  return say;
}
var sayNumber = sayHello3();
sayNumber(); // logs 43

4

同一個閉包可以同時被多個函數所訪問,setupSomeGlobals()中的局部變量可以同時被3個方法調用
但是,setupSomeGlobals一旦被重新調用,新的閉包就會被創建,堆上一塊新的內存被分配.

var gLogNumber, gIncreaseNumber, gSetNumber;
function setupSomeGlobals() {
  // Local variable that ends up within closure
  var num = 42;
  // Store some references to functions as global variables
  gLogNumber = function() { console.log(num); }
  gIncreaseNumber = function() { num++; }
  gSetNumber = function(x) { num = x; }
} 
setupSomeGlobals();gIncreaseNumber();
gLogNumber(); //43 
gSetNumber(6);
gLogNumber();//6 
var oldLog = gLogNumber; setupSomeGlobals();
gLogNumber(); //42 
oldLog();//6

5

JavaScript函數中var/let聲明的函數作用域爲整個函數內部,所以,閉包包含外部函數中所有的在其return之前的局部變量.
JavaScript的特性:variable hoisting: 變量提前化,變量會被挪到其作用域(函數,代碼塊…)的最開始位置

function sayAlice() {
    var say = function() { console.log(alice); } //anonymous function declared first
    var alice = 'Hello Alice';     // Local variable that ends up within closure
    return say;
}
sayAlice()();// logs "Hello Alice"

6

返回共享一個閉包的函數數組
buildList返回的是根據list的個數生成的方法數組,共享一個閉包,也包括其局部變量i
當fnlistj調用匿名函數(function(){console.log(item + ‘’ + i +’‘+ list[i])})時,都指向同一閉包
此時var聲明的索引變量i作用域是整個for循環體,因爲for循環已經遍歷完成,匿名函數對應的局部變量都變成了3
而let聲明的變量k的作用域是匿名函數,不同的遍歷階段,匿名函數中的k值是不一致的

//var
function buildList(list){
    var result = [];
    for(var i=0; i<list.length; i++){
        var item = 'item' + i;
        result.push(function(){console.log(item + '_' + i +'_'+ list[i])});
    }
    return result;
}
function testList() {
    var fnlist = buildList([1,2,3]);
    // Using j only to help prevent confusion -- could use i.
    for (var j = 0; j < fnlist.length; j++) {
        fnlist[j]();
    }
}
testList() // 打印3次 item2_3_undefined
//let
function buildList(list){
    var result = [];
    for(let k=0; k<list.length; k++){
        let item = 'item' + k;
        result.push(function(){console.log(item + '_' + k +'_'+ list[k])});
    }
    return result;
}
testList(); //item0_0_1 \n item1_1_2 \n item2_2_3

7

每次調用主函數都建立一個獨立的閉包

function newClosure(someNum,someRef){
    var num = someNum;
    var anArray = [1,2,3];
    var ref = someRef;
    return function(x){
        num += x;
        anArray.push(num);
        console.log('num:'+num+',anArray:'+anArray.toString()+",ref.someVar:"+ref.someVar);
    }
}
obj = {someVar:4}
fn1 = newClosure(4,obj) 
fn2 = newClosure(5,obj) 
fn1(1) // num:5,anArray:1,2,3,5,ref.someVar:4 
fn2(1) // num:6,anArray:1,2,3,6,ref.someVar:4

Note

  1. 在function中使用另一個function,就會使用閉包.但是構造器函數New Function()不會使用閉包
  2. 閉包可以視作一個函數的入口以及與此函數的相關的局部變量(函數退出時的局部變量的副本)的組合
  3. 每次調用有閉包的函數,都會保留一組新的局部變量(如果函數包含一個內部函數,並且內部函數的引用被返回或以某種方式保留)
  4. 因爲隱祕的閉包,兩個函數看起來可能有相同的源代碼,但實際的作用完全不同
  5. 閉包在處理速度和內存消耗方面對腳本有負面影響
  6. 閉包可用來構建JavaScript私有成員

11 Callbacks 回調

回調讓不連續事件的處理變得更容易.異步方法多采用回調函數
僞代碼:用戶交互,向服務器發送請求,最終顯示服務器的響應.同步實現
request = prepare_teh_request();
response = send_request_synchronously(request);
display(response);
異步實現,display作爲回調函數返回
request = prepare_the_request();
send_request_asynchronously(request, function(response){display(response)});//不理解這裏的response是如何得到的

12 Module 模塊

模塊是提供接口卻隱藏狀態與實現的函數或對象.實現:函數+閉包,利用函數作用域和閉包來創建綁定對象與私有成員的關聯.
通過函數產生模塊,幾乎可以替代全局變量的使用.

Function.prototype.method = function (name, func){
    if(!this.prototype[name]){
        this.prototype[name] = func;
    }
}
String.method('deentityify',function(){
    var entity = {quot:'"', lt:'<', gt:'>'};
    return function(){
        //replace() 方法返回一個由替換值替換一些或所有匹配的模式後的新字符串.模式可以是一個字符串或者一個正則表達式, 
        //替換值可以是一個字符串或者一個每次匹配都要調用的函數.
        return this.replace(/&([^&;]+);/g, function(a,b){ //查找&開頭和;結束的字符串
            return typeof entity[b] === 'string' ? entity[b] : a;
        });
    };
}())
'&lt;&quot;&gt;'.deentityify() // <">

//模塊模式的一般形式是定義了私有變量和函數的函數;利用閉包創建可以訪問變量和函數的特權函數;最後返回特權函數或保存到可訪問的地方
//生產序列號的對象

var serial_maker = function(){
    var prefix = '';
    var seq = 0;
    return {
        set_prefix: function(p){ prefix = String(p);},
        set_seq: function(s){seq=s;},
        gensym:function(){ return prefix + seq++;}
    };
};
var seqer = serial_maker();
seqer.set_prefix('Q');seqer.set_seq(1000);
seqer.gensym() //"Q1000"
seqer.gensym() //"Q1001"

13 Cascade 級聯

有些方法沒有返回值(默認undefined),例如,設置或修改對象的某個狀態卻不返回值的方法.
若我們讓這些方法返回this,就可以啓用級聯.級聯可以產生出具備很強表現力的接口.

14 Curry 套用

函數也是值,所以我們可以去操作函數值.套用允許我們將函數與傳遞給它的參數相結合去產生一個新的函數
Function.prototype.method = function(name,func){if(!this.prototype[name]) this.prototype[name]=func;}//給方法類型增加method方法

Function.method('curry', function(){
    var slice = Array.prototype.slice, args = slice.apply(arguments), that = this;//this arguments value is 1
    return function(){return that.apply(null,args.concat(slice.apply(arguments)));};//this arguments value is 5
});  
var add = function (a,b){return a+b} ;
var add2 = add.curry(1);
add2(5);//6

15 Memoization 記憶

函數可以用對象去記住向前操作的結果,從而避免無謂的運算,這種優化被稱爲記憶.Javascript中多用對象和數組實現這種優化

var memoizer = function(memo,fundamental){
    var shell = function(n){
        var result = memo[n];//memo作爲入參,在單次調用時保持一致
        if(typeof result !== 'number'){ //如果memo[n]未被緩存,result = undefined
            result = fundamental(shell,n); memo[n]=result; 
        } 
        return result;
    };
    return shell;
}
var fibonacci = memoizer([0,1],function(shell,n){return shell(n-1)+shell(n-2);});//fibonacci(0) = 0, fibonacci(1) = 1, so memo is [0,1]
fibonacci(4);
var factorial = memoizer([1,1],function(shell,n){return n*shell(n-1);})

五. Inheritance 繼承

繼承提供兩個重要作用,1,他是代碼重用的一種形式;2,包括了一套類型系統的規範.
Javascript是弱類型語言(動態弱類型),不需要類型轉換.對象的起源無關緊要,對對象來說,重要的是他能做什麼.
Javascript是基於原型的語言,對象可直接從其他對象繼承,這提供更爲豐富的代碼重用模式.可以模擬基於類的模式以及其他的模式.

1,Pseudoclassical 僞類

ECMAScript2015(ES6)中引入的JavaScript類實質是基於原型的繼承的語法糖
類實際上是個特殊的函數,包括:類表達式和類聲明.類的首字母大寫

1.1

類聲明:class關鍵字.函數聲明會提升但類聲明不會,所以必須先聲明類,然後才能訪問它

class Rectangle {
constructor(height, width) {
    this.height = height;
    this.width = width;
}
}

類表達式:是定義類的另一種方式,主要包括命名和匿名
/* 匿名類 */

let Rectangle = class {
constructor(height, width) {
    this.height = height;
    this.width = width;
}
};
/* 命名的類 */ 
let Rectangle = class Rectangle {
constructor(height, width) {
    this.height = height;
    this.width = width;
}
};

1.2 類體和方法定義

類聲明和類表達式的主體都執行在嚴格模式下.比如,構造函數,靜態方法,原型方法,getter和setter都在嚴格模式下執行
constructor方法是一個特殊的方法,其用於創建和初始化使用class創建的一個對象.一個類只能擁有一個名爲 “constructor”的特殊方法.
一個構造函數可以使用 super 關鍵字來調用一個父類的構造函數.

class Rectangle {
constructor(height, width) {
    this.height = height;
    this.width = width;
}
    get area(){return this.calcArea()}
    calcArea(){return this.height * this.width}
}
//原型鏈:(10, 10) -→ Rectangle.prototype ----> Object.prototype ----> null
const square = new Rectangle(10, 10);//如果不寫new,就是一個普通函數,返回undefined
console.log(square.area);//100

1.3 屬性私有,共享方法的對象

Class.prototype指向實例化的原型對象,Class.constructor指向Class函數自身
原型對象實例的proto可以查看Class函數

'use strict';
function Cat(name) {
    this.name = name;
}
Cat.prototype.say = function (){
    return 'Hello, '+this.name+'!';
}   
// 測試:
var kitty = new Cat('Kitty');
var doraemon = new Cat('哆啦A夢');
if (kitty && kitty.name === 'Kitty' && kitty.say && typeof kitty.say === 'function' && kitty.say() === 'Hello, Kitty!' && kitty.say === doraemon.say) {
    console.log('測試通過!');
} else {
    console.log('測試失敗!');
}

1.4 原型繼承

藉助中間函數實現原型鏈繼承,並在新的構造函數的原型上定義新方法

function inherits(Child, Parent) {
    var F = function () {};
    F.prototype = Parent.prototype;
    Child.prototype = new F();
    Child.prototype.constructor = Child;
}
function Student(props) {
    this.name = props.name || 'Unnamed';
}
Student.prototype.hello = function () {
    console.log('Hello, ' + this.name + '!');
}
function PrimaryStudent(props) {
    Student.call(this, props);
    this.grade = props.grade || 1;
}
// 實現原型繼承鏈:
inherits(PrimaryStudent, Student);
// 綁定其他方法到PrimaryStudent原型:
PrimaryStudent.prototype.getGrade = function () {
    return this.grade;
};
s.__proto__ === PrimaryStudent.prototype;//true
s.__proto__.__proto__ === Student.prototype;//true
var t = new PrimaryStudent({name:'jack',grade:12})
t.hello()//Hello, jack!
t.getGrade()//12
#### 1.5 class繼承
"use strict";
class Polygon {
constructor(height, width) {
    this.height = height;
    this.width = width;
}
}
class Square extends Polygon {
constructor(sideLength) {
    super(sideLength, sideLength);//Polygon.constructor(height, width)
}
get area() {
    return this.height * this.width;
}
set sideLength(newLength) {
    this.height = newLength;
    this.width = newLength;
}
toString (){
    console.log('this.height:'+this.height+'; this.width:'+this.width);
}
}
var square = new Square(3);
square.sideLength = 5
square.toString()// this.height:5; this.width:5

1.5 靜態方法

static關鍵字定義類的靜態方法,調用靜態方法不需要實例化類,同時類實例不能調用靜態方法

class Point{
    constructor(x,y){this.x=x;this.y=y;}
    static distance(a,b){return Math.hypot(a.x-b.x,a.y-b.y); } 
} 
Point.distance(new Point(1,1),new Point(4,5));

1.6 Object.create

ECMAScript 5 中引入了一個新方法:Object.create().可以調用這個方法來創建一個新對象
新對象的原型就是調用 create 方法時傳入的第一個參數

var a = {a: 1}; 
// a ---> Object.prototype ---> null
a.__proto__ === Object.prototype//true
var b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (繼承而來)
b.__proto__.__proto__=== Object.prototype//true
var c = Object.create(b);
// c ---> b ---> a ---> Object.prototype ---> null
var d = Object.create(null);
// d ---> null
console.log(d.hasOwnProperty); // undefined, 因爲d沒有繼承Object.prototype

2 Object Specifiers 對象說明符

構造器參數很多時,通過指定實參的名稱,可以實現亂序排列
形參新的特性:默認參數,剩餘參數
剩餘參數語法允許我們將一個不定數量的參數表示爲一個數組.
var func = function(a,b=a+1,c=2,…args){
console.log(‘a:’+a+’; b:’+b+’; c:’+c+’; args.length:’+args.length);
}
func(1,…[234,12,34,3]) //a:1; b:234; c:12; args.length:2

3 Prototype 原型

原型模式中會摒棄類的概念,轉而專注於對象,一個對象可以繼承一箇舊對象的屬性.
通過Object.create實現差異化繼承

var Mammal = {
    name:"mammal",
    says: function(){ return this.name + this.saying || '';}
}
var cat = Object.create(Mammal) 
cat.saying = 'meow' 
cat.name = 'cat' 
cat.says() //"catmeow"

4 Functional 函數化

通過模型模式實現屬性私有.感覺有點過時

var mamal = function(spec){
    var that = {};
    that.getNmae = function (){return spec.name;}
    that.says = function (){ return spec.saying || '';}
    return that;
}

var cat = mamal({name:'cat',saying:'miaow'})
cat.says() // "miaow"
var dog = mamal({name:'dog',saying:'wowang'})
dog.getNmae()// "dog"

5 Parts 部件

通過從一套部件中組合出對象,代碼複用的一種方式.
構造能添加簡單事件處理特性到任何對象的函數,它包括on,fire,註冊表對象

var eventutilize = function (that) {
var registry = {};
that.fire = function (event){
    var array,func,handler,i,type = typeof event === 'string'?event:event.type;
    if(registry.hasOwnProperty(type)){
        array = registry[type];
        for(i=0;i<array.length;i++){
            handler = array[i];
            func = handler.method;
            if(typeof func === 'string')
                func = this[func];
            func.apply(this,hanler.parameters || [event]);
        }
    }
    return this;
}
that.on =function(type,method,parameters){
    var handler = {
        method: method,
        parameters:parameters
    }
    if(registry.hasOwnProperty(type)){
        registry[type].push(handler)
    }else{
        registry[type] = [handler];
    }
    return this;
}
return that;

}
eventutilize(new Object())//{fire: ƒ, on: ƒ}

六. Arrays 數組

數組是一段線性分配的內存,通過整數計算偏移去訪問其中的元素.不過JavaScript中沒有數組數據結構,
而是提供一種擁有數組特性的對象.JavaScript中數組的下標是字符串,比真正的數組結構慢,但也不再限制數組元素的數據類型,
同時提供了數組字面量格式.

1 Array Literals 數組字面量

數組字面量是在一對中括號中零個或多個用逗號分隔的值的表達式,屬性名是固定的1,2,3…
繼承自Array.prototype
對象字面量需要指定屬性名,繼承自Object.prototype

numbers=[1,'12','asd',{'a':'asd','b':'bnm'}] 
numbers[4] //undefined
numbers[3] //{a: "asd", b: "bnm"}
objects = {1:'asd','2':'2test','final':[1,2,3]}
objects[2] //"2test"
objects[3] //undefined
objects['final'] //(3) [1, 2, 3]

2 Length 長度

數組都有length屬性,JavaScript中的length沒有上界,將自動增大來容納新元素,不會發生邊界錯誤

numbers=[1,2] 
numbers[numbers.length]=numbers.length ;
numbers.push(numbers.length);//[1, 2, 2, 3]

3 Delete 刪除

delete numbers[2] //刪除元素,但保留下標,相當於摳出元素
numbers //(4) [1, 2, empty, 3]
numbers.splice(2,1)//刪除元素及標號,後面元素的標號重排
numbers //(3) [1, 2, 3]

4 Enumeration 枚舉

數組可以用for in遍歷,但是會亂序輸出,一般用來遍歷object
for (var property1 in numbers) {
console.log(numbers[property1]);
}
for遍歷

for(let i =0;i<numbers.length;i++) console.log(numbers[i]);

ES6引入for of, 類似於foreach

for(let i of numbers) console.log(i);

5 Confusion 混淆

JavaScript本身對數組和對象的區別是混亂的,數組可以看作有內置方法的對象的子集
使用數組還是對象的規則:屬性名是小而連續的整數時,應該使用數組,否則用對象.
區分數組與對象的方法

var isArray = function(value){
    return value && typeof value === 'object' && typeof value.length === 'number' && typeof value.splice === 'function' && 
            !(value.propertyIsEnumerable('length'))
}

6 Methods 方法

Array.prototype中有一套內置的方法,也可以自己擴充.

Array.prototype.method = function(name, func){
    if(!this.prototype[name])
        this.prototype[name]=func;
}
Array.prototype.method('reduce',function(f,value){
    for(let i of this)
        value = f(i,value);
    return value;
})
var add = function(a,b){return a+b;} ;
numbers.reduce(add,0);

7 Dimensions 維度

JavaScript沒有多維數組,但是像c語言一樣,它支持元素爲數組
ƒ (m,n,initial){
    var a=[],matrix=[];
    for(let i=0;i<m;i++){
        for(let j=0;j<n;j++)
            a[j]=initial;

        matrix[i]=a;
        a=[];
    }
    return matrix;
}
var myMatrix = Array.matrix(5,2,9)  
0:(2) [9, 9]
1:(2) [9, 9]
2:(2) [9, 9]
3:(2) [9, 9]
4:(2) [9, 9]

七. Regualr Expression 正則表達式

Javascript的許多特性借鑑自其他語言,語法出自Java,函數借鑑Scheme,原型繼承出自Self,而正則表達式則借鑑自Perl
略過

八. Methods 方法

Array

array.concat(a,b)
array.join(”)
array.pop()
array.push(a)
array.reverse()
array.shift() 移除第一個元素
array.unshift() 添加元素到第一個元素
array.slice(1,2) 淺複製第2,3個元素
array.splice(1,2,’test’) 移出第2,3個元素並替換成test
array.sort((

Number

number.toExponential(20) 指數形式字符串
number.tofixed(20) to string
number.toprecision(20)
number.tostring()

Function

function.apply(( apply調用模式

Object

object.hasOwnProperty(name) 對象是否包含屬性name,原型鏈中的同名屬性不檢查

Regexp

regexp.exec(string( 強大但慢
regexp.test(( 簡單但快

String

string.charat(2(
string.charcodeat(( 整數形式的字符碼
string.concat(( +
string.indexof(‘test’,2(
string.compare()
string.replace() 可以使用正則表達式
string.slice(-3( 淺複製新的字符串
string.split(”,5(

九. Style 代碼風格

學習python

十. Beautiful Features 優美的特性

函數是頭等對象

函數是有詞法作用域的閉包

基於原型繼承的動態對象

對象是無類別的
可以通過普通的賦值給任何對象增加一個新成員元素
一個對象可以下哦那個另一個對象繼承成員元素

對象字面量和數組字面量

字面量是json的靈感之源

Note

保留字策略
塊級作用域
特性有規定成本,設計成本和開發成本,文檔成本.

A Awful Part

1 global variable 全局變量

全局變量就是所有作用域中都可見的變量.因爲全局變量可以被程序的任意位置任意時間改變,降低了程序的可靠性和維護性.
運行獨立子程序變得更難,子程序的變量名可能與全局變量相同,導致衝突.
JavaScript的問題在於不僅允許全局變量,還要求使用它們.Java中的public static成員元素就是全局變量,不能被修改

2 Scope

沒有塊級作用域,代碼塊中的變量在函數的任意位置都可見.
ES6引入了let代替var

3 Semicolon Insertion 句末自動插入分號

會出現return;
return
{
status: true
};
解決
return {
status: true
};

Reserved Words

Unicode

Unicode視一對字符爲一個單一的字符,而JavaScript認爲那是兩個不同的字符

typeof

typeof null //object
檢測null的方式:my_value === null

parseInt

parseInt(‘16’)// 16
parseInt(‘16 asdf’)//16

+

Floating Point

0.1+0.2 reutrn 0.30000000000000004

NaN

NaN是一個特殊的數量值,用來表示不是一個數字
typeof NaN === ‘number’//true

Phony Arrays 僞數組

JavaScript的數組實質是對象,容易使用且不會有越界錯誤,但性能很糟糕

Falsy Values 假值

0,NaN,”, false,null,undefined
Note:undefined,NaN是全局變量,不是常量,我們可以改變它的值

hasOwnProperty

是方法而非運算符,所以可能被其他的函數替換

Object

JavaScript中的對象永遠不會有真的空對象,因爲可以從原型鏈中取得成員元素

Bad Parts

==

== 會先進行類型轉換後對比值
=== 類型一致且值相同

eval

eval((傳遞一個字符串給JavaScript編譯器並執行結果.使性能顯著降低.

continue

continue跳到循環的頂部,代碼移除continue語句後,性能會改善

switch的貫穿

case條件貫穿很難發現錯誤

++ –

容易造成溢出錯誤

位運算

JavaScript中只有雙精度的浮點數,位運算現將數字運算數轉換成整數,然後運算,最後轉換回去

new

JavaScript的new運算符創建一個繼承其運算數的原詞那個的新對象,然後調用此運算數,再將新創建的對象綁定給this.
運算數(構造函數(可以在返回給請求者前去自定義新創建的對象

JSLint

JSLint是JavaScript的輔助程序,通過掃描文件來查找問題

Json

grammer

類型:對象,數組,字符串,數字,bool和null
空白可被插到任何值的前後,也可省略
對象是容納’鍵/值’對的無序集合,值可以爲任何類型,可以無限層的嵌套
數組是一個值的有序序列
字符串要被包圍在雙引號之間.\用於轉義
數字的首字符不能爲0,區別於其他語言的八進制

json的解析器

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章