類型:javascript
Undefinedjava
Nullajax
Boolean數組
String瀏覽器
Number緩存
Object性能優化
原始類型(值類型):undefined, null, true, "...", 123網絡
對象類型(引用類型):var obj = {}; var arr = []; var date = new Date();閉包
JS的三大對象類型:app
原生對象:
JS語言規範(ECMA規範)所定義的一系列對象。
可分爲兩部分:
構造函數:用開發者定義的內置構造函數來定義一系列對象
由Boolean/ String/ Number/ Object/ Function/ Array/ Date/ RegExp/ Error組成
對象:Math, JSON, 全局對象,arguments
宿主對象:
瀏覽器運行環境提供的一系列對象,包括window, document, history, navigator等
這些對象提供了諸如dom操做等的API,開發者可操做網絡節點等。(在DOM課程詳解)
瀏覽器擴展對象:各瀏覽器廠商爲本身的瀏覽器所擴展的瀏覽器對象
早期--爲不一樣瀏覽器開發不一樣頁面--兼容性
規範的發展--瀏覽器擴展對象慢慢被棄用--瞭解便可
原始類型和對象類型的區別:
問:如何複製一個對象?如何克隆出一個獨立但屬性、方法徹底同樣的對象
https://www.zhihu.com/question/23031215
隱式類型轉換:(弱類型語言)
何時發生隱式類型轉換呢:
1. 數字運算符
i.e.
10 + <input type="text" id="num"/> <input type="button" value="等於" id="btn"/> <span id="ret"></span> <script type="text/javascript"> var btn = document.getElementById("btn"); var ret = document.getElementById("ret"); var num = document.getElementById("num"); btn.addEventListener('click', function() { ret.innerText = 10 + num.value; }) </script>
此時會將10隱式轉換成String後進行+的字符串鏈接操做 // 1010
如果ret.innerText = 10 - num.value; // num.value字符串會被隱式轉換成數字後進行算術運算
總結:除了+運算外,當一方是數字、另外一方爲字符串時,會被隱式轉換爲數字進行計算
當爲+運算時,會被隱式轉換爲字符串進行拼接
2. . 號
將直接量隱式轉換成對象後便可調用該對象的方法。
i.e.
(3.1415).toFixed(2); // "3.14"
console.dir(3.1415); // undefined
系統在進行 . 操做時,進行了隱式類型轉換成對象類型(該例爲一個Number對象)。
"hello world".split(" "); // 將"hello world"直接量隱式轉換成了String對象。
3. if語句
條件語句的條件會被隱式轉換成boolean類型的值
4. ==
隱式類型轉換結果:
顯式類型轉換
以上例爲例子:ret.innerText = 10 + Number(num.value); // 20
Number(), String(), Boolean()
parseInt(), parseFloat() // 取數值
!, !! // 取布爾值
類型識別:
何時須要使用到類型識別呢?
i.e. function toDate(param) {
// 輸入格式: '2015-08-05'/ 1438744815232/ {y:2015, m:8, d:5}/ [2015,8,5]
// 返回格式:Date
function toDate(param) { if (typeof(param) == 'string' || typeof(param) == 'number') { return new Date(param); } if (param instanceof Array) { var date = new Date(0); date.setYear(param[0]); ... return date; } if (typeof(param) == 'object') { var date = new Date(0); date.setYear(param.y); ... return date; } return -1; }
類型識別的方法:
typeof(...) / typeof ...
typeof "jerry"; // "string"
typeof 12; // "number"
typeof true; // "boolean"
typeof undefined; // "undefined"
typeof null; // "object" X
typeof {name:"jerry"}; // "object"
typeof function() {}; // "function"
typeof []; // "object"
typeof new Date; // "object"
typeof /\d/; // "object"
function Person() {}; typeof new Person; // "object"
總結:typeof能夠識別標準類型(Null除外);
可是不能準確識別具體的對象類型(function能夠)
instanceof
[] instanceof Array; // true
/\d/ instanceof RegExp; // true
1 instanceof Number; // false
"jerry" instanceof String; // false
function Point(x,y) { ... }
function Circle(x, y, r) { ... }
var c = new Circle(1,1,2);
c instanceof Circle // ture
c instanceof Point; // true
總結:能夠判別內置對象類型,可是不能判別原始類型;
能夠判別自定義對象類型(包括父類子類)
--> 能夠判別全部的對象類型
Object.prototype.toString.call
Object.prototype.toString.call(123); // "[object Number]"
Object.prototype.toString.call("123"); // "[object String]"
Object.prototype.toString.call(function() {}); // "[object Function]"
function Point(x,y){ ... }; Object.prototype.toString.call(new Point(1,2)); // "[object Object]"
總結:能夠識別標準類型,能夠識別內置對象類型
可是不能識別自定義對象類型
constructor
constructor爲構造對象的構造函數自己
當建立一個對象後,會發現對象有一個屬性稱爲constructor
"jerry".constructor === String; // true (.運算符會隱式轉換成對象類型)
(1).constructor === Number; // true
({}).constructor === Object; // true
new Date().constructor === Date; // true
[].constructor === Array;
function Person(name) {...}
new Person("jerry").constructor === Person; // true
總結:能夠判別標準類型(Undefined/Null除外,由於這兩個類型沒有構造函數)
能夠判別內置對象類型
能夠判別自定義對象類型
--> 利用constructor本身寫類型判斷函數:
function getConstructorName(obj) { return (obj===undefined||obj===null)?obj:(obj.constructor&&obj.constructor.toString().match(/function\s*([^(]*)/)[1]); }
getConstructorName(undefined); // undefined
getConstructorName(null); // null
getConstructorName(new Date()); // "Date"
函數定義的方法:
1. 函數聲明:function name() {}
2. 函數表達式:var name = function() {};
3. 函數實例化(少用不推薦):var name = new Function("..", "..", "return ..");
三種函數定義方法的區別:
定義與調用的順序:
用聲明方式定義的函數,能夠在函數被聲明以前就調用。
由於JS執行過程:JS雖然是腳本語言,但執行順序不是簡單的逐行執行:
1. 預解析:變量聲明、函數定義;2. 單步執行JS代碼
函數表達式和函數實例化方式定義的函數,不能在定義前被調用。
重複定義:
函數聲明:最後聲明的爲有效聲明。
函數表達式和函數實例化:按定義和調用順序執行。
若:同一個函數被函數聲明和其餘方法均定義:
其餘方法的定義會覆蓋函數聲明方式的定義。
總結:
函數聲明的特色:函數定義會被前置(預解析);重複定義時以最後一次定義爲有效定義。
函數實例化的特色:(因爲做用域的關係)除了函數內部做用域外,不會逐級向上而是直接到全局做用域。
--該函數只能訪問本地做用域和全局做用域。
var person = {name:"aaa", age:50}; (function() { var person = {name:"aaa", age:30}; var func = new Function("console.log(person.age);"); func(); }) ();
此時返回的爲age=50; 而不是30.
var func = new Function("var person = {name:"aaa", age:10}; console.log(person.age););})();
則會返回age=10,由於是函數內做用域
函數調用的方法:
1. 函數調用模式:func_name(param);
2. 方法調用模式:
var var_name = {
...,
func_name: function(param) { ... }
}
var_name.func_name(args);
3. 構造函數調用模式:new Function("");
4. apply調用模式:
JS中的函數爲對象,任何函數中均有apply()方法。
Function.prototype.apply的使用:
function Point(x, y) { this.x = x; this.y = y; } Point.prototype.move = function(x, y) { this.x += x; this.y += y; } var p = new Point(0, 0); p.move(2, 2); // 方法調用模式 var circle = {x:1, y:1, r:1}; // 直接定義一個對象 // 如何實現將circle對象進行move()操做呢 p.move.apply(circle, [2, 1]); // 將p的move的方法借用給circle使用
函數調用模式的區別-this:
在函數調用的時候,會自動添加this和arguments這兩個臨時變量
this的指向區別:
函數調用模式:this指向全局對象Window(內部嵌套的函數中的this也指向Window)
方法調用模式:this指向該對象(調用者)
構造函數調用模式:this指向被構造的對象
apply (call)調用模式:將this的指向從原對象變爲函數中的第一個參數對象(上例中爲circle對象)
Arguments:
是相似數組的對象(Array-like)
arguments[index]
arguments.length
函數傳參:
原始類型變量按值傳遞 - call by value
對象類型變量按引用傳遞 - call by reference???
看起來是的,可是:其實是按共享傳遞 - call by sharing
傳遞時,獲取了對象地址的副本,所以指向的是同一個對象,而當在函數內把該變量指向的地址改變了,也並不影響原來的對象。
var count = {a:1, b:1}; var addOne = function(obj) { obj = {a:2, b:2}; return obj; } var ret = addOne(count); console.log(ret); // Object {a:2, b:2} console.log(count); // Object {a:1, b:1}
閉包:Closure
函數內部定義的子函數,用到了父函數的變量造成的這一個特定做用域
i.e.
(function() { var a = 1; (function() { console.log(a); debugger; })() })();
debugger時,在Scope中:
Local: this: Window
Closure: a: 1
有哪些功能:
1. 保存函數的執行狀態
/* 將字符串中的一些特定字符按順序用數組中的元素替換,如 var arr = ['c', 'f', 'h', 'o']; var str = ‘ab4de8g4ijklmn7'; 替換後爲 'abcdefghijklmno' */ // 思路:讓函數記住自身被調用的次數 var func = (function(){ var count = 0; return function(){ return arr[count++]; } })(); str = str.replace(/\d/g, func);
2. 封裝
不讓對象使用者直接access/modify某些屬性 -- 相似於private
var Car = function(type) { var status = "stop", return { type: type, start: function() { status = "driving"; }, stop: function() { status = "stop"; }, getStatus: function() { console.log(status); } } } var audi = new Car("audi");
此時在外部訪問audi,則不能直接訪問status的值。
3. 性能優化
(減小函數定義時間和內存消耗)
// 不使用閉包 function sum(i, j) { var add = function(i, j) { return i+j; } } var startTime = new Date(); for(var i = 0; i < 1000000; i++) { sum(1,1); } var endTime = new Date(); console.log(endTime - startTime); // 195 milliseconds
每次調用sum()時都須要在函數內部定義一個add函數
// 使用閉包 var sum = (function() { var add = function(i, j) { return i+j; } return function(i,j) { add(i,j); } })() var startTime = new Date(); for(var i = 0; i < 1000000; i++) { sum(1,1); } var endTime = new Date(); console.log(endTime - startTime); // 17 milliseconds
add()爲sum的閉包做用域裏的函數,不須要每次都定義,節約了時間
-- 能夠將不須要保存狀態的頻繁調用的函數放入閉包做用域內以優化性能(基於js執行性能考慮,被頻繁調用的函數內部定義和調用的幫助函數,若是不須要保存狀態,應該將這些幫助函數保存到閉包做用域。)
First-class function:函數可被當作普通變量使用(好比可被做爲函數的參數或函數的返回值)
功能:
將函數做爲參數:好比.forEach(...)/ .replace(...)/ 異步回調函數 等
將函數做爲返回值:Function.prototype.bind (與apply相似)
上例中的Point和circle例子:p.move.apply(circle, [2, 1]);
可以使用bind:var circlemove = p.move.bind(circle, 2, 1); 返回的是函數的引用
區別:代碼執行完circle不會當即移動,需調用circlemove();來完成移動
NB: 能夠綁定爲var circlemove = p.move.bind(circle, 1); 調用時circlemove(1);
或綁定爲var circlemove = p.move.bind(circle); 調用時circlemove(2, 1);
curry:函數克里化:嚴格意義:將接受多個參數的函數轉化爲一個接受單一參數並返回一個接受餘下參數並返回結果的新函數的函數的技術
var sum = function(a, b, c) { return a+b+c; } --> var sum_curry = function(a) { return function(b, c) { return a+b+c; } }
更泛化的定義:給函數分步傳遞參數, 每次函數接受部分參數後應用這些參數,並返回一個函數接受剩下的參數,這中間可嵌套多層這樣的接受部分參數的函數,直至返回最後結果。概括爲逐步傳參,逐步縮小函數的適用範圍,逐步求解的過程。
// currying實現將一個函數轉變爲柯里化函數 var currying = function (fn) { var _args = []; return function () { if (arguments.length === 0) { // 實現最終的計算 return fn.apply(this, _args); } // 這裏只是簡單的將參數緩存起來(用於解釋柯里化概念,並不是實際應用場景) Array.prototype.push.apply(_args, [].slice.call(arguments)); return arguments.callee; } }; // sum函數接受任意參數,並返回求和結果 var sum=function () { var total = 0; for (var i = 0, c; c = arguments[i++];) { total += c; } return total; }; // 或得一個泛化柯里化的sum函數 var sum_curry = currying(sum); sum_curry(1)(2,3); sum_curry(4); console.log(sum_curry());
從更上層的角度去理解,柯里化容許和鼓勵你將一個複雜過程分割成一個個更小的更容易分析的過程(這些小的邏輯單元將更容易被理解和測試),最後這樣一個難於理解複雜的過程將變成一個個小的邏輯簡單的過程的組合。
上面兩個例子很好的解釋了什麼是函數柯里化,可是何時用?任何能簡化邏輯實現、提升可讀性地方都鼓勵使用
i.e.
// refresh函數實現經過ajax請求更新頁面上的相關模塊的數據。 function refresh(url, callback){ // ajax_get實現一個ajax get請求,請求成功後回調callback函數(這裏不提供ajax_get實現,有興趣的同窗能夠參考前面課程中有提到過相似的實現)。 ajax_get(url, callback); } function update(data){ // 更新的邏輯所有在這裏處理 } refresh("xxx?target=news", update); // update函數是一個柯里化後函數,第一級函數根據傳入參數將須要更新的模塊分拆出來。 function update(target){ var _elm = document.getElementById(target); // 這裏實現模塊分拆,代碼僅是舉例 return function(data){ // 返回一個用請求結果更新頁面顯示的函數 _elm.innerHTML = data; // 這裏實現用ajax請求返回結果更新頁面顯示過程,代碼僅是舉例 } } // 更新頁面能夠寫成這樣 refresh("xxx?target=news", update("news")); refresh("xxx?target=pictures", update("pictures")); // 繼續,若是新聞模塊須要繼續拆分紅「社會」新聞,「娛樂」新聞,那咱們柯里化的update函數該怎麼寫呢?能夠這樣寫: function update(target){ var _elm = document.getElementById(target); // 這裏實現第一級模塊分拆,代碼僅是舉例 return function(type){ // 返回一個接受其他參數的函數 var _elm = document.getElementById(item); // 這裏實現第二級模塊分拆,代碼僅是舉例 return function(data){ // 返回一個接受其他參數並最終更新頁面顯示的函數 _elm.data = data; // 這裏實現用ajax請求返回結果更新頁面顯示過程,代碼僅是舉例 } } } // 更新頁面就能夠寫成這樣 refresh("action.do?target=news&type=society", update("news")("society")); refresh("action.do?target=news&type=entertainment", update("news")("entertainment"));
思考題1:函數聲明和函數表達式定義同一個函數時,執行的是哪一個?
// 如下代碼執行時,三次打印分別輸出什麼?爲何? function add1(i){ console.log("函數聲明:"+(i+1)); } add1(1); var add1 = function(i){ console.log("函數表達式:"+(i+10)); } add1(1); function add1(i) { console.log("函數聲明:"+(i+100)); } add1(1);
101,11,11
思考題2:對象方法中定義的子函數,子函數執行時this指向哪裏?
如下代碼中打印的this是個什麼對象?
這段代碼可否實現使myNumber.value加1的功能?
在不放棄helper函數的前提下,有哪些修改方法能夠實現正確的功能?
var myNumber = { value: 1, add: function(i){ var helper = function(i){ console.log(this); this.value += i; } helper(i); } } myNumber.add(1);
1. this爲helper對象。2. 不能。
3.
//使用閉包 var myNumber={ value:1, add:function(i){ var that=this, helper=function(i){ console.log(that); that.value+=i; } helper(i); } } myNumber.add(1); //使用apply或call var myNumber={ value:1, add:function(i){ var helper=function(i){ console.log(this); this.value+=i; }, helper.apply(myNumber,[i]); } } myNumber.add(1); //使用方法調用 var myNumber={ value:1, helper:function(i){ console.log(this); this.value+=i; }, add:function(i){ this.helper(i); } } myNumber.add(1);
函數回顧:
函數定義3種方法--區別
函數調用4種模式--本地做用域,this, arguments
閉包和閉包的三個主要功能(保存函數狀態、封裝、性能優化)
First-class function(函數可做爲參數或返回值)
函數的克里化實現 (經過first-class function)
原型是什麼:
類是具體事物的抽象--類構造對象:抽象->具體
原型是具體的,用一個現成的對象爲原型,去構造新的對象:具體->具體
設置對象的原型:
1. Object.create(proto [, propertiesObject])
傳入一個原型對象proto(propertiesObject爲新對象的屬性定義),返回一個新對象
i.e.
// 定義原型對象 var landRover = { name: 'landRover', start: function() { console.log(this.logo + "start"); }; run: function() { console.log(this.logo + "run"); }; stop: function() { console.log(this.logo + "stop"); }; } // 使用原型建立新的對象 var landWind = Object.create(landRover); landWind.logo = 'landWind'; var landCruiser = Object.create(landRover); landCruiser.logo = 'landCruiser'; // 啓動 landWind.start();
建立新對象時,landWind/landCruiser中的_proto_指針指向landRover對象(_proto_屬性指向原型,不能被直接修改)
新對象能夠共享原型的屬性和方法。
2. 構造函數:使用prototype設置原型
i.e.
// Car構造函數 function Car(logo) { this.logo = logo || 'unknown name'; } // 設置Car的prototype屬性 Car.prototype = { start: function() { console.log(this.logo + "start"); }; run: function() { console.log(this.logo + "run"); }; stop: function() { console.log(this.logo + "stop"); }; } // 使用原型建立新的對象 var landWind = new Car('landWind'); var landCruiser = new Car('landCruiser'); // 啓動 landWind.start();
建立新對象時(landWind),設置對象的原型,_proto_爲構造函數的prototype屬性,
將新對象(landWind)做爲this去執行構造函數(Car),Car.apply(landRover, arguments);
原型鏈:
i.e.
// 在以前Car的構造函數和Car的prototype屬性的基礎上 // LandRover的構造函數 function Landrover(serialno) { this.serialNumber = serialno; } // 設置LandRover的prototype屬性 LandRover.prototype = new Car('landRover'); // 建立LandRover對象 var landRover1 = new LandRover(10000); var landRover2 = new LandRover(10001); console.log(landRover1.serialNumber);
1. 定義Car和其prototype
2. 定義LandRover並設置其prototype=new Car('landRover'); 當經過new Car()建立對象時,會有原型指針指向Car.prototype
3. 使用構造函數LandRover建立對象landRover1,使用new LandRover()時,會有原型指針指向LandRover.prototype
4. 事實上,Car.prototype (是一個普通的對象)是經過new Object()建立出來的,會有原型指針指向Object.prototype
5. 從landRover1開始landRover1._proto_->new Car('landRover')._proto_->Car.prototype._proto_->Object.prototype._proto_:原型鏈
另外一方面,(構造)函數自己也是對象,則:
landRover/Car中的_proto_都會指向Function.prototype
而Function.prototype能夠經過new Object建立,因而一樣的,Function.prototype._proto_->Object.prototype
對象的訪問、修改、刪除都跟原型鏈有關
訪問對象指針時首先會在對象自己查找,若沒有,會隨着原型鏈往上查找。
i.e. console.log(landRover1.serialno); // 在對象landRover1中直接查找到
console.log(landRover1.toString()); // 隨着原型鏈往上一個一個查找,直到Object.prototype中查找到toString()方法
修改和刪除只能做用於對象自身的屬性(若屬性在原型,則會在自身建立該屬性並賦值,不會改變原型)
hasOwnProperty():
每個對象都擁有hasOwnProperty()(來自Object.prototype)
判斷傳入的屬性是否是對象自身的屬性
i.e. landRover1.hasOwnProperty('serialno'); // true
landRover1.hasOwnProperty('logo'); // false
做業:
編碼實現下面刪除數組中重複元素的功能
[2,4,2,3,4].deleteRepeat() 返回:[2,4,3]
靜態做用域:
又稱爲詞法做用域
由程序定義的位置決定,和代碼執行順序無關
var x = 10; function foo() { alert(x); } function bar() { var x = 20; foo(); } bar();
該例中,如果靜態做用域(只跟程序定義的位置有關):
1. 全局做用域:x=10; foo=<function>; bar=<function>
2. foo和bar在全局做用域中,bar的做用域:x=20;
3. 即便foo函數內沒有變量x,可是foo函數會在外面(即全局做用域)找到x=10;
動態做用域:
在程序運行時刻決定,使用動態棧(壓棧、從棧頂取出)
上例中,如果動態做用域:
1. x=10壓入棧,緊接着定義foo:<function>壓入棧,最後定義bar:<function>壓入棧(此時x=10在最底端)
2. 以後執行bar函數,在bar函數內定義了x=20,將x=20壓入棧(此時x=20在最頂端)
3. 緊接着調用了foo函數,foo函數中alert(x); 在棧中查找x的值,離棧頂最近的即爲x=20
JS變量做用域:
使用靜態做用域
沒有塊級做用域(好比if/while/for等語句的大括號語句塊)
一共有兩種做用域:全局做用域、函數做用域
ES5中使用詞法環境來管理靜態做用域
詞法環境:描述環境的對象
包含兩部分:
1. 環境記錄:用來記錄環境裏面定義的形參、函數聲明以及變量等。
2. 對外部詞法環境的引用(outer)
var x = 10; function foo(y) { var z = 30; function bar(q) { return x + y + z + q; } return bar; } var bar = foo(20); bar(40);
最外層建立了全局環境(全局環境也有一個outer引用,值爲null)
以後的foo函數建立了foo環境(因爲foo是在全局環境中定義的,因此foo函數有一個outer引用指向全局環境)
以後的bar函數建立了bar環境,有一個outer引用指向foo環境
環境記錄初始化:(即聲明提早)
在每一塊代碼執行前,會初始化改代碼的環境
上例中,
先建立全局環境, outer:null,
全局環境的環境記錄:
1. 函數聲明(沒有形參,跳過第一步形參):foo: <function>
2. 變量:x: undefined; bar: undefined
建立好環境記錄後,開始執行全局環境中的代碼:
x=10;
bar=foo(20);
此時準備執行foo函數
foo函數執行前,建立foo environment。先掃描整個函數中的內容,將形參、函數聲明和變量定義在環境記錄中。
foo的環境記錄:
1. 形參:y: 20;
2. 函數聲明:bar: <function> {formalParameter:..., functionBody:..., scope:指向foo環境(用於以後將outer賦值爲scope)
(函數聲明和函數表達式的區別:函數聲明是提早建立的,函數表達式是執行到該語句的時候才建立的)
3. var定義的變量:z: undefind(此時初始化爲undefined而不是30)
建立好foo的詞法環境後,開始執行foo函數
z=30;
return bar;
bar(40); 此時準備執行bar函數
建立bar的詞法環境:outer引用指向foo的詞法環境
1. q=40
2. 沒有函數定義
3. 沒有var變量
開始執行bar函數
return x + y + z + q; // 這些變量都須要在bar的環境記錄裏面找。
x: bar:outer->foo:outer->global environment: x=10;
y: bar:outer->foo: y=20
z: bar:outer->foo: z=30
q: bar: q=40
特殊的詞法環境:
with:
var foo = "abc"; with({ foo: "bar" }) { function f() { alert(foo); }; (function() { alert(foo); })(); f(); }
全局環境:
outer: null;
f:<function>;
foo:"abc"; with()語句
with語句會建立一個臨時的詞法環境,將傳入的對象中的屬性定義到with的詞法環境記錄中。
(函數聲明或var變量定義在with塊內或with塊外是沒有差異的)
with environment:
outer:global;
foo:"bar";
以後爲function f(){}定義,跳過
匿名函數建立詞法環境Anonymous environment:outer:with environment;
alert(foo); --Anonymous:outer->with:foo="bar";
以後準備執行f();
建立f函數的詞法環境:
outer:global:不是with環境。f函數是定義在全局環境內的,因此f函數的scope是全局環境。當f函數開始執行時,它的outer即爲global
alert(foo): f:outer->global:foo="abc";
// 若是是函數表達式,則會看是在with內執行的,outer:with env;而函數聲明是無論這些的,取決於何時定義。
try-catch:
try { var e = 10; throw new Error(); } catch (e) { function f() { alert(e); } (function() { alert(e); })(); f(); }
http://www.jianshu.com/p/984e7dc3afbe
http://www.jianshu.com/p/30274a76ac8d