"use strict"
var
關鍵字隱式聲明變量。會報引用錯誤「ReferenceError:abc is not define」var obj = {a:1, b:2, a:3}
會報語法錯誤「SyntaxError」。arguments.callee
:一般咱們使用這個語法來實現匿名函數的遞歸,但在嚴格模式下是不容許的。會報錯TypeError。with
語句:嚴格模式下with語句是被禁用的。會報語法錯誤 SyntaxError 。2.註釋/* */
不可嵌套javascript
主要介紹6種基本類型(Undefine、Null、Boolean、Number、String、Object)、原生類型及引用類型概念html
值存於棧內存(stack)中。佔據的空間小,大小固定,頻繁被使用。java
值存於堆內存(heap)中。棧中只保留了一個指針,指向堆中存儲位置的起始地址。佔據空間大,大小不固定。node
<a href="javascript:void(0)">
Boolean:false Number:NaN String:"undefined"
Boolean:false Number:0 String:"null"
true false
if(document.getElementById("notExitElement");){...}
true
, var a = true;
只有如下6個值會被轉換爲false:正則表達式
undefined null false 0 NaN ""或''(空字符串)
注意:空數組[]
和空對象{}
,對應的布爾值都是true。算法
Number:1 0 String:"true" "false"
"abbv" 'lll'
String: "" "123" "notEmpty" Number: 0 123 NaN Boolean:false true true
123
var a = 1;
Number: 0 123 NaN Infinity Boolean:false true true false String: "0" "123" "NaN" "Infinity"
十進制:沒有前導0的數值。
八進制:有前綴0o或0O的數值,或者有前導0、且只用到0-7的七個阿拉伯數字的數值。
十六進制:有前綴0x或0X的數值。
二進制:有前綴0b或0B的數值。
一組屬性的集合。chrome
{a: "value-a", b: "value-b", ...}
Object: {} Number: NaN Boolean: true String: "[object Object]"
主要介紹typeof、Object.prototype.toString、constructor、instanceof等類型識別的方法。express
typeof 1 // number typeof true // boolean typeof "str" // string typeof undefined // undefined typeof null // object typeof {} // object
也就是說,標準數據類型中,除了null被識別爲object,其他類型都識別正確。編程
另外,須要注意typeof undefined
會獲得 undefined
,利用這一點,typeof可用於檢查一個沒有聲明的變量而不報錯:json
if(a) { ... } //Uncaught ReferenceError: a is not defined if(typeof a === 'undefined') { console.log('未定義也不報錯') } //未定義也不報錯
typeof function(){} // function typeof [] // object typeof new Date(); // object typeof /\d/; // object function Person() {}; typeof new Person; // object
所以,不能識別具體的對象類型,函數對象除外。
Object.prototype.toString.call(1); // "[object Number]"
因此咱們寫一個函數來簡化調用,以及截取咱們須要的部分:
function type(obj){ return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase(); } type(1) // number type("str") // string type(true) // boolean type(undefined) //undefined type(null) //null type({}) //object type([]) //array type(new Date) // date type(/\d/) // regexp type(function(){}) // function
Object.prototype.toString能夠準確識別出全部的標準類型
以及內置(build-in)對象類型
。
那麼自定義類型
呢?
function Point(x, y){ this.x = x; this.y = y; } type(new Point(1, 2)) //object
因此它沒法識別出自定義類型
。
對象原型上的一個屬性,它指向了構造器自己。
能夠識別原始數據類型
(undefined
和null
除外,由於它們沒有構造器),內置對象類型
和自定義對象類型
,尤爲注意自定義對象類型(如:Person)。
//判斷原始類型 "Jerry".constructor === String //true (1).constructor === Number //true true.constructor === Boolean //true ({}).constructor === Object //true //判斷內置對象 [].constructor === Array //true //判斷自定義對象 ヾ(o◕∀◕)ノ function Person(name){ this.name = name; } new Person("Jerry").constructor === Person // true ヾ(o◕∀◕)ノ !!!
//判斷原始類型 1 instanceof Number // false true instanceof boolean // false //判斷內置對象類型 [] instanceof Array // true /\b/ instanceof RegExp // true //判斷自定義對象類型 function Person(name){ this.name = name; } new Person("miao") instanceof Person // true
不能判斷原始類型
,能夠判斷內置對象類型
,能夠判斷自定義對象類型及父子類型
。
分爲兩類:普通對象
與構造器對象
。其中,構造器對象可用於實例化普通對象。
普通對象只有自身的屬性和方法。
構造器對象除了自身的屬性和方法以外,還有原型對象prototype上的屬性和方法,以及實例化出來的對象的屬性和方法。
如下代碼建立了一個Point構造器並實例化了一個p對象
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(1,1); p.move(1,2);
- "__proto__"是對象的一個內部隱藏屬性。
- "__proto__"是對實例化該對象的構造器的
prototype屬性
的一個引用,所以能夠訪問prototype的全部屬性和方法。- 除了Object對象,每一個對象都有一個"__proto__"屬性,"__proto__"逐級增加造成一個鏈就是咱們所說的原型鏈,原型鏈頂端是一個Object對象。
- 當開發者調用對象屬性或方法時(好比"p.move(1,2)"),引擎首先會查找p對象的自身屬性,若是自身屬性中沒有"move"方法,則會繼續沿原型鏈逐級向上查找,直到找到該方法並調用。
- "__proto__"跟瀏覽器引擎實現相關,不一樣的引擎中名字和實現不盡相同(chrome、firefox中名稱是"__proto__",而且能夠被訪問到,IE中沒法訪問)。基於代碼兼容性、可讀性等方面的考慮,不建議開發者顯式訪問"__proto__"屬性或經過"__proto__"更改原型鏈上的屬性和方法,能夠經過更改構造器prototype對象來更改對象的__proto__屬性。
Point.prototype
其實就是個普通對象:
Point.prototype = { move: function(){ ... }, constructor: function(){ ... } }
1.構造器對象原型鏈上 倒數第二個__proto__
是一個Function.prototype
對象引用,所以能夠調用Function.prototype
的屬性和方法。
2.構造器對象自己有一個prototype
屬性, 有這個屬性就代表該對象能夠用來生成其餘對象:用該構造器實例化對象時該prototype
會被實例對象的__proto__
所引用,即添加到實例對象的原型鏈上。
3.構造器對象自己是一個function
對象,所以會有name,length
等自身屬性。
對象的全部鍵名都是字符串,鍵名加不加引號均可以。
用於建立對象的下面三行語句是等價的:
var o1 = {}; var o2 = new Object(); var o3 = Object.create(Object.prototype);
String/Number/Boolean/Array/Date/Error
構造器都是Object子類對象。
自身的屬性、方法:prototype,create,keys,getOwnPropertyNames,getOwnPropertyDescriptor,getPrototypeOf
原型對象prototype的屬性和方法:constructor,toString,valueOf,hasOwnProperty,isPrototypeOf
生成的實例對象只有原型鏈屬性__proto__,沒有其餘的屬性,也就是Object
沒有實例對象屬性,方法
。相比較而言,Array
有實例對象屬性length
,也就是說,Arrya類型的實例對象有length
屬性。
幾個重要方法:
Object.create(proto)
: 基於原型對象建立新對象,傳入的是一個對象,會被做爲建立出的對象的__proto__屬性值。
Object.keys(obj)
:返回一個由obj自身所擁有的可枚舉屬性的屬性名組成的數組。好比:
var obj = { a: 'aaa', b: 'bbb'}; Object.keys(obj); // ["a", "b"] var arr = ['a', 'b', 'c']; Object.keys(arr); // ["0", "1", "2"]
若是想要對象上不可枚舉的屬性也被列舉出來,用Object.getOwnPropertyNames(obj)
方法。好比:
var arr = ['a', 'b', 'c']; Object.getOwnPropertyNames(arr); // ["0", "1", "2", "length"]
Object.getOwnPropertyDescriptor(obj, 'prop')
能夠讀出對象自身屬性的屬性描述對象。
var obj = { prop1: 'hello'}; Object.getOwnPropertyDescriptor(obj, 'prop1'); //Object {value: "a", writable: true, enumerable: true, configurable: true}
Object.getPrototypeOf(obj)
獲取對象的Prototype對象。
Object.prototype.valueOf
:返回指定對象的原始值
這個方法幾乎不會手動調用,而是JavaScript自動去調用將一個對象轉換成原始值(primitive value)。
默認狀況下,每個內置對象都會覆蓋這個方法返回一個合理的值。若是對象沒有原始值,這個方法就會返回對象自身。好比:
//有原始值 var num= new Number(3); num; // {[[PrimitiveValue]]: 3} num.valueOf(); // 3 num + 4; // 7 valueOf()被JavaScript隱式調用了 //沒有原始值 var obj = {'a': 'aaa'} obj.valueOf(); // {'a': 'aaa'}
假設你有一個對象類型的myNumberType,你想爲它建立一個valueOf方法。下面的代碼能夠自定義一個valueOf方法:
function myNumberType (n){ this.number = n; } myNumberType.prototype.valueOf = function () { return this.number; } var obj = new myNumberType(4); obj + 3; // 7 自定義的valueOf方法被JavaScript自動調用了
Object.prototype.toString
:獲取方法調用者的標準類型
默認狀況下,toString() 方法被每一個繼承自Object的對象繼承。若是此方法在自定義對象中未被覆蓋,toString() 返回 "[object type]",其中type是對象類型。如下代碼說明了這一點:
var obj = {a: 1}; obj.toString(); // "[object Object]"
不少內置對象重寫了toString方法。包括Array,Boolean,Number,Date,String例如:
var a = new Number(1) b = a.toString(); // "1" //可用於進行進制轉換 var num1 = 10; num1.toString(16); // "a" num1.toString(2); // "1010" num1.toString(8); // "12" var c = new String('abc') d = c.toString(); // "abc" var e = new Array(['a', 'b', 'c']) f = e.toString(); // "a,b,c"
還能夠用來檢查對象類型:
var toString = Object.prototype.toString; toString.call(new Date); // [object Date] toString.call(new String); // [object String] toString.call(Math); // [object Math] toString.call(1); // [object Number] toString.call('1'); // [object String] //Since JavaScript 1.8.5 toString.call(undefined); // [object Undefined] toString.call(null); // [object Null]
Object.prototype.hasOwnProperty
:判斷一個屬性是對象自身屬性,仍是原型上的屬性。
var obj = Object.create({a:1}); obj.b = 2; obj.hasOwnProperty('a'); //false obj.hasOwnProperty('b'); //true
Object.property.isPrototypeOf
:判斷當前對象是否爲另外一個對象的原型。
var father = { a: 'aaa', b: 'bbb'}; var child = Object.create(father); father.isPrototypeOf(child); // true
delete
命令用於刪除對象的屬性,無論該屬性是否存在,刪除後都返回true
,只有當該屬性的configurable: false
時,刪除操做才返回false,也就是該屬性不得刪除。
var o = { p: 'Hello'} delete o.p; o; // Object{}
注意:delete
不得刪除var定義的變量。
in
運算符用於檢查對象是否包含某個屬性(這裏寫的是屬性的鍵名)。存在返回true
。能夠用於判斷全局變量是否存在。
var o = {p: 'hello'} 'p' in o; // true 'o' in window; // true
存在的問題是:in
運算符對於繼承的屬性也返回true
。
for ... in
循環用來遍歷一個對象的所有屬性。
var o = { a: 'hello', b: 'world'}; for( var key in o) { console.log(key + ': ' + o[key]); } //a: hello //b: world
注意:會遍歷繼承來的屬性。因此通常不推薦使用。通常會用Object.keys(obj)
,能夠只遍歷對象自己的屬性。
使用String()
和Number()
方法能夠將各類類型的值強制轉換爲string
或number
類型。好比:
Number('222'); //222 Number(null); // 0 Number(undefined); // NaN String(new Number(2)); // '2' String(['a', 'b']); //'a,b'
但當轉換的數值爲對象時,轉換規則爲:
Number(obj)
:(簡單地說值爲NaN)
1.先調用對象自身的valueOf()方法,若是返回原始類型的值,那麼直接對該值使用Number函數,再也不進行後續步驟。
2.若是valueOf方法返回的值類型是對象,那麼調用這個對象的toString()方法,若是返回的是原始類型的值,那麼對這個值使用Number()方法,再也不進行後續步驟。
3.若是toString返回的是對象,就報錯。
var obj = {a: 1}; Number(obj); // NaN //等同於 if(typeof obj.valueOf() === 'object') { Number(obj.toString()); } else { Number(obj.valueOf()); }
String(obj)
:
1.先調用對象自身的toString()方法,若是返回原始類型的值,那麼直接對該值使用String函數,再也不進行後續步驟。
2.若是toString方法返回的值類型是對象,那麼調用這個對象的valueOf()方法,若是返回的是原始類型的值,那麼對這個值使用String()方法,再也不進行後續步驟。
3.若是valueOf返回的是對象,就報錯。
var obj = {a: 1} String(obj); // [object Object] //等價於 if(typeof obj.toString() === 'object') { String(obj.valueOf()); } else { String(obj.toString()); }
構造器對象屬性、方法:prototype, fromCharCode
原型對象屬性、方法:constructor,indexOf,replace,slice,charCodeAt,toLowerCase
幾個重要方法:
1.String.prototype.indexOf
:獲取子字符串在字符串中位置索引(一次只查找一個)
語法:strObj.indexOf(searchvalue,fromindex)
var str = "abcdabcd"; var idx = str.indexOf("b"); // 1 var idx2 = str.indexOf("b", idx+1); //5
2.String.prototype.replace
:查找字符串替換成目標字符(一次只替換一個)
語法:strObj.replace(regexp/searchvalue, newSubStr/function)
var str = "1 plus 1 equal 3"; str = str.replace("1","2"); // "2 plus 1 equal 3" str = str.replace(/\d+/g, "$& dollar"); //"2 dollar plus 1 dollar equal 3 dollar"
第二個參數是newSubStr
時,其中
$& 表明插入當前匹配的子串 $` 表明插入當前匹配的子串左邊的內容 $' 表明插入當前匹配的子串右邊的內容 $n 是當第一個參數爲regexp對象,且n爲小於100的非負整數時,插入regexp第n個括號匹配到的字符串
第二個參數是function
時,函數的返回值爲替換的字符串,注意若是第一個參數爲regexp且爲全局匹配,那麼每次匹配都會調用這個函數,把匹配到的值用函數的返回值替換。函數參數:
match:匹配的子串,至關於 $& p1,p2,...: 若是第一個參數爲regexp,對應第n個括號匹配到的字符串,至關於$1,$2 offset: 匹配到的字符串到原字符串中的偏移量,好比 bc 相對於 abcd ,offset 爲1 string: 被匹配的原字符串 //精確的參數個數取決於第一個參數是否爲regexp,以及有多少個括號分組
3.String.prototype.split
:按分割符將字符串分割成字符串數組
語法:strObj.split(separator, howmany)
var str = "1 plus 1 equal 3"; str.split(" "); // ["1", "plus", "1", "equal", "3"] str.split(" ", 3); // ["1", "plus", "1"] str.split(/\d+/); //["", " plus ", " equal ", ""]
4.str.slice(beginSlice[, endSlice])
提取一個字符串的一部分,並返回一個新的字符串
var str = 'abcdefg'; str.slice(1,3); // 'bc' str; //'abcdefg'
5.str.substr(start[, length])
返回從start開始,length個數的字符串
var str = 'abcdefg'; str.substr(1,3); //'bcd' str; //'abcdefg'
6.str.substring(start[, end])
返回從start開始到end(不包括end)結束的字符串。其中,start和end都大於0.
var str = 'abcdefg'; str.substring(1,3); //'bc' str; //'abcdefg'
7.str.trim()
返回的是一個新字符串,刪除了原字符串兩端的空格
var str = ' abc '; str.trim(); // 'abc' str; // ' abc '
8.str.charAt(index)
從一個字符串中返回index位置的字符,indx默認是0
var str = 'abcdefg'; str.charAt(1); //'b'
9.str.concat(string2, string3[, ..., stringN])
返回新字符串,將原字符串與一個或多個字符串相連
var str = 'abc'; var str2 = '123'; str.concat(str2);// 'abc123'
強烈建議使用 賦值操做符(+, +=)代替 concat 方法。
10.str.includes(searchString[, position])
判斷searchString是否包含在str中,返回布爾值。
var str = 'abc'; str.includes('ab'); //true
11.str.indexOf(searchValue[, fromIndex])
返回str中searchValue第一次出現的位置,沒找到返回-1
"Blue Whale".indexOf("Blute"); // returns -1 "Blue Whale".indexOf("Whale", 0); // returns 5
12.str.lastIndexOf(searchValue[, fromIndex])
返回str中searchValue最後一次出現的位置,沒找到返回-1
"abcabc".lastIndexOf("a") // returns 3 "abcabc".lastIndexOf("d") // returns -1 "abcabc".lastIndexOf("a",2) // returns 0 "cbacba".lastIndexOf("a",0) // returns -1
13.str.endsWith(searchString [, position]);
屬於ES6.判斷searchString是不是以另一個給定的字符串結尾的,返回布爾值
var str = "To be, or not to be, that is the question."; str.endsWith("question."); // true str.endsWith("to be"); // false str.endsWith("to be", 19); // true str.endsWith("To be", 5); // true
14.str.match(regexp);
當一個字符串與一個正則表達式匹配時,match()方法檢索匹配項。 返回一個包含了整個匹配結果以及任何括號捕獲的匹配結果的 Array ;若是沒有匹配項,則返回 null 。
若是正則表達式沒有 g 標誌,則 str.match() 會返回和 RegExp.exec() 相同的結果。
若是正則表達式包含 g 標誌,則該方法返回一個 Array ,它包含全部匹配的子字符串而不是匹配對象。
var str = 'For more information, see Chapter 3.4.5.1'; var re = /see (chapter \d+(\.\d)*)/i; var found = str.match(re); console.log(found); // logs [ 'see Chapter 3.4.5.1', // 'Chapter 3.4.5.1', // '.1', // index: 22, // input: 'For more information, see Chapter 3.4.5.1' ] // 'see Chapter 3.4.5.1' 是整個匹配。 // 'Chapter 3.4.5.1' 被'(chapter \d+(\.\d)*)'捕獲。 // '.1' 是被'(\.\d)'捕獲的最後一個值。 // 'index' 屬性(22) 是整個匹配從零開始的索引。 // 'input' 屬性是被解析的原始字符串。
15.str.search(regexp)
執行正則表達式和String對象之間的一個搜索匹配。若是匹配成功,則 search() 返回正則表達式在字符串中首次匹配項的索引。不然,返回 -1。
下例記錄了一個消息字符串,該字符串的內容取決於匹配是否成功。
function testinput(re, str){ var midstring; if (str.search(re) != -1){ midstring = " contains "; } else { midstring = " does not contain "; } console.log (str + midstring + re); }
16.str1.localeCompare(str2)
比較兩個字符串,返回一個整數,若是小於0,表示第一個字符串小於第二個字符串;若是大於0,表示第一個字符串大於第二個字符串。
'apple'.localeCompare('banana'); //-1
該方法最大的特色就是會考慮語言的順序。看下面的例子:
'B' > 'a' // false 由於Unicode編碼中,大寫字母都在小寫字母前面:B的碼點是66,而a的碼點是97。 'B'.localeCompare('a'); //1 考慮了語言的天然順序
Number.prototype.toFixed()
用於將一個數轉換爲指定位數(有效範圍爲0到20)的小數,返回這個小數對應的字符串。
var a = 1.234 a.toFixed(2);//1.23
Number.prototype.toExponential()
用於將一個數轉爲科學計數法形式。參數爲保留的小數位數,有效範圍0-20。
(1234).toExponential() // "1.234e+3" (1234).toExponential(1) // "1.2e+3" (1234).toExponential(2) // "1.23e+3"
Number.prototype.toPrecision()
用於將一個數字轉爲指定位數的有效數字。參數爲有效數字的位數,範圍1-21。
(12.34).toPrecision(1) // "1e+1" (12.34).toPrecision(2) // "12" (12.34).toPrecision(4) // "12.34" (12.34).toPrecision(5) // "12.340"
記住:
Boolean(undefined) // false Boolean(null) // false Boolean(0) // false Boolean('') // false Boolean(NaN) // false Boolean(1) // true Boolean('false') // true Boolean([]) // true Boolean({}) // true Boolean(function () {}) // true Boolean(/foo/) // true
Array.isArray(obj)
splice,push,pop[刪除最後一個],shift[刪除第一個],unshift[添加到第一個],reverse[反轉],sort
concat,slice,indexOf,lastIndexOf
every,filter,map,forEach,reduce
length
length
屬性是可寫的,若是設置length到小於數組當前長度,那麼數組長度會縮短。
所以,將數組清空的一個有效方法,就是將數組的length設置爲0。舉例以下:
var arr = [1,2,3]; arr.length = 0; arr; []
當length屬性設爲大於數組個數時,讀取新增的位置都會返回undefined
。
注意:只要有length的對象就是類數組對象。典型的相似數組的對象是函數的arguments對象,以及大多數DOM元素集,還有字符串。
將類數組對象轉化爲對象的方法是:var arr = Array.prototype.slice.call(arrayLike);
。
幾個經常使用對象方法:
語法: arr.sort([compareFunction])
若是compareFunction(a, b)返回值小於0,a在b前;返回值大於0,a在b後。
比較函數格式以下:
function compare(a, b) { if (a is less than b by some ordering criterion) { return -1; } if (a is greater than b by the ordering criterion) { return 1; } // a must be equal to b return 0; }
但願比較數字而非字符串,比較函數能夠簡單的以 a 減 b,以下的函數將會將數組升序排列
function compareNumbers(a, b) { return a - b; }
var fruit = ['cherries', 'apples', 'bananas']; fruit.sort(); // ['apples', 'bananas', 'cherries'] var scores = [1, 10, 21, 2]; scores.sort(); // [1, 10, 2, 21] // 注意10在2以前, // because '10' comes before '2' in Unicode code point order. var things = ['word', 'Word', '1 Word', '2 Words']; things.sort(); // ['1 Word', '2 Words', 'Word', 'word'] // 在Unicode中, 數字在大寫字母以前, // 大寫字母在小寫字母以前. var numbers = [4, 2, 5, 1, 3]; numbers.sort(function(a, b) { return a - b; }); console.log(numbers); // [1, 2, 3, 4, 5]
2.Array.prototype.splice(start[, deleteCount, item1, item2, ...])
:從數組中添加、刪除或替換元素,並返回被刪除的元素列表。會更改原數組。若是deleteCount
被省略,則其至關於(arr.length - start)
。
var arr = [1,2,3,4]; let deleteArr = arr.splice(1, 2, 'a', 'b'); deleteArr; //[2, 3] arr; // [1, 'a', 'b', 4]
3.Array.prototype.forEach:遍歷數組元素並調用回調函數
語法:arr.forEach(callback[,thisArg])
function callback(value, index, array){ ... }
var arr = ["a", ,"b"]; arr.forEach(function(value, index, array){ console.log(value);}) //"a" //"b" //空元素被跳過去了哦 (,• ₃ •,)
注意:forEach
方法沒法中斷執行,總會將全部成員遍歷完,若是但願符合某種條件就中斷執行,須要使用 for
循環。
4.arr.filter(callback)
返回一個新數組,包含經過了callback函數中的過濾條件的全部元素
語法:
let newArr = arr.filter(function(element, index, array){ ... })
function isBigEnough(value) { return value >= 10; } var filtered = [12, 5, 8, 130, 44].filter(isBigEnough); // filtered is [12, 130, 44]
5.arr.map(callback)
返回一個新數組,結果是該數組中的每一個元素調用提供的callback函數以後的結果
語法:
let newArr = arr.map(function(element, index, array){ ... })
let numbers = [1, 5, 10, 15]; let roots = numbers.map(function(x, index, array) { return x * 2; }); // roots is now [2, 10, 20, 30] // numbers is still [1, 5, 10, 15]
當須要返回值的時候,通常使用map
;不須要時,通常使用forEach
。
6.arr.every(callback)
測試數組的全部元素是否都經過了指定函數的測試,返回一個布爾值。
語法:
let passed = arr.every(function(element, index, array){ ... })
function isBigEnough(element, index, array) { return (element >= 10); } var passed = [12, 5, 8, 130, 44].every(isBigEnough); // passed is false passed = [12, 54, 18, 130, 44].every(isBigEnough); // passed is true
7.arr.concat(arr1[, arr2, ...])
合併兩個或多個數組
let arr3 = arr1.concat(arr2);
8.arr.slice(begin, end)
返回一個從begin到end(不包括end)這之間的數組淺拷貝到一個新的數組對象。原始數組不會被修改。
var arr = [1,2,3,4]; let newArr = arr.slice(1,3); arr; //[1,2,3,4]; newArr; //[2,3]; arr.slice(); // [1,2,3,4] 不加參數時返回原數組的拷貝
9.arr.indexOf(searchVal[, fromIndex = 0])
返回在數組中能夠找到的給定元素的第一個索引,不存在返回-1
10.arr.lastIndexOf(searchVal[, fromIndex = arr.length - 1])
返回在數組中能夠找到的給定元素的最後一個索引,不存在返回-1
11.arr.reduce()
從左到右依次處理數組的每一個成員,最終累計爲一個值。
方法的第一個參數是一個函數,這個函數接收4個參數:
1.累積變量:默認數組的第一個變量
2.當前變量:默認數組的第二個變量
3.當前位置(從0開始)
4.原數組
前兩個參數是必須的。
求數組成員之和:
var arr = [1,2,3,4,5]; arr.reduce(function(x, y) { console.log(x, y); return x + y; }) // 1 2 // 3 3 // 6 4 // 10 5 // 15
第一輪執行,x是數組的第一個成員,y是數組的第二個成員。從第二輪開始,x爲上一輪的返回值,y爲當前數組成員,直到遍歷完全部成員,返回最後一輪計算後的x。
若是要對累積變量指定初值,能夠把它放在reduce方法和reduceRight方法的第二個參數。
var arr = [1,2,3,4,5]; arr.reduce(function(x, y) { console.log(x, y); return x + y; }, 10) // 10 1 // 11 2 // 13 3 // 16 4 // 20 5 // 25
因爲 reduce
方法依次處理每一個元素,因此它實際上還能夠用來搜索某個元素。好比,找出長度最長的數組元素:
function findLongest(entries) { return entries.reduce(function (longest, entry){ return longest.length > entry.length ? longest : entry; }) } findLongest(['aaa', 'bbbb', 'cc'])
採用function
命令聲明函數時,整個函數會像變量聲明同樣被提高到代碼頭部。好比下面的代碼不會報錯:
f(); function f() { ... }
可是下面的代碼卻會報錯:
f(); var f = function () { ... } //至關於下面這樣 var f; f(); f = function(){ ... }
自身屬性,方法:prototype
原型對象屬性,方法:constructor,apply,call,bind
實例對象屬性,方法:name
,length
,prototype
,arguments,caller
name
返回緊跟在 function
關鍵字以後的函數名。
length
返回函數定義中的參數個數。
toString()
返回函數的源碼。f.toString()
幾個經常使用對象方法:
Function.prototype.apply
:經過傳入參數指定 函數調用者
和 函數參數(一個數組或類數組對象)
並執行該函數。語法:funcObj.apply(thisArg[, argsArray])
thisArg
: 函數運行時指定的this值argsArray
: 一個數組或類數組對象
栗子:
(1) Object.prototype.toString.apply("123");// "[object String]"
(2) 使用apply能夠容許你在原本須要遍歷數組變量的任務中使用內建的函數,好比得到一個數組中最大的值:
var arr = [2, 3, 7, 1]; Math.max.apply(null, arr);// 7 //這等價於 Math.max(arr[0], arr[1], arr[2], arr[3])
(3) 還能夠用於 使用任意一個對象(能夠不是當前自定義對象的實例對象)
去調用自定義對象中定義的方法
function Point(x, y) { this.x = x; this.y = y; } Point.prototype.move = function(x1, y1) { this.x += x1; this.y += y1; } var p = new Point(0,0); //p是一個點 p.move(2,2); // p這個點的位置在x軸右移了2,y軸右移了2 //等價於 p.move.apply(p, [2,2]); //如今咱們有一個圓,原點位於(1,1),半徑r=1 var c = {x: 1, y: 1, r: 1} //要求x軸右移2,y軸右移1 p.move.apply(c, [2,1]);
Function.prototype.bind
:經過傳入參數指定 函數調用者
和 函數參數(一個列表)
並返回該函數的引用而並不調用。語法:funcObj.bind(thisArg[, args])
thisArg
: 函數運行時指定的this值args
: 一個列表(不是數組)
栗子:
(1)綁定函數調用的this值,避免錯誤的使用了將this指向全局做用域
this.x = 9; var module = { x: 81, getX: function() { return this.x; } }; module.getX(); // 返回 81 var retrieveX = module.getX; retrieveX(); // 返回 9, 在這種狀況下,"this"指向全局做用域 // 建立一個新函數,將"this"綁定到module對象 // 新手可能會被全局的x變量和module裏的屬性x所迷惑 var boundGetX = module.getX.bind(module); boundGetX(); // 返回 81
(2)返回函數的引用而並不調用的另外一個好處是:能夠指定函數執行的時間。好比有的需求是,在過了一段時間後執行這個函數。
仍是上面Point的例子,要求在1000ms之後移動圓:
var c = {x: 1, y: 1, r: 1} //要求x軸右移2,y軸右移1 var circlemove = p.move.bind(c, 2, 1); setTimeout(circlemove, 1000);
上面例子中,調用其餘對象的方法
也能夠經過繼承的方法實現:
function Circle(x, y ,r) { Point.apply(this, [x, y]); this.radius = r; } //將Circle的原型對象指定爲Point的實例對象,這樣Circle.prototype上就有了move(x,y)方法 Circle.prototype = Object.create(Point.prototype); //此時Circle.prototype.constructor屬性還等於function Point(x,y),因此要手動改爲function Circle(x,y,r) Circle.prototype.constructor = Circle; //還能夠定義一些其餘的方法 Circle.prototype.area = function () { return Math.PI*this.radius*this.radius; } var c = new Circle(1,2,3); c.move(2,2);//調用的是本身原型上從Point繼承過來的方法 c.area();
構造器對象Circle的原型鏈:
實例對象c的原型鏈:
函數參數的三個特色:
值傳遞
,也就是說,參數傳遞都只是在棧內存中的操做。因此要特別當心引用類型的參數傳遞,可能會改變傳進來的實參的值。參數類型檢查
實現函數重載RegExp
構造方法:
原型對象屬性、方法:test
, exec
test
:使用正則表達式對字符串進行測試,並返回測試結果
語法:regObj.test(str)
var reg = /^abc/i; reg.test('Abc123'); //true reg.test('bc123');//false
Date
Date.now()
返回當前距離 1970年1月1日 00:00:00 UTC的毫秒數。
Date.parse()
解析日期字符串,返回距離待解析日期距離 1970年1月1日 00:00:00 UTC的毫秒數。
Date.parse('2017-10-10T14:48:00') // 1507618080000 Date.parse('2017-10-10') // 1507593600000 Date.parse('Aug 9, 1995') // 807897600000
Date.prototype.toString()
返回一個完整的日期字符串。
var d = new Date(2013, 0, 1); d.toString() // "Tue Jan 01 2013 00:00:00 GMT+0800 (CST)"
Date.prototype.toLocaleDateString()
返回一個字符串,表明日期的當地寫法。
var d = new Date(2013, 0, 1); d.toLocaleDateString() // 中文版瀏覽器爲"2013年1月1日" // 英文版瀏覽器爲"1/1/2013"
Date.prototype.toLocaleTimeString()
返回一個字符串,表明時間的當地寫法。
var d = new Date(2013, 0, 1); d.toLocaleTimeString() // 中文版瀏覽器爲"上午12:00:00" // 英文版瀏覽器爲"12:00:00 AM"
get
類方法:
getTime():返回距離1970年1月1日00:00:00的毫秒數,等同於valueOf方法。 getDate():返回實例對象對應每月的幾號(從1開始)。 getDay():返回星期幾,星期日爲0,星期一爲1,以此類推。 getYear():返回距離1900的年數。 getFullYear():返回四位的年份。 getMonth():返回月份(0表示1月,11表示12月)。 getHours():返回小時(0-23)。 getMilliseconds():返回毫秒(0-999)。 getMinutes():返回分鐘(0-59)。 getSeconds():返回秒(0-59)。 getTimezoneOffset():返回當前時間與UTC的時區差別,以分鐘表示,返回結果考慮到了夏令時因素。
一個計算本年度還剩多少天的例子:
function leftDays() { var today = new Date(); var endYear = new Date(today.getFullYear(), 11, 31, 23, 59, 59, 999); var msPerDay = 24 * 60 * 60 * 1000; return Math.round((endYear.getTime() - today.getTime()) / msPerDay); }
var myDate = new Date(); var myDate = new Date(2017,3,1,7,1,1,100); var year = myDate.getFullYear(); // 四位數字返回年份 vat month = myDate.getMonth(); // 0-11 var date = myDate.getDate(); // 1-31 var day = myDate.getDay(); //0-6 var h = myDate.getHours(); // 0-23 var m = myDate.getMinutes(); // 0-59 var s = myDate.getSeconds(); // 0-59 var ms = myDate.getMilliseconds(); //0-999 var allmMS = myDate.getTime(); //獲取從1970至今的毫秒數 獲得的值能夠用於new Date(ms); 小練習:獲取昨天的日期: var today = new Date(); var todayms = today.getTime(); var oneDay = 24*60*60*1000; var yesterday = new Date(todayms - oneDay);
Error
錯誤處理機制Javascript的6種原生錯誤類型:
SyntaxError
解析代碼時發生的語法錯誤ReferenceError
引用不存在的變量時發生的錯誤RangeError
當一個值超出有效範圍時發生的錯誤TypeError
變量或參數不是預期類型時發生的錯誤。好比 new 123
URIError
URI相關函數的參數不正確時拋出的錯誤。主要涉及encodeURI()
,decodeURI()
,encodeURIComponent()
,decodeURIComponent()
,escape()
和unescape()
這六個函數。EvalError
eval函數沒有被正確執行時拋出。該錯誤類型已再也不在ES5中出現了。以上6種派生錯誤,連同原始的Error對象,都是構造函數。
Math,Json
Math對象是擁有一些屬性和對象的單一對象,主要用於數字計算
經常使用方法:
Math.floor(num)
:向下取整
Math.ceil(num)
向上取整
Math.random()
返回0-1之間的一個僞隨機數,必定小於1,但可能等於0。
用處:
1.任意範圍內生成隨機整數
function getRandomInt(min, max) { return Math.floor(Math.random() * (max - min +1 )) + min; }
2.返回指定長度的隨機字符:
function random_str(length) { var ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; ALPHABET += 'abcdefghijklmnopqrstuvwxyz'; ALPHABET += '0123456789-_'; var str = '', randIndex = ''; for(var i=0; i < length; i++) { randIndex = Math.floor(Math.random() * ALPHABET.length); str += ALPHABET.charAt(randIndex); } return str; }
JSON對象主要用於存儲和傳遞文本信息
經常使用方法:
JSON.parse(jsonStr)
:將JSON字符串解析
爲JSON對象
JSON.stringify(jsonObj)
:將JSON對象序列化
爲JSON字符串
全局對象
屬性:NaN,Infinity,undefined
方法:parseInt,parseFloat,isNaN,isFinite,eval
處理URI的方法:encodeURIComponent,decodeURIComponent,encodeURI,decodeURI
構造器屬性:Boolean,String,Number,Object,Function,Array,Date,Error...
對象屬性:Math,JSON
(1)NaN
不等於任何值,包括它本身自己。
(2)parseInt(string[, radix])
用於將字符串轉換爲數字,參數:要轉換的字符串,進制(默認十進制)
注意:若是第一個參數不是字符串,會將其先用String(param)
轉換爲字符串,因此parseInt
會將空字符串
,true
,null
轉換爲NaN
。
parseInt('1'); //1 * 10^0 = 1 parseInt('1', 16); //1 * 16^0 = 1 parseInt('1f'); // 1 * 10^0 = 1 parseInt('1f'); //1 * 16^1 + f * 16^0 = 16 + 15 = 31 parseInt('f1'); // NaN
將非字符串先轉換爲字符串還會致使一些意外的結果:
parseInt(0x11, 36) // 43 // 等同於 parseInt(String(0x11), 36) parseInt('17', 36)
上面代碼中,十六進制的0x11會被先轉爲十進制的17,再轉爲字符串。而後,再用36進制解讀字符串17,最後返回結果43。
radix
取值在2-36之間,0、null、undefined都會被直接忽略,當作默認的10來處理:
parseInt('10', 1); // NaN parseInt('10', 37); // NaN parseInt('10', 0); // 10 parseInt('10', null); // 10 parseInt('10', undefined); // 10 parseInt('10', 2); // 2 ( 1*2^1 + 0*2^0 )
(3)parseFloat
用於將字符串轉換爲浮點數。注意,它與parseInt
同樣,都會將空字符串
,true
,null
轉換爲NaN
:
parseFloat(true) // NaN Number(true) // 1 parseFloat(null) // NaN Number(null) // 0 parseFloat('') // NaN Number('') // 0 parseFloat('123.45#') // 123.45 Number('123.45#') // NaN
(4)eval
計算某個字符串,並執行其中的Javascript代碼。
語法:eval(string)
不建議使用,會有性能問題。
(5)encodeURIComponent
:用於將URI參數中的中文、特殊字符等做爲URI的一部分進行編碼
語法:encodeURIComponent(URIString)
栗子:
1.防止注入性攻擊:var url = "http://www.xxx.com/index.html?name=" + encodeURIComponent(name);
2.依賴URL進行參數傳遞時,對於參數中有特殊字符(如"&","/")的要對其進行編碼:
var url = "http://www.xxx.com/index.html?name=" + encodeURIComponent("Tom&Jerry") + "&src=" + encodeURIConponent("/assert/image/test.png");
介紹表達式、算術運算符、位運算符、布爾運算符、關係運算符、相等與全等、條件運算符。
JS短語,解釋器能夠執行它並生成一個值。
+
:加法運算符(既能夠處理算術的加法,也能夠處理字符串鏈接)算法步驟:
- 若是運算子是對象先自動轉成原始類型的值(即,先valueOf(),若是結果仍是對象,就toString();Date類型則先toString())
- 兩個運算子都爲原始類型以後,若是其中一個爲字符串,則兩個都轉爲字符串,拼接。
- 不然,兩個運算子都轉爲數值,進行加法運算。
[1, 2] + [3] // "1,23" // 等同於 String([1, 2]) + String([3]) // '1,2' + '3'
加法運算符之外的其餘算術運算符(好比減法、除法和乘法),都不會發生重載。它們的規則是:全部運算子一概轉爲數值,再進行相應的數學運算。
1 - '2' // -1 1 * '2' // 2 1 / '2' // 0.5
===
:類型相同且值相等var a = "123"; var oa = new String("123"); a === oa; // false 其中 a 爲string值類型,oa爲object對象類型 //undefined 與 null 都與自身嚴格相等 undefined === undefined // true null === null // true
==
:判斷操做符兩邊對象或值是否相等規則用僞代碼表示爲:
function equal(a, b) { if a、b類型相同 return a === b; else a、b類型不一樣 return Number(a) === Number(b); } "99" == 99 //true "abc" == new String("abc") // true 例外: 1.null == undefined //true 2.null與undefined進行 == 運算時不進行類型轉換 0 == null //false null == false // false "undefined" == undefined // false
!!x
表示取x表達式運行後的Boolean
值&&
和 ||
都有短路功能且運算符的運算規則:若是第一個運算子的布爾值爲true
,則返回第二個運算子的值(注意不是布爾值);若是第一個運算子的布爾值爲false
,返回第一個運算子的值(不是布爾值)。或運算符的運算規則:若是第一個運算子的布爾值爲
true
,則返回第一個運算子的值(注意不是布爾值),且再也不對第二個運算子求值;若是第一個運算子的值爲false
,則返回第二個運算子的值(不是布爾值)。
用於直接對二進制位進行計算。
異或運算:^
。表示當兩個二進制位不相同,則結果爲1
,不然結果爲0
.
經常使用於在不定義新變量的條件下互換兩個數的值:
var a = 3; var b = 7; a^=b; b^=a; a^=b; a; // 7 b; // 3
位運算符能夠用做設置對象屬性的開關。
假設某個對象有4個開關,每一個開關都有一個變量。那麼能夠設置一個四位的二進制數,用來表示四個開關
var FLAG_A = 1; // 0001 var FLAG_B = 2; // 0010 var FLAG_C = 4; // 0100 var FLAG_D = 8; // 1000
上面代碼設置A、B、C、D四個開關,每一個開關分別佔有一個二進制位。
而後就能夠用與運算檢驗當前是否打開了某個開關。
var flags = 5; // 0101 if(flags && FLAG_C) { console.log('當前打開了C開關.'); } // 當前打開了C開關.
,
用於對兩個表達式求值,並返回後一個表達式的值。
var x = 0; var y = (x++, 10); x; // 1 y; // 10
* / %
高於 + -
高於 && ||
高於 ? :
完整表格:
大部分運算符是從左到右計算的,少數是從右向左計算。最多見的是賦值運算符 =
和 三元條件運算符 ? :
var x = y = z = m; var q = a ? b : c ? d : e ? f : g; //其實等價於: var x = (y = (z = m)); var q = a ? b : (c ? d : (e ? f : g));
條件控制語句
循環控制語句
for in
:遍歷對象的屬性
function Car(id, type, color) { this.id = id; this.type = type; this.color = color; } var benz = Car(12345, "benz", "black"); for(var key in benz) { console.log(key + ": " + benz[key]); } //id: 12345 //type: benz //color:black
//當這個對象有函數時遍歷就會把原型對象上的方法也遍歷出來 Car.prototype.start = function() { console.log(this.type + "start"); } //遍歷的時候會多一項 //start: function(){ ... } //但咱們一般是不須要方法的,而方法一般都是存在於原型對象上,因此能夠經過hasOwnProperty判斷是否爲對象自己屬性: for(var key in benz) { if(benz.hasOwnProperty(key)) { console.log(key + ":" + benz[key]); } }
異常處理語句
try catch finally
throw
with語句
經過暫時改變變量的做用域鏈(將with語句中的對象添加到做用域鏈的頭部),來減小代碼量。
(function(){ var a = Math.cos(3 * Math.PI) + Math.sin(Math.LN10); }); //用with語句 (function(){ with(Math){ var a = cos(3 * PI) + sin(LN10); } })
原型鏈如圖:
生命週期
和做用範圍
。變量做用域分爲靜態做用域和動態做用域:
靜態做用域(詞法做用域):在編譯階段就能夠決定變量的引用,只跟程序定義的原始位置有關,與代碼執行順序無關。
動態做用域:在運行時才能決定變量的引用。一步一步執行代碼,把執行到的變量和函數定義從下到上依次放到棧裏面,用的時候選離本身最近的一個(好比有兩個x的定義,選離本身最近的一個)。
JS使用靜態做用域。ES5使用詞法環境管理靜態做用域。
record
)[形參、變量、函數等]outer
)一段代碼在執行以前,會先初始化建立一個詞法環境。又由於JS沒有塊級做用域,只有全局做用域和函數做用域,因此在JS中只有全局代碼
或者函數代碼
開始執行前會先初始化詞法環境。
scope:currentEnvironment
)這也是造成閉包的必要條件呀!●▽● 知道了詞法環境的構成,就能夠分析代碼是如何執行的,知道里面的變量到底引用的是什麼值。
1.形參、函數定義或變量定義名稱衝突時,優先級:函數定義 > 形參 > 變量定義
2.arguments
這個對象其實也是在環境記錄中的
3.函數表達式
是在執行到函數表達式這一句代碼時才建立函數對象,而函數定義
是在代碼初始化時就建立了函數對象,保存了當前做用域。
var foo = "abc"; with({ foo: "bar" }) { function f() { alert foo; } (function() { alert foo; })(); //這就是一個函數表達式,只有在執行到這一句時纔會建立函數對象 f(); }
注意函數定義與函數表達式的區別:
由於詞法環境只會記錄【形參,函數定義,變量定義】這三項。
並且JS中只有全局做用域
和函數做用域
,沒有塊級做用域
,因此全部不在函數做用域中定義的變量或者函數都是屬於全局做用域的
。
而with會建立一個臨時的做用域,叫with的詞法環境,在with中用var定義一個變量與在with外同樣,都是在代碼初始化時就被放到詞法環境中,而函數表達式則是在執行到那一句代碼時再放進詞法環境中。
因此函數定義f()的詞法環境outer指向global,而函數表達式詞法環境的outer指向with。
注意:這裏僅僅拿with舉例,咱們不建議使用with,容易形成混淆。
catch
的代碼塊與with
的代碼塊很類似,也是臨時建立的詞法環境。
左側的 closure Environment
即爲右邊紅框圈出來的函數表達式
的詞法環境,outer指向catch Environment。
(function A(){ A = 1; alert(A); })();
由於A是一個函數表達式,並非一個函數定義,因此A不會定義到global Environment
中。
而後執行到這一句時,與以前的匿名函數表達式相似,建立一個該函數表達式的詞法環境,把A這個函數表達式定義到這個詞法環境中。並且在A的環境裏定義的A函數不能被修改,因此A = 1
是沒有用的,仍然會alert出 function A(){A=1;console.log(A);}
。
參考資料:wikipedia Scope
function foo() { var x = 1; function bar() { console.log(x); } return bar; } var x = 2; var f = foo(); f() // 1
上面代碼中,函數foo內部聲明瞭一個函數bar,bar的做用域綁定foo。當咱們在foo外部取出bar執行時,變量x指向的是foo內部的x,而不是foo外部的x。正是這種機制,構成了下文要講解的「閉包」現象。
函數定義
和函數表達式
到底怎樣區分?!一直就沒搞懂過這個問題,看了半天MDN上的文檔,都是在說:
函數定義
與函數表達式
基本是同樣滴
由於那些在等號右邊的只能是表達式,因此它們是函數表達式
雖然,函數表達式
通常狀況下沒有名字,可是呢,它們也是能夠有名字滴,有名字以後就與函數聲明
的語法真的同樣了,這還怎麼區分啊摔!( ´Д`)
不信你本身看 MDN函數那一章 看的我都要哭了
直到我去翻了阮大大博客裏講函數的一篇, ⊙▽⊙ 裏面講當即調用的函數表達式(IIFE)一節裏有這樣一句話:
爲了不解析上的歧義,JavaScript引擎規定, 若是function關鍵字出如今行首,一概解釋成語句。所以,JavaScript引擎看到行首是function關鍵字以後,認爲這一段都是函數的定義。
因此這樣句首不是function
的(function(){})();
就是函數表達式啦!下面這樣也能夠:
var i = function(){ return 10; }(); true && function(){ /* code */ }(); 0, function(){ /* code */ }(); // 甚至這樣也能夠: !function(){ /* code */ }(); ~function(){ /* code */ }(); -function(){ /* code */ }(); +function(){ /* code */ }();
閉包是由函數
和它引用的外部詞法環境
組合而成。
閉包容許函數訪問其引用的外部詞法環境中的變量(又稱自由變量)。
看個栗子:下面的函數add()
最終return
的值是一個函數,且在add函數
外調用return出來的這個匿名函數
時能夠引用add函數內部的環境
,使用add函數內部定義的變量i
。
function add(){ var i = 0; return function() { console.log(i++); } } var f = add(); f(); // 0 f(); // 1
通常來講,一個函數執行完以後,內部的局部變量都會被釋放掉,就不存在了。可是JS比較特殊,由於JS中容許函數做爲返回值
,當函數做爲返回值,這個函數就保存了對外部詞法環境的引用,而相應的,外部詞法環境中定義的變量就不會被釋放掉,會一直保留在內存中。這就是JS的閉包。閉包使得它引用的函數
的內部環境一直存在,因此閉包能夠看做是它引用的函數的內部環境
的一個接口。
廣義上來講,全部的JS函數均可以稱爲閉包,由於全部的JS函數建立後都保存了當前的詞法環境。
閉包最大的做用有兩個,一個是能夠讀取函數內部的變量,另外一個是使這些變量始終保存在內存中。
function createIncrementor(start) { return function () { return start++; }; } var inc = createIncrementor(5); inc() // 5 inc() // 6 inc() // 7
start
存在於 createIncrementor
的詞法環境中,經過閉包inc
被保留在內存中,所以能夠累加。
var addHandlers = function(nodes) { for(var i=0; i<nodes.length; i++){ nodes[i].onclick = function(){ alert(i); } } }
試過會發現:每次彈出的都是3,因此這樣寫是不能實現的。由於JS沒有塊級做用域,因此每次for循環不會造成單獨的做用域,所以最內層的匿名function函數中全部的i共享它的外部詞法環境addHandler函數中的同一個變量i。而當onclick被調用時,循環早已結束,因此i的值也就是循環結束後的nodes.length
。
想要更清晰一些,能夠把for循環拆開看看:
var addHandler = function(nodes){ var i = 0; nodes[i].onclick = function(){ alert(i); } //等價於 //nodes[0].onclick = function(){ // alert(i); 注意這裏仍是i,當onclick被調用,循環已經結束了,也就是addHandler函數執行到最後了,因此i是3 //} i = 1; nodes[i].onclick = function(){ alert(i); } i = 2; nodes[i].onclick = function(){ alert(i); } i = 3; } addHandler(document.getElementsByTagName('a'));
這樣總能夠理解了吧。
既然問題出在每一次for循環沒有可以造成單獨的做用域來保存每一次for循環的變量i的值
,那麼要解決的就是這個問題。
方法就是用剛剛學的閉包:在內層匿名函數外面與for循環之間加一個幫助函數helper,helper有一個形參i
,在每一次for循環時,都調用一次helper(i),傳入本次循環的i,helper接收了這個形參
以後,把它存到本身的詞法環境中,內部的匿名function函數就能夠訪問這個形參,再將這個匿名函數return出去,這就造成了一個閉包。這樣就作到了每次循環都造成一個閉包
。所以就能夠把傳入的i的值給保存下來。未來當onclick事件被觸發時,就會調用helper函數return出來的匿名函數,也就可使用helper函數的內部詞法環境,彈出當時存進去的那個i的值。
劃重點:
每一次for循環,調用一次helper(i)函數;
每一次onclick事件被觸發,調用helper內部return出來的匿名函數,這個匿名函數的outer指向helper函數內部。
var addHandlers = function(nodes) { var helper = function(i) { return function(){ alert(i); } } for(var i=0; i<nodes.length; i++){ nodes[i].onclick = heapler(i); } }
若是仍是沒法理解,那麼咱們再來把for循環拆開看看
var addHandler = function(nodes){ var helper = function(i){ return function(){ alert(i); } } var i = 0; nodes[i].onclick = helper(i); // 等價於 nodes[0].onclick = helper(0); i = 1; nodes[i].onclick = helper(i);// 等價於 nodes[1].onclick = helper(1); i = 2; nodes[i].onclick = helper(i); i = 3; } addHandler(document.getElementsByTagName('a'));
那麼,你能不能再想出一種使用閉包解決這個問題的寫法呢?想一想看 (。・ω・)ノ゙
參考寫法:
var addHandler = function(nodes){ var helper2 = function(i) { nodes[i].onclick = function(){ alert(i); } } for(var i=0; i<nodes.length; i++){ helper2(i); } } addHandler(document.getElementsByTagName('a'));
注意,外層函數每次運行,都會生成一個新的閉包,而這個閉包又會保留外層函數的內部變量,因此內存消耗很大。所以不能濫用閉包,不然會形成網頁的性能問題。
共享函數,私有變量。
var observer = (function(){ var observerList = []; return { add: function(obj){ observerList.push(obj); }, empty: function(obj){ observerList = []; }, get: function(obj){ return observerList; } } })
//Teacher constructor function Teacher() { this.courses = []; this.job = 'teacher'; this.setName = function(name){ this.name = name; } this.addCourse = function(course){ this.courses.push(course); } } var bill = new Teacher(); bill.setName("Bill"); bill.addCourse("math"); var tom = new Teacher(); tom.setName("Tom"); tom.addCourse("physics");
這樣徹底使用構造函數來構造實例對象,他們的存儲是這樣的:
這樣一來,每一個實例對象都會存儲一遍全部的屬性和方法,對於全部的實例對象來講,都耗用了一遍內存,這是很浪費的。實際上咱們能夠看到屬性job
和方法setName
,addCourse
實際上是全部實例對象能夠共用的,那麼如何實現呢?這就引出了原型。
原型是構造器對象的一個屬性,叫prototype
,JS中每一個構造器對象都有這樣一個屬性。這個屬性的值就是實例對象的原型,用於實現原型繼承,讓同一個構造器建立出的多個實例對象共享同一個原型
,這些實例對象就有了共同的屬性和方法。
用原型改寫上面的Teacher構造器:
//Teacher prototype function Teacher(){ this.courses = []; } Teacher.prototype = { job: 'teacher', setName: function(name) { this.name = name; }, addCourses: function(course){ this.courses = course; } } var bill = new Teacher(); bill.setName("Bill"); bill.addCourse("math"); var tom = new Teacher(); tom.setName("Tom"); tom.addCourse("physics");
原型鏈如圖所示:
能夠看到實例對象bill
與tom
都有一個隱式的指針指向了Teacher.prototype
上面的原型鏈並非完整的,咱們來看完整的原型鏈的構成:
咱們知道,在JS中,函數對象是用於建立自定義構造器對象
的構造器對象,因此全部的自定義構造器對象都是Function的一個實例對象;又由於Function也有一個prototype屬性,因此咱們自定義的Teacher也有一個隱式的__proto__
屬性指向Function.prototype
,而這其中有一些引擎自身實現了的一些屬性和方法,好比toString() , apply()
等方法。
而又由於Teacher.prototype
的對象能夠由new Object()
來建立,它是Object
對象的一個實例,因此Teacher.prototype
有一個隱式的__proto__
屬性指向Object.prototype
。
它們之間其實構成了一個鏈式的關係:tom
是以Teacher.prototype
爲原型的,Teacher.prototype
又是以Object.prototype
爲原型的。這樣就造成了一個原型鏈。
JS中的屬性查找、屬性修改和屬性刪除都是經過原型鏈實現的。
先在對象自己找,找不到就到對象的原型上找。
以tom對象爲例:
tom.name = 'Tom Green';
由於tom對象自己有name
這個屬性,因此直接修改。
tom.job = 'assitant';
tom對象
自己沒有job
這個屬性,那麼就爲tom
對象建立一個job
屬性,同時把值賦給這個屬性。注意這裏即便tom對象的原型
上有job
這個屬性,也不會直接修改原型上的屬性。
若是想要修改原型對象上的屬性,那麼這樣作:
Teacher.prototype.job = "assistant";
與修改屬性相似,delete也只能刪除對象自身的屬性,不能刪除原型上的屬性。eg:
delete tom.job;
tom.hasOwnProperty('job');
來源於對象自己就返回 true
,來源於原型或不存在則返回 false
。
Object.create(proto[, propertiesObject])
(而不是使用構造器constructor
方法)能夠直接從一個原型對象
建立一個新的對象,同時爲這個對象指定一個原型。而不須要經過 new Teacher();
來完成。
var teacher = { job: "teacher", courses: [], setName: function(name) { this.name = name; }, addCourses: function(course) { this.courses.push(course); }, } var bill = Object.create(teacher); tom.setName('Bill')
這樣咱們就不用經過構造器,直接建立一個以teacher
對象爲原型的bill實例對象。並無使用前面說的constructor,prototype
等方式。這是ES5中提供的一種新的原型繼承的方式。原型鏈:
全局變量很容易衝突,因此須要作信息隱藏。最好的方式就是封裝:將一部分信息開放出來,另一部分隱藏起來。
Java中有private, protected, public
關鍵字來定義屬性和方法的訪問權限。但JS中並無,那麼JS中是如何實現封裝的呢?
按照咱們原來的寫法,像下圖這樣是沒有實現封裝的:
經過JS語言的特性作相似的模擬。像下圖這樣是能夠實現封裝的:
模擬private
:在function A(){}內部經過var 定義了一個屬性_config
,在外部沒法被直接訪問和修改,而後提供了一個函數this.getConfig()
來讓咱們能夠在外部獲取這個屬性。這樣一來就模擬了private
的特性。
模擬protected
:對於protected
和public
,在JS中沒法作到本質上的區分。可使用一些人爲約束的規則來區分,好比:使用下劃線開頭來定義protected
的屬性和方法。這樣雖然其實是均可以調用的,可是在代碼的書寫上是能夠區分出來的。
(function(){ function ClassA(){} ClassA.classMethod = function(){} ClassA.prototype.api = function(){} function ClassB(){ ClassA.apply(this, arguments); } //給ClassB的原型賦值了一個ClassA類型的實例對象 ClassB.prototype = new ClassA(); //上述這樣會致使ClassB.prototype的constructor也變成ClassA因此咱們要手動改爲ClassB ClassB.prototype.constructor = ClassB; ClassB.prototype.api = function() { ClassA.prototype.api.apply(this, arguments); } var b = new ClassB(); b.api(); })();
原型鏈:
類繼承能夠說是對其餘語言的類繼承的一種模擬,而原型繼承就是JS固有的特性了。
(function(){ //先定義一個原型 var proto = { action1: function(){ }, action2: function(){ } } //使用Object.create基於已有對象建立一個新的對象 var obj = Object.create(proto); })();
new
命令。用 new
命令從構造函數生成一個新對象時,若是忘了加上new
,新對象內部 this
關鍵字就會指向全局對象,致使全部綁定在 this
上的變量都變成全局變量。所以,建議使用Object.create()
命令替代 new
命令。==
運算符,只使用嚴格相等 ===
運算符。+=
代替 ++
。好比:用 x += 1;
代替 ++x;
。switch ... case
每個 case
後都要加 break
語句,這樣形成代碼冗長。並且,使得代碼結構混亂。建議避免使用,改寫成對象結構。function doAction(action) { var actions = { 'hack': function() { return 'hack'; }, 'slash': function() { return 'slash'; }, ... } }