JavaScript基礎大全(原型,閉包)[持續更新]

基本語法

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.註釋/* */不可嵌套javascript

類型系統

3.基本類型(標準類型)

主要介紹6種基本類型(Undefine、Null、Boolean、Number、String、Object)、原生類型及引用類型概念html

圖片描述

原始數據類型:Undefined、Null、Boolean、String、Number

值存於棧內存(stack)中。佔據的空間小,大小固定,頻繁被使用。java

引用數據類型:Object

值存於堆內存(heap)中。棧中只保留了一個指針,指向堆中存儲位置的起始地址。佔據空間大,大小不固定。node

4.Undefined

出現場景

  1. 已聲明未賦值的變量
  2. 獲取對象不存在的屬性
  3. 無返回值的函數的執行結果
  4. 函數的參數沒有傳入
  5. void(expression) 經常使用於<a href="javascript:void(0)">

向其餘數據類型轉換

Boolean:false
Number:NaN
String:"undefined"

5.Null

出現場景

  1. null表示對象不存在 document.getElementById("notExitElement");

向其餘數據類型轉換

Boolean:false
Number:0
String:"null"

6.Boolean : true false

出現場景

  1. 條件語句致使系統執行的隱式類型轉換 if(document.getElementById("notExitElement");){...}
  2. 字面量或變量定義: true, var a = true;

只有如下6個值會被轉換爲false:正則表達式

undefined
null
false
0
NaN
""或''(空字符串)

注意:空數組[]和空對象{},對應的布爾值都是true。算法

向其餘數據類型轉換

Number:1 0
String:"true" "false"

7.String

出現場景

  1. "abbv" 'lll'

向其餘數據類型轉換

String:  ""    "123"  "notEmpty"
Number:  0      123      NaN
Boolean:false  true     true

8.Number

出現場景

  1. 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的數值。

9.Object

一組屬性的集合。chrome

出現場景

  1. {a: "value-a", b: "value-b", ...}

向其餘數據類型轉換

Object:  {}
Number:  NaN 
Boolean: true 
String:  "[object Object]"

類型識別

主要介紹typeof、Object.prototype.toString、constructor、instanceof等類型識別的方法。express

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可用於檢查一個沒有聲明的變量而不報錯: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

所以,不能識別具體的對象類型,函數對象除外。

11.Object.prototype.toString

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

因此它沒法識別出自定義類型

12.constructor

對象原型上的一個屬性,它指向了構造器自己。

能夠識別原始數據類型undefinednull除外,由於它們沒有構造器),內置對象類型自定義對象類型,尤爲注意自定義對象類型(如: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
/\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);

普通對象p的原型鏈:

普通對象p的原型鏈

"__proto__"就是咱們一般說的原型鏈屬性,他有以下幾個特色:

  1. "__proto__"是對象的一個內部隱藏屬性。
  2. "__proto__"是對實例化該對象的構造器的prototype屬性的一個引用,所以能夠訪問prototype的全部屬性和方法。
  3. 除了Object對象,每一個對象都有一個"__proto__"屬性,"__proto__"逐級增加造成一個鏈就是咱們所說的原型鏈,原型鏈頂端是一個Object對象。
  4. 當開發者調用對象屬性或方法時(好比"p.move(1,2)"),引擎首先會查找p對象的自身屬性,若是自身屬性中沒有"move"方法,則會繼續沿原型鏈逐級向上查找,直到找到該方法並調用。
  5. "__proto__"跟瀏覽器引擎實現相關,不一樣的引擎中名字和實現不盡相同(chrome、firefox中名稱是"__proto__",而且能夠被訪問到,IE中沒法訪問)。基於代碼兼容性、可讀性等方面的考慮,不建議開發者顯式訪問"__proto__"屬性或經過"__proto__"更改原型鏈上的屬性和方法,能夠經過更改構造器prototype對象來更改對象的__proto__屬性。

構造器對象的原型鏈:

構造器對象Point的原型鏈

Point.prototype其實就是個普通對象:

Point.prototype = {
  move: function(){ ... },
  constructor: function(){ ... }
}

構造器對象相對於普通對象有以下幾個特色:

1.構造器對象原型鏈上 倒數第二個 __proto__是一個 Function.prototype對象引用,所以能夠調用 Function.prototype的屬性和方法。
2.構造器對象自己有一個 prototype屬性, 有這個屬性就代表該對象能夠用來生成其餘對象:用該構造器實例化對象時該 prototype會被實例對象的 __proto__所引用,即添加到實例對象的原型鏈上。
3.構造器對象自己是一個 function對象,所以會有 name,length等自身屬性。

14.Object對象

對象的全部鍵名都是字符串,鍵名加不加引號均可以。

用於建立對象的下面三行語句是等價的:

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()方法能夠將各類類型的值強制轉換爲stringnumber類型。好比:

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/) // true

16.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);

幾個經常使用對象方法:

  1. 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
實例對象屬性,方法:namelengthprototypearguments,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

函數參數的三個特色:

  1. 形參個數不必定等於實參個數
  2. 全部參數傳遞都是值傳遞,也就是說,參數傳遞都只是在棧內存中的操做。因此要特別當心引用類型的參數傳遞,可能會改變傳進來的實參的值。
  3. 經過參數類型檢查實現函數重載

圖片描述

18.RegExp、Date、Error

RegExp

構造方法:

  1. /pattern/flags
  2. new RegExp(pattarn[, flags]);

原型對象屬性、方法:testexec

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種原生錯誤類型:

  1. SyntaxError 解析代碼時發生的語法錯誤
  2. ReferenceError 引用不存在的變量時發生的錯誤
  3. RangeError 當一個值超出有效範圍時發生的錯誤
  4. TypeError 變量或參數不是預期類型時發生的錯誤。好比 new 123
  5. URIError URI相關函數的參數不正確時拋出的錯誤。主要涉及encodeURI(),decodeURI(),encodeURIComponent(),decodeURIComponent(),escape()unescape()這六個函數。
  6. EvalError eval函數沒有被正確執行時拋出。該錯誤類型已再也不在ES5中出現了。

以上6種派生錯誤,連同原始的Error對象,都是構造函數。

19.標準內置對象中的非構造器對象之——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字符串

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會將空字符串truenull轉換爲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同樣,都會將空字符串truenull轉換爲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.運算符

圖片描述

+ :加法運算符(既能夠處理算術的加法,也能夠處理字符串鏈接)

算法步驟:

  1. 若是運算子是對象先自動轉成原始類型的值(即,先valueOf(),若是結果仍是對象,就toString();Date類型則先toString())
  2. 兩個運算子都爲原始類型以後,若是其中一個爲字符串,則兩個都轉爲字符串,拼接。
  3. 不然,兩個運算子都轉爲數值,進行加法運算。
[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使用詞法環境管理靜態做用域。

詞法環境

是什麼? 是用來描述和管理靜態做用域的一種數據結構。

組成?

  1. 環境記錄(record)[形參、變量、函數等]
  2. 對外部詞法環境的引用(outer

何時建立?

一段代碼在執行以前,會先初始化建立一個詞法環境。又由於JS沒有塊級做用域,只有全局做用域和函數做用域,因此在JS中只有全局代碼或者函數代碼開始執行前會先初始化詞法環境。

哪些東西會被初始化到詞法做用域中呢?

  1. 形參(必定要是這個函數顯式定義的形參,而不是真實傳進來的參數,即不是實參)
  2. 函數定義(除了這個函數的形參和函數體,須要特別注意:在初始化的時候會保存當前的做用域到函數對象中,即 scope:currentEnvironment這也是造成閉包的必要條件呀!●▽●
  3. 變量定義(var a = xxx;)

詞法環境是怎樣構成的?

圖片描述

知道了詞法環境的構成,就能夠分析代碼是如何執行的,知道里面的變量到底引用的是什麼值。

幾個關於詞法環境的問題

1.形參、函數定義或變量定義名稱衝突時,優先級:函數定義 > 形參 > 變量定義
2.arguments這個對象其實也是在環境記錄中的
3.函數表達式是在執行到函數表達式這一句代碼時才建立函數對象,而函數定義是在代碼初始化時就建立了函數對象,保存了當前做用域。

詞法環境可能發生變化的栗子

with

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-catch

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函數建立後都保存了當前的詞法環境。

閉包的應用

閉包最大的做用有兩個,一個是能夠讀取函數內部的變量,另外一個是使這些變量始終保存在內存中。

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; i<nodes.length; i++){
    nodes[i].onclick = function(){
      alert(i);
    }
  }
}

jsfiddle本身試試

試過會發現:每次彈出的都是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);
  }
}

jsfiddle裏置幾試試

若是仍是沒法理解,那麼咱們再來把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'));

注意,外層函數每次運行,都會生成一個新的閉包,而這個閉包又會保留外層函數的內部變量,因此內存消耗很大。所以不能濫用閉包,不然會形成網頁的性能問題。

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和方法setNameaddCourse實際上是全部實例對象能夠共用的,那麼如何實現呢?這就引出了原型。

原型

原型是構造器對象的一個屬性,叫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");

原型鏈如圖所示:
能夠看到實例對象billtom都有一個隱式的指針指向了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

使用ES5中的原型繼承構造一個有原型的實例對象: 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中提供的一種新的原型繼承的方式。原型鏈:

圖片描述

JS面向對象

全局變量很容易衝突,因此須要作信息隱藏。最好的方式就是封裝:將一部分信息開放出來,另一部分隱藏起來。

封裝

Java中有private, protected, public關鍵字來定義屬性和方法的訪問權限。但JS中並無,那麼JS中是如何實現封裝的呢?

按照咱們原來的寫法,像下圖這樣是沒有實現封裝的:

圖片描述

經過JS語言的特性作相似的模擬。像下圖這樣是能夠實現封裝的:

圖片描述

模擬private:在function A(){}內部經過var 定義了一個屬性_config,在外部沒法被直接訪問和修改,而後提供了一個函數this.getConfig()來讓咱們能夠在外部獲取這個屬性。這樣一來就模擬了private的特性。

模擬protected:對於protectedpublic,在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);
})();

編程風格

  1. 縮進:2個空格。
  2. 循環和判斷的代碼體老是使用大括號。
  3. 圓括號。表示函數調用時,函數名與左括號間不加空格;表示函數定義時,函數名與與左括號間不加空格;其餘狀況,前面位置的語法元素與左括號之間,都加空格。
  4. 行尾的分號建議不要省略。
  5. 全局變量儘可能減小使用。不得不用時建議用大寫字母。
  6. 變量聲明。由於JS有變量聲明提高,因此儘可能將全部變量聲明在代碼塊頭部。
  7. new 命令。用 new 命令從構造函數生成一個新對象時,若是忘了加上new,新對象內部 this 關鍵字就會指向全局對象,致使全部綁定在 this 上的變量都變成全局變量。所以,建議使用Object.create()命令替代 new 命令。
  8. with語句儘可能不要使用。
  9. 不要使用相等 == 運算符,只使用嚴格相等 === 運算符。
  10. 語句的合併。建議不要將不一樣目的的語句合併到同一行。
  11. 儘可能用 += 代替 ++ 。好比:用 x += 1; 代替 ++x;
  12. switch ... case 每個 case 後都要加 break 語句,這樣形成代碼冗長。並且,使得代碼結構混亂。建議避免使用,改寫成對象結構。
function doAction(action) {
    var actions = {
        'hack': function() {
            return 'hack';
        },
        'slash': function() {
            return 'slash';
        },
        ...
    }
}
相關文章
相關標籤/搜索