javascript fundamental concept

世有因果知因求果發表於2015-08-13

http://hzjavaeyer.group.iteye.com/group/wiki?category_id=283

上面這個url非常適合javascript初學者閱讀,感謝原作者的分享

什麼是script?

A script is a series of instructions that a computer can follow to achieve a goal.

A browser may use different parts of the script depending on how the user interacts with the web page.

Scripts can run different sections of the code in response to the situation around them.

類似於菜譜,汽車維修操作說明書。

 

javascript中的型別 

js分為兩類:原始型別和(值引用)和Object型別(引用型別)。在瀏覽器宿主環境下,JS又可以分類為:原生物件,宿主物件,瀏覽器擴充套件物件(比如ActiveXObject).原始型別和物件型別他們的區別在於原始型別是值引用,分配在棧空間中,Object型別則在棧中分配的只是變數本身,它指向一個分配在堆中的記憶體塊。

 

資料型別隱式轉換:

  • 當數字和字串執行元算時," +  " 操作將會把數字隱藏轉換為字串, " - "或者*/操作將會把字串轉換為數字

 

  • 注意看下面的程式碼:3.1415這個數字如果放在()中並且使用“ . ”點操作符時,瀏覽器預設行為是將他轉換為Number物件型別,而該Number物件型別是有一個toFixed()方法的;

(3.1415).toFixed(2)

"3.14"

console.dir(new Number(3.1415))

VM278:2 Number

同樣的

"hello world".split(" ");
["hello", "world"]

上面這段程式碼執行時,瀏覽器將值直接量轉換為String物件型別,故而可以呼叫String的split方法

  • if(表示式)這裡表示式也將隱式轉換為布林值;
  • == 將隱式轉==兩邊的值

顯式型別轉換方法

Number(), String(),Boolean(),parseInt(),parseFloat(), !, !! 

typeof與instanceof的區別

  • typeof operator returns a string indicating the type of the unevaluated operand.

typeof操作符能識別除了Null之外的標準原始型別,不能識別具體的物件型別(除了Function外)typeof(null)返回object

返回以下內容“ undefined, boolean, number, string, symbol, function, object"

typeof operand
  • instanceof operator tests whether an object has in its prototype chain the prototype property of a constructor

instanceof操作符可以識別所有的內建物件和自定義物件,但是不能識別原生初始型別的值

返回true或者false

object instanceof constructor
  •  Object.prototype.toString.call 可以識別除了自定義型別物件外的所有型別:
Object.prototype.toString.call(123);
"[object Number]" 
Object.prototype.toString.call("this is a string");
"[object String]"
  • constructor方法來識別型別

使用.constructor可以識別所有的型別(除了null,undefined外,當然即使是null,undefined,使用.constructor也可以返回他們本身!!)。其原理為:當傳入原始型別時,點操作符使得js自動轉換為對應的物件型別,built-in和自定義物件則會返回物件的建構函式本身。

function getConstructorName(obj){ return obj && obj.constructor && obj.constructor.toString().match(/function\s*([^(]*)/)[1]; }
undefined
getConstructorName(null)
null
getConstructorName(undefined)
undefined
getConstructorName([])
"Array"
getConstructorName({})
"Object"
getConstructorName(123)
"Number"
getConstructorName("string")
"String"

 constructor詳解

 上面通過使用obj.constructor.toString方法能夠識別所有自定義物件的前提是:你須要預先執行下面的一行程式碼constructoFunction.prototype.constructor = constructorFunction;

當設定建構函式的prototype原型物件屬性時,不能僅僅通過一條constructorFunction.prototype = { // 一個literal物件};這種方式來定義產出物件的原型,因為這麼做的話,意味著constructorFunction.prototype.constructor = Object(因為obj.constructor永遠指向建立自己的建構函式本身,而在這裡constructorFunction.prototype物件實際上是由new Object來創造的,所以constructorFunction.prototype.constructor = Object!!)。

解決或避免上面問題的最佳實踐是:每次設定建構函式的prototype屬性時,順帶就執行constructorFunction.prototype.constructor = constructorFunction;

下面來解釋一下相關背景。

1.每一個物件都有一個constructor屬性包括建構函式本身,因為建構函式也是物件;

2.任何物件的constructor始終指向建立該物件的建構函式(只要檢視等效的new XXXX,就知道物件的建構函式)

3.每一個函式都有一個預設的屬性prototype,而這個prototype屬性也是一個物件,他的constructor預設指向這個函式;

4.任何函式的prototype屬性物件的constructor預設就是函式本身(在不顯示設定prototype的情況下)

5.obj.constructor = objConstructor.prototype.constructor這是定律,由於prototype.constructor或顯式或隱式被設定為建構函式本身,當然也可以設定為其他的函式

看看下面的程式碼:

function Person(name) {   
    this.name = name;   
}   
var p = new Person("ZhangSan");   
console.log(p.constructor === Person);  // true   p的建構函式為Person,因為p是通過new Person來建立的
console.log(Person.prototype.constructor === Person); // true   任何函式的prototype屬性物件的constructor預設就是函式本身。
console.log(p.constructor.prototype.constructor === Person); // true

上面是在未設定Person的prototype屬性物件的情況下預設的new Person行為;下面我們增加一行程式碼,即:指定Person建構函式的prototype屬性,增加一個getName普遍方法:

function Person(name) {   
    this.name = name;   
}   
Person.prototype = {  //設定Person的prototype為一個literal物件,供後代繼承
  getName: function() {
    return this.name;
  }
} 
var p = new Person("ZhangSan");   //由於任何物件的constructor指向建立自己的建構函式(new XXXX中的XXXX),實際由construcfunc.prototype.constructor或顯式或預設來指定
console.log(p.constructor === Person); // false //obj.constructor = objConstructor.prototype.constructor這是定律 ,而這裡Person.prototype的建構函式為Object, Object的prototype.constructor預設就指向Object建構函式本身,因此p.constructor === Object console.log(Person.prototype.constructor === Person); // false 實際上Person.prototype.constructor === Object console.log(p.constructor.prototype.constructor === Person); // false 

解決上面的問題,只要在設定Person.prototype屬性物件之後,新增一句: Person.prototype.constructor = Person即可!!

 

function:

function本身是一個object,它可以被賦值給任何變數,也可以有很多自定義的屬性

immediate invoked function expression(IIFE):  function(){}()

method vs function: 一個method是一個物件的一個函式屬性(a method is a function that is a property of an object)例如:

var console={

  log:function(){}

}

console.log(“log是console的method!");

String methods:

下面的方法或者屬性是string物件定義的方法或者屬性!

length: 這個length實際上是一個屬性,而不是一個方法。

indexOf

var line = "HAL: I'm sorry, Dave. I'm afraid I can't do ▶
that";
alert( line.indexOf("I'm") ); // 5
alert( line.indexOf("I'm", 6) ); // 22

slice,substr,substring函式:

var greeting = "Hello, Andrew, what's up?",
name = greeting.slice(7, 13);
alert(name); // Andrew

split:該函式將一個字串使用空格符作為分割依據將每個單詞分開放到一個陣列中

var arr = "apples oranges peaches bananas".split(" ");
console.log(arr); // ["apples", "oranges", "peaches", ▶
"bananas"]

toLowerCase, toUpperCase

Date Methods

Date object有很多實用的方法可供使用

getDate,getDay,getFullYear,getHours,getMilliseconds,getMinutes,getMonth,getSeconds,getTime,getTimezoneOffset

setDate,setFullYear,setMinute,setMonth,setSeconds,setTime,setMilliseconds,setHours,

parse:可以用於講一個字串解析為Date object

alert( Date.parse("June 18, 1970") ); // 14529600000
alert( Date.parse("2010/11/11") ); // 1289451600000
var d = new Date(Date.parse("1995/04/25")); // Tue Apr 25 ▶
1995 00:00:00 GMT-0400 (EST)
alert(d);

Array methods

join:這是和split相反的動作,它將把陣列中的所有元素結合在一起形成一個string

alert( ["cats", "dogs", "hamsters", "fish"].join(' ') );
// "cats dogs hamsters fish"
alert( "this should not have spaces".split(" ").join("_") );
// "this_should_not_have_spaces"

pop/shift:

var arr = ["cats", "dogs", "hamsters", "fish"];
console.log( arr.pop() ); // "fish"
console.log( arr.shift() ); // "cats"
console.log( arr ); // ["dogs", "hamsters"]

push/unshift:push將在陣列的尾部增加一個item,unshift則在陣列的開始增加一個item

var arr = ["Beta", "Gamma"];
arr.push("Delta");
console.log(arr); // ["Beta", "Gamma", "Delta"];
arr.unshift("Alpha");
console.log(arr); // ["Alpha", "Beta", "Gamma", "Delta"]

reverse:

alert( ["Crockford", "Resig", "Zakas"].reverse() ); ▶
// "Zakas, Resig, Crockford"

slice:有時,你希望將你的陣列slice成多個部分,這個slice就提供這個功能。包含兩個引數:index of the starting element and index of the element after the last one you want to slice.

alert( ["a", "b", "c", "d", "e"].slice(2, 4) ); ▶
// ["c", "d"]
alert( ["f", "g", "h", "i", "j"].slice(1) ); ▶
// ["g", "h", "i", "j"]

sort:按照字母排序:

["d", "e", "c", "a", "b"].sort(); // ["a", "b", "c", "d", ▶
"e"]
[0, 5, 10, 15, 20, 25].sort();//[0, 10, 15, 20, 25, 5].

在上面對數字的排序可能並不是你希望得到的效果,一個可行的方案是sort函式傳入一個函式:

var arr = [5, 2, 3, 4, 1,10,20];
arr.sort(function (a, b) {
return a - b;
});
console.log(arr); // [1, 2, 3, 4, 5 ,10 ,20];

Math functions

javascript也提供了一個Math 物件,你雖然不能像Date物件一樣二次建立物件,但是你卻可以直接呼叫Math物件的方法

min,max,random,round,ceil,floor,pow,

alert( Math.min(9, 1, 4, 2) ); // 1
alert( Math.random() ); // 0.5938208589795977 (you'll ▶
probably get something else)
alert( Math.random() * 10 ); // 6.4271276677027345 (again,▶
your mileage may vary)
alert( Math.round(10.4) ); // 10
alert( Math.round(10.5) ); // 11
alert( Math.ceil(10.4) ); // 11
alert( Math.floor(10.5) ); // 10
function getRandomNumberInRange(min, max) {
return Math.floor( Math.random() * (max - min + 1) + ▶
min);
}
alert( getRandomNumberInRange(0, 100) ); // 39; you'll ▶
probably get something different.

This

This是javascript預留的keyword,你可以將this想象成一個你不能控制的動態變數。this變數的值將隨著你在程式碼的哪一個地方而不斷變化其值:

預設情況下,this將指向global物件。this沒有一個合適的名字,但是他卻有一個屬性指向它自己:window.如果你看看下面這個小的程式碼,就有些感覺了:

var my_variable = "value";
function my_function () { return "another_value" }
alert( this.my_variable ); // "value"
alert( this.my_function() ); // "another_value"
var global = {
window : global
. . .
};
this = global;

這意味著你可以訪問你的全域性變數或者函式作為window的屬性,而這比用this來訪問這些全域性變數函式更加普遍。這依然有點搞,因為你可以訪問window物件來使用global物件上的函式或屬性,即使this可以指向其他的物件。甚至,你不用使用window.xx的方式呼叫,除非你有一個local的變數或者函式而覆蓋了全域性的函式或變數!

new keyword

第一種改變this指標的方法是使用new 關鍵字:

你可能知道了javascript物件是什麼(屬性和函式的封裝)。將相關的功能封裝到一個合適的object中並且通過合適的介面來訪問是一個非常非常非常基礎的OOP程式設計模式。Classes就像一個藍圖:他們不是實際的物件,但是他們描述了一旦通過該class建立一個object,那麼這個object應該具備的函式和變數。javascript是物件導向的,但是它卻不用class這個概念。想法,它使用prototypes這個概念。prototypes是一些實際的物件objects,我們可以使用這些物件objects作為鮮活的建立其他物件的藍圖”物件“。

在其他語言中,比如c++,使用一個建構函式來建立新的物件。而在javascript中,建構函式是普通的函式,如果在它前面新增一個new關鍵字則可以建立新的物件。

比如我們要建立truck物件,

function Truck(model) {
this.num_of_tires = 4;
this.kilometers = 0;
this.model = model;
}
var my_truck = new Truck("Hercules");
console.log(my_truck);

上面的程式碼注意兩點:首先,我們定義所有的變數作為this物件的屬性;其次,我們沒有從這個函式中返回任何東西。這兩點是因為我們計劃使用new關鍵字來呼叫這個函式建立物件。注意我們是如何使用new關鍵字的。它做了兩件事:首先,它更改this指標指向一個新的乾淨的物件。很明顯,我們然後可以增加我們定製的屬性在這個函式中。第二件new乾的事情是:它將this返回。現在my_truck變數將指向新的物件,這個my_truck就像我們下面的程式碼一樣的結果:

var my_truck = {
num_of_tires : 4,
kilometers : 0,
model : "Hercules"
};
console.log(my_truck);

這就是使用new的想法:我們獲得一個建立新物件的超級簡單的方法。很明顯,你如果僅僅需要一個或兩個簡單的物件,你不需要這麼做。通常你在需要有很多功能並且希望打包到一個物件中時,你才需要這種new的模式,或者你希望建立一系列類似的物件時,也可以使用new方法來建立物件。

call and apply

第二種更改this指標的方法是call或者apply.在javascript中一切皆為物件---即便function也是物件。既然function是物件,那麼他們也可以有屬性和方法(property,method)。call和apply就是每一個function物件的兩個預設方法。

這兩個方法存在的價值就是在函式中修改this指標

function Truck (model, num_of_tires) {
this.num_of_tires = num_of_tires;
this.kilometers = 0;
this.model = model;
}
var basic_vehicle = { year : 2011 };
Truck.call(basic_vehicle, "Speedio", 4);
console.log(basic_vehicle);

小知識點:primitive vs reference values:你可能想,雖然我們更改一個已知的物件,我們會丟失那些變更,除非我們將他們賦值給一個新的變數。如果變數是一個primitive value,那麼這將是對的。然而,objects(arrays)是referrnce values:values passed by referrnce.一個primitive value(比如一個string或者number)是在計算機記憶體中儲存的,而我們使用一個變數來訪問它。當我們將這個變數傳給一個函式時,我們會copy那個值到一個新的記憶體,而將函式引數指向這個新的copy記憶體,而在函式中對這個copy操作,因此原始的primitive value並不會被變更。(因為記憶體不同),但是當我們使用reference value時則不同:當我們傳遞一個object或者一個array給一個function時,形式引數將指向原始object/array相同的記憶體。這意味著,Truck.call(basic_vehicle)中的this和函式外的basic_object中的this是完全一個basic_object.所以沒有必要做任何assign動作,因為basic_vehicle現在已經有了由Truck賦值的所有屬性。如果我們將Truck.call(basic_vehicle)的返回值付給一個變數,將會發生什麼,它將會是undefined,因為Truck without new將不會返回任何東西!

在這裡,我們通過Truck函式的call方法來呼叫Truck函式。call的第一個引數是我們希望在函式中this所指向的物件。既然我們沒有使用new來呼叫Truck,它將不會建立任何新的物件或者返回this.這也是我們所希望的,因為我們僅僅是在修改已經存在的物件,而不是重新建立一個!

在將成為this所指向的物件引數後(你可以稱為函式的上下文context),我們可以傳入任何需要的引數。這些將作為引數被傳入將被執行的函式。上面的例子中,第二個引數"Speedio"將被傳給Truck函式作為其第一個Parameter。第三個引數6將成為Truck的第二個引數。

和call向對應,還有一種方法叫做apply.兩者的區別是:apply只接受兩個引數。第一個是context object,第二個是將被傳入function的一個引數的陣列。如果你有一個作為陣列的引數,這種方法將非常有用。例如,

function Truck (model, num_of_tires) {
this.num_of_tires = num_of_tires;
this.kilometers = 0;
this.model = model;
}
var basic_vehicle = { year : 2011 },
user_input = "Speedio 18";
Truck.apply(basic_vehicle, user_input.split(" "));
console.log(basic_vehicle);

Inside an Object

還有另外一種方法可以更改this的值。

var waitress = {
name : "Ashley",
greet: function (customer) {
customer = customer || " there!";
return "Hi " + customer + " My name is " +this.name + "; what can I get you?";
}
};
alert( waitress.greet("Joe") ); // Hi Joe My name is ▶
Ashley; what can I get you?

 大家都知道使用new關鍵字來呼叫一個建構函式是一種古老而有用的建立物件的方法,比如下面的建構函式:

function Computer (name, ram_amount, memory_amount) {
this.name = name;
this.RAM = ram_amount; // in GBs
this.space = memory_amount; // in MBs
}

上面是一個普通的建構函式,如果我們使用new Computer("MacBook", 2, 250000)呼叫的話,我們將建立一個Computer物件。注意:Computer並不是所有computer物件的prototype(或者blueprint)Computer僅僅是這些computer物件的建構函式constructor,也就是構造產出物件own Property和own Method!!!!.

如果我們希望給上述computer物件增加一個函式,比如儲存檔案的能力函式將會發生什麼?你可能這麼做:

function Computer (name, ram_amount, memory_amount) {
this.name = name;
this.RAM = ram_amount;
this.space = memory_amount;
this.files = [];
this.store_file = function (filename, filesize) {
this.files.push(filename);
this.space -= filesize;
};
}

上面的方法看似無傷大雅,但是這裡存在一個問題。你不應該在你的constructor function中建立function,因為每次你建立一個新的物件copy時,你將會建立那個function的新的copy,而這將佔用記憶體。關鍵是:沒有必要這樣做,因為我們可以僅僅儲存那個function的一個copy,而該copy將為所有的Computer object服務。這之所以工作,是因為我們在建構函式中使用this來引用了物件本身

我們需要做的是一種方法用於將每一份Computer物件指向一個store_file函式的方法。我們迄今所討論的方法是有效的,因為function是一個method,所以this指向一個物件。我們可以這樣做--並且只需要一個函式--而使用Computer建構函式的prototype屬性:這意味著使用new Computer建立物件時,除了Computer建構函式own property/own method以外,還有一個隱形的__proto__原型鏈生成以便供繼承使用!

Computer.prototype.store_file = function (filename, filesize) {
this.files.push(filename);  //this指的是new Computer的返回物件
this.space -= filesize;
};

記住:function本身也是object哦,所以他們可以有屬性的,在上面這個程式碼片段中,我們訪問了Computer建構函式的prototype屬性,而該屬性的值實際上是一個object,該物件是所有Computer objects用於inherit的祖。這意味著:當你執行: my_computer.store_file("image.jpg",26)時,js引擎發現my_computer本身並沒有一個叫做store_file的method,所以js引擎會繼續檢視my_computer的prototype object(Computer.prototype來指示)(通過my_computer.__proto__來定址),如果在prototype中發現這個方法則呼叫它,如果沒有發現,則繼續向prototype chain中繼續向上搜尋,直到最上層Object.prototype---Object是javascript中的所有物件的父親。

在這個使用new+constructor來建立新的物件的案例中,有兩點需要說明:

1. 建構函式本身建立一個物件(denoted by this inside the function);在這個函式中,我們對該物件的屬性進行賦值操作;

2. 每一個使用指定constructor建立的任何一個物件的prototype或者說parent object都儲存在ConstructorFunction.prototype(注意建構函式本身也是一個物件)裡。所有prototype/parent物件的屬性或者方法都可以從"child"object中來訪問(機制是由js的prototype chain檢索來實現的)

這就是為什麼被稱為"Prototypal Inheritance";每一個從相同的建構函式建立的物件都繼承於相同的prototype。很重要的一點是:如果一個給定的child object有一個它自己的屬性,比如說my_obj.name,那麼prototype不會被檢索該屬性。同時,如果你修改一個child object的屬性或者方法,你只修改了那個物件本身,並不會影響到prototype object。

以下為例:

function Product(name) {
if (name) {
this.name = name;
}
}
Product.prototype.name = "To be announced"; //提供預設的name,如果new Product時傳入name則覆蓋這個原型上的預設值 
Product.prototype.rating = 3;
var ipad = new Product("iPad");
alert( ipad.name ); // "iPad";
alert( ipad.rating ); // 3

// ipad from above
ipad.rating = 4;
ipad.rating; // 4
Product.prototype.rating; // 3

 

這裡我們建立了一個簡單的Product物件,你可以看到name屬性列印的是ipad物件本身修改過後的name,而rating屬性則是來自prototype的繼承(實際上,如果你在child object上修改也會覆蓋掉prototype的屬性)。通過修改rating這個值,我們實際上就是在ipad物件上新增了一個rating屬性,因為物件的原型物件上的屬性是隻讀的!!,既然ipad物件自己有了rating屬性,當我們訪問ipad.rating屬性時,js引擎將不再向上級prototype檢索。然而,即便如此,prototype的rating屬性值也沒有被改變。

原型鏈

this的scope

當一個函式沒有使用" . "來呼叫該函式時,這個函式中的this指標是和這個函式被呼叫前面一行的this是指向同一個物件的

比如:

var joe={
 firstName: 'joe',
 lastName: 'will',
fullName: function() {
  return this.firstName + ' ' + this.lastName;
}
}
var fullName = joe.fullName;
var firstName = 'John';
var lastName = 'papa';
console.log(fullName()); //注意由於fullName()函式被直接呼叫,未用.方式來引用,因此fullName函式中的this和他前面一行指向是相同的。在這裡也就是global的空間,而全域性空間中確實有firstName和lastName,故而輸出” John papa“

另外一個簡單的例子,通過jquery定位到所有的"all" button,點選以後所有的answer div內容將變為button的text:

html:
Mark ALL test: <button class="all">Yes</button><button class="all">No</button>
<div class="answer">?</div>
<div class="answer">?</div>
<div class="answer">?</div>

javascript:
$(function(){
 $('.all').click(function(){
//在這個context時,this是指$('.all')即button
   var that = this;
   $('.answer').each(function(){
//這裡this是指各個有.answer類的單個div $(
this).text($(that).text()); }); }); });

 

 

Object Methods

迄今為止,我們討論除了Objects外的所有type的方法,之所以這麼安排是因為:

1.我希望保持所有object-related資料在一個集中的地方;

2.大多數你可以使用的methods就是你自己建立的那些方法

然而,對於object物件本身也有一些built-in的方法。既然你建立的任何物件都是一個Object(即:從Object.prototype來繼承的),那麼這些方法都是隨時可用的:

hasOwnProperty

function Person(name) {
this.name = name;
}
Person.prototype.legs = 2;
var person = new Person("Joe"), prop;
for (prop in person) {
console.log(prop + ": " + person[prop]);
}
// in console:
// name : Joe
// legs: 2

在這裡,如果你的物件是從有著屬性的prototype繼承過來的,那麼那些屬性本身也會被loop檢索到。這可能會列印出你不希望看到的結果(比如從prototype中繼承出來的leg)。在這種情況下,物件可以使用一個實用的方法hasOwnProperty來決定一個給定的property是否屬於object本身還是它的prototype.

function Person(name) {
this.name = name;
}
Person.prototype.legs = 2;
var person = new Person("Joe"), prop;
for (prop in person) {
if (person.hasOwnProperty(prop)) {
console.log(prop + ": " + person[prop]);
}
}
// console:
// name: Joe

toString

每一個object都有一個toString方法。這個方法在object在任何地方將被列印時呼叫;它的作用是轉換物件為一個字串。不同的built-in types將做不同的事情:strings明顯不會改變;數字將會成為數字的字串;dates將格式化為date strings。但是一般的object將會怎樣呢?

var o = { name : "Andrew" };
alert( o.toString()); // "[object Object]"

上述程式碼實際上毫無用處,只是告訴你它是一個object。如何解決這個問題呢?你可以定義你自己的toString函式來覆蓋prototype中的這個函式

var person = {
name : "Joe",
age : 30,
occupation: "Web Developer",
toString : function () {
return this.name + " | " + this.occupation;
}
};
alert( person.toString() ); // "Joe | Web Developer"

valueOf:

該函式將返回一個primitive value以便代表你的物件。比如:

var account = {
holder : "Andrew",
balance : 100,
valueOf : function () {
return this.balance;
}
};
alert( account + 10 ); // 110

Object.create:creating object from object

 

var Human = {
arms: 2,
legs: 2,
walk: function() { console.log("Walking"); }
}
<< {"arms": 2, "legs": 2, "walk": function ()
➥{ console.log("Walking"); }}
lois = Object.create(Human);
<< {"arms": 2, "legs": 2, "walk": function ()
➥{ console.log("Walking"); }}

 

Closure

closure是javascript最重要的一個功能之一。如果你熟悉其他的程式語言(特別是oop的語言,比如c++),你一定知道private variable(私有變數)這個概念。一個私有變數就是一個僅僅被物件的方法能夠訪問而不能在物件之外直接訪問的變數。Javascript本身並沒有這個功能,但是我們可以使用closure來"make"private variables。記住兩點關於貢獻於closure的javascript特性:

1.everyting is an object, even functions.這意味著我們可以從一個函式返回一個函式

2.Anonymous,self-invoking functions是沒有名字的立即執行而通常只執行一次的函式

現在我們使用這兩個概念來建立一個closure。首先看看通常的javascript程式碼:

var secretNumber = 1024;
function revealSecret(password) {
if (password === "please") {
return secretNumber++;
}
return 0;
}

上面這段程式碼對你來說是很普通的。我們有一個叫做secretNumber的變數和一個如果密碼正確返回加1的函式。在這裡,secretNumber變數本身沒有任何祕密可言,他是一個任何函式都能訪問的全域性變數。為了增加私密性,為什麼我們不將它放到函式內部呢?這樣我們從函式外面就將不能訪問這個變數了!

不過注意了,我們不能那樣做哦,因為如果那樣,每次我們呼叫那個函式,js引擎都將reset setcretNumber變數到1024!怎麼做呢?Closure可以完成上面的場景!

var revealSecret = (function () {
var secretNumber = 1024;
return function (password) {
if (password === "please") {
return secretNumber++;
}
return 0;
};
}());
alert( revealSecret("please") ); // 1024
alert( revealSecret("please") ); // 1025

注意revealSecret本身被賦予一個anonumous self-invoking function的返回值。既然那個匿名函式立即執行,revealSecret就將被賦予該函式的返回值。在這個例子中,它又是一個函式。下面的事實令人印象深刻:內部函式(被返回的函式)本身引用了secretNunber變數從它的"parent"function's scope.即使匿名函式已經返回,inner function依然能夠訪問到這個變數。那就是closure:an inner function having access to the scope of it's parent function, even after the parent function has returned!!!這種方法,secretNUmber僅僅被初始化一次,它可以在每次內部函式呼叫時加1,而任何其他人不能訪問它,因為它被匿名函式的scope所保護。總的來說:closure是暴露從函式外來訪問一個函式的scope的有限控制(closure is the concept of exposing limited control of a function's scope outside the function.)

這種closure在建立模組時也非常有用:

var a_module = (function () {
var private_variable = "some value";
function private_function () {
// some code;
}
return {
public_property : "something"
// etc : " ... "
};
}());

這就是module pattern:在一個anonymous selft-invoking function中,我們建立無法在匿名函式外訪問的變數和方法。然後,我們返回一個物件,這個物件包含public屬性和能夠訪問匿名函式閉包裡面的私有變數或者方法的公共方法。通過這種方式,那些不必暴露的資訊被遮蔽,而我們只需要暴露共有的函式或者變數。

Errors

有兩種錯誤:一種是當你寫程式碼時犯的錯誤,比如語法錯誤或者型別錯誤,另一種是無法預知的錯誤,比如使用者的輸入錯誤或者missing feature in the js engine.雖然js引擎通常默默地處理曉得錯誤,比如忘記了分號,一些大的嚴重錯誤js會丟擲異常。當然,我們也可以主動丟擲我們自己的錯誤異常。

try {
// code that might cause an error here
} catch (e) {
// deal with the error here
}

我們將可能產生錯誤的程式碼封裝在try塊中。如果我們不這樣做,js引擎會處理那個錯誤,這可能意味著停止執行你的程式碼並且可能向使用者顯示錯誤:而這可能並不是我們想要的。如果一個錯誤被丟擲,error object將被傳遞到catch block。在catch程式碼塊中,你可以處理這個錯誤。

在error物件中,不同的web瀏覽器包含不同的屬性。他們都會包含error name和error message.Firefox也會包含檔名和檔案行。Chrome包含一個stack trace.也有一些其他的東西,但是主要的就是name和message.

function make_coffee(requested_size) {
var sizes = ["large", "medium", "small"],
coffee;
if (sizes.indexOf(requested_size) < 0) {
throw new Error("We don't offer that size");
}
coffee = { size: required_size, added: ["sugar", ▶
"sugar", "cream" ] };
return coffee;
}
try {
make_coffee("extra large");
} catch (e) {
console.log(e.message);
}

 Events:

什麼是event?

event,簡單地說,就是在DOM中的元素上發生的事件,比如:

1.the page loaded;2.an element was clicked;3.the mouse moved over an element;4.when something like this happnes, you may often want to do something.比如,當page loaded,執行一段js程式碼,或者當元素被clicked時,執行這個函式。。。

常見的event事件有:click,mouseover,mouseout,keypress,load,submit

正如你所看到的事件可以分為兩類:一類為使用者的行為所導致的事件一類是瀏覽器的行為所導致的事件。上面的簡單列表中:使用者執行click,mouseover,out和keypress事件;瀏覽器fires the load event when the page has finished loading, and the submit event when the user clicks a submit button(so it is almost a user-performed events)

Adding Event handlers:

An event is fired on a given DOM element.這意味著我們可以wireup一個event handler(或者event listener)to a given element.一個event handler是一個當一個給定事件發生時需要執行的函式。比如下面的程式碼:

// <div id="some_button"> Click Me! </div>
var btn = document.getElementById("some_button");
function welcome_user(evt) {
alert("Hello there!");
}
btn.addEventListener("click", welcome_user, false);

在event中有一個概念bubbling and capturing:

<div>
<span> Click Here! </span>
</div>

看上面的程式碼,如果我們點選span,因為span在div元素內,所以當我們點選span時,div元素也將被點選。所以兩個元素div和span豆漿有一個click事件被觸發。如果兩個元素都有eventhandler偵聽,那麼豆漿被呼叫。問題是:誰先被執行?這就是capturing和bubbling的背景了。DOM標準說事件應該首先capture隨後bubble. Capture階段是當事件從最高階別的元素開始向下擴散。在這裡,click handler of div將被先執行。一旦event到了最底部,或者最裡面,event將被bubble。Bubble階段將從最底部不斷向上冒泡。我們再回到addEventListener的第三個引數是用於指定是否在capture phase來處理。由於IE9之前不支援capturing,那麼通常false是安全的(bubbling)。

 

相關文章