摘要:對象原型上的一個屬性,它指向了構造器本身。其中,構造器對象可用于實例化普通對象。基于代碼兼容性可讀性等方面的考慮,不建議開發者顯式訪問屬性或通過更改原型鏈上的屬性和方法,可以通過更改構造器對象來更改對象的屬性。
基本語法 1.嚴格模式 "use strict" 作用
消除JS語法的一些不合理、不嚴謹、不安全的問題,減少怪異行為并保證代碼運行安全
提高編譯器解釋器效率,增加運行速度
與標準模式的區別隱式聲明或定義變量:嚴格模式不能通過省略 var 關鍵字隱式聲明變量。會報引用錯誤“ReferenceError:abc is not define”
對象重名的屬性:嚴格模式下對象不允許有重名的屬性。var obj = {a:1, b:2, a:3}會報語法錯誤“SyntaxError”。
arguments.callee:通常我們使用這個語法來實現匿名函數的遞歸,但在嚴格模式下是不允許的。會報錯TypeError。
with語句:嚴格模式下with語句是被禁用的。會報語法錯誤 SyntaxError 。
2.注釋/* */不可嵌套
類型系統 3.基本類型(標準類型)主要介紹6種基本類型(Undefine、Null、Boolean、Number、String、Object)、原生類型及引用類型概念
原始數據類型:Undefined、Null、Boolean、String、Number值存于棧內存(stack)中。占據的空間小,大小固定,頻繁被使用。
引用數據類型:Object值存于堆內存(heap)中。棧中只保留了一個指針,指向堆中存儲位置的起始地址。占據空間大,大小不固定。
4.Undefined 出現場景已聲明未賦值的變量
獲取對象不存在的屬性
無返回值的函數的執行結果
函數的參數沒有傳入
向其他數據類型轉換Boolean:false Number:NaN String:"undefined"5.Null 出現場景
null表示對象不存在 document.getElementById("notExitElement");
向其他數據類型轉換Boolean:false Number:0 String:"null"6.Boolean : true false 出現場景
條件語句導致系統執行的隱式類型轉換 if(document.getElementById("notExitElement");){...}
字面量或變量定義: true, var a = true;
只有以下6個值會被轉換為false:
undefined null false 0 NaN ""或""(空字符串)
注意:空數組[]和空對象{},對應的布爾值都是true。
向其他數據類型轉換Number:1 0 String:"true" "false"7.String 出現場景
"abbv" "lll"
向其他數據類型轉換String: "" "123" "notEmpty" Number: 0 123 NaN Boolean:false true true8.Number 出現場景
123 var a = 1;
向其他數據類型轉換Number: 0 123 NaN Infinity Boolean:false true true false String: "0" "123" "NaN" "Infinity"
十進制:沒有前導0的數值。9.Object
八進制:有前綴0o或0O的數值,或者有前導0、且只用到0-7的七個阿拉伯數字的數值。
十六進制:有前綴0x或0X的數值。
二進制:有前綴0b或0B的數值。
一組屬性的集合。
出現場景{a: "value-a", b: "value-b", ...}
向其他數據類型轉換Object: {} Number: NaN Boolean: true String: "[object Object]"類型識別
主要介紹typeof、Object.prototype.toString、constructor、instanceof等類型識別的方法。
10.typeof 對標準數據類型的識別typeof 1 // number typeof true // boolean typeof "str" // string typeof undefined // undefined typeof null // object typeof {} // object
也就是說,標準數據類型中,除了null被識別為object,其余類型都識別正確。
另外,需要注意typeof undefined 會得到 undefined,利用這一點,typeof可用于檢查一個沒有聲明的變量而不報錯:
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
因此,不能識別具體的對象類型,函數對象除外。
11.Object.prototype.toStringObject.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
所以它無法識別出自定義類型。
12.constructor對象原型上的一個屬性,它指向了構造器本身。
可以識別原始數據類型(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???)? !!!13.instanceof
//判斷原始類型 1 instanceof Number // false true instanceof boolean // false //判斷內置對象類型 [] instanceof Array // true // 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);普通對象p的原型鏈: "__proto__"就是我們通常說的原型鏈屬性,他有如下幾個特點:
構造器對象的原型鏈:"__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的屬性和方法。14.Object對象
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),可以只遍歷對象本身的屬性。
15.String, Number, Boolean使用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()); }String
構造器對象屬性、方法: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
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
記住:
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/) // true16.Array 構造器對象(自身)屬性、方法:prototype,Array.isArray(obj) 原型對象屬性、方法:constructor, - 1.改變原數組: splice,push,pop[刪除最后一個],shift[刪除第一個],unshift[添加到第一個],reverse[反轉],sort - 2.不改變原數組: concat,slice,indexOf,lastIndexOf - 3.對數組進行遍歷的方法: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);。
幾個常用對象方法:
Array.prototype.sort:排序,并返回數組
語法: 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"])17.Function
采用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()
幾個常用對象方法:
1.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]);2.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的原型鏈:
function作為普通函數,有3種調用方式:() , apply , call函數參數的三個特點:
形參個數不一定等于實參個數
所有參數傳遞都是值傳遞,也就是說,參數傳遞都只是在棧內存中的操作。所以要特別小心引用類型的參數傳遞,可能會改變傳進來的實參的值。
通過參數類型檢查實現函數重載
18.RegExp、Date、Error RegExp構造方法:
/pattern/flags
new RegExp(pattarn[, flags]);
原型對象屬性、方法:test, exec
test:使用正則表達式對字符串進行測試,并返回測試結果
語法:regObj.test(str)
var reg = /^abc/i; reg.test("Abc123"); //true reg.test("bc123");//falseDate
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對象,都是構造函數。
19.標準內置對象中的非構造器對象之——Math,JsonMath對象是擁有一些屬性和對象的單一對象,主要用于數字計算
常用方法:
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字符串
20.標準內置對象中的非構造器對象之——全局對象屬性: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");
介紹表達式、算術運算符、位運算符、布爾運算符、關系運算符、相等與全等、條件運算符。
21.表達式JS短語,解釋器可以執行它并生成一個值。
22.運算符 + :加法運算符(既可以處理算術的加法,也可以處理字符串連接)算法步驟:
如果運算子是對象先自動轉成原始類型的值(即,先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));JS語句
條件控制語句
循環控制語句
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); } })
原型鏈如圖:
變量作用域 主要關注兩個方面: 1. 變量的生命周期和作用范圍。 2. 看到一個變量時,要能找到變量定義的位置。變量作用域分為靜態作用域和動態作用域:
靜態作用域(詞法作用域):在編譯階段就可以決定變量的引用,只跟程序定義的原始位置有關,與代碼執行順序無關。
動態作用域:在運行時才能決定變量的引用。一步一步執行代碼,把執行到的變量和函數定義從下到上依次放到棧里面,用的時候選離自己最近的一個(比如有兩個x的定義,選離自己最近的一個)。
JS使用靜態作用域。ES5使用詞法環境管理靜態作用域。
詞法環境 是什么? 是用來描述和管理靜態作用域的一種數據結構。 組成?環境記錄(record)[形參、變量、函數等]
對外部詞法環境的引用(outer)
什么時候創建?一段代碼在執行之前,會先初始化創建一個詞法環境。又因為JS沒有塊級作用域,只有全局作用域和函數作用域,所以在JS中只有全局代碼或者函數代碼開始執行前會先初始化詞法環境。
哪些東西會被初始化到詞法作用域中呢?形參(一定要是這個函數顯式定義的形參,而不是真實傳進來的參數,即不是實參)
函數定義(除了這個函數的形參和函數體,需要特別注意:在初始化的時候會保存當前的作用域到函數對象中,即 scope:currentEnvironment)這也是形成閉包的必要條件呀!●▽●
變量定義(var a = xxx;)
詞法環境是怎樣構成的?知道了詞法環境的構成,就可以分析代碼是如何執行的,知道里面的變量到底引用的是什么值。
幾個關于詞法環境的問題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,容易造成混淆。
try-catchcatch的代碼塊與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函數創建后都保存了當前的詞法環境。
閉包的應用閉包最大的作用有兩個,一個是可以讀取函數內部的變量,另一個是使這些變量始終保存在內存中。
1.使外部詞法環境的變量始終保存在內存中function createIncrementor(start) { return function () { return start++; }; } var inc = createIncrementor(5); inc() // 5 inc() // 6 inc() // 7
start 存在于 createIncrementor 的詞法環境中,通過閉包inc被保留在內存中,因此可以累加。
2.保存變量現場 需求:為一組元素注冊onclick事件,當點擊其中某一個元素觸發onclick事件時能將該元素的索引打印出來。 看下面這段代碼是否可以實現?var addHandlers = function(nodes) { for(var i=0; ijsfiddle自己試試
試過會發現:每次彈出的都是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; ijsfiddle里置幾試試
如果還是無法理解,那么我們再來把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注意,外層函數每次運行,都會生成一個新的閉包,而這個閉包又會保留外層函數的內部變量,所以內存消耗很大。因此不能濫用閉包,否則會造成網頁的性能問題。
3.封裝共享函數,私有變量。
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.cour
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/82775.html
摘要:簡潔直觀強悍的前端開發框架,讓開發更迅速簡單。是一套基于的前端框架。首個版本發布于年金秋,她區別于那些基于底層的框架,卻并非逆道而行,而是信奉返璞歸真之道。 2017-1209 ZanUI (Vue) 2017-1218 Onsen UI(Vue, React, Angular) 2017-1215 增加 Vuetify, Weex UI, Semantic UI React,ele...
摘要:簡潔直觀強悍的前端開發框架,讓開發更迅速簡單。是一套基于的前端框架。首個版本發布于年金秋,她區別于那些基于底層的框架,卻并非逆道而行,而是信奉返璞歸真之道。 2017-1209 ZanUI (Vue) 2017-1218 Onsen UI(Vue, React, Angular) 2017-1215 增加 Vuetify, Weex UI, Semantic UI React,ele...
摘要:在線挑戰,還沒用過,貌似現在對英文資料心里還有種抵觸,必須克服實驗樓研發工程師包含了等學習課程。書的作者就是開發了用于數據分析的著名開源庫的作者英文資料,對數據分析中要用到的一些庫,等等做了簡要介紹。形式的資料,示例代碼都很全。 showImg(https://segmentfault.com/img/remote/1460000004852849); 一、說明 面對網絡上紛繁復雜的資...
摘要:從最開始的到封裝后的都在試圖解決異步編程過程中的問題。為了讓編程更美好,我們就需要引入來降低異步編程的復雜性。異步編程入門的全稱是前端經典面試題從輸入到頁面加載發生了什么這是一篇開發的科普類文章,涉及到優化等多個方面。 TypeScript 入門教程 從 JavaScript 程序員的角度總結思考,循序漸進的理解 TypeScript。 網絡基礎知識之 HTTP 協議 詳細介紹 HTT...
閱讀 2224·2021-11-22 09:34
閱讀 1334·2021-10-11 10:59
閱讀 4427·2021-09-22 15:56
閱讀 3270·2021-09-22 15:08
閱讀 3401·2019-08-30 14:01
閱讀 773·2019-08-30 11:16
閱讀 1129·2019-08-26 13:51
閱讀 2906·2019-08-26 13:43