請聽題,請在評論區或在心中給出如下代碼的執行結果:ajax
var arr = [];for(var i = 0; i < 3; i++) { arr.push(function() {console.log(i)});}arr[0]();arr[1]();arr[2]();12345678
parseInt("678tianlangstudio");+"678tianlangstudio";1 + "678tianlangstudio";123
寫好答案了嗎?要公佈答案了哦數據庫
0編程
1json
2windows
NaN數組
678tianlangstudio瀏覽器
1NaN閉包
若是你的答案跟上面的任意一個匹配上了,那恭喜你! 能夠一塊兒往下面看了,由於這是一份全錯的答案。app
想起上高中時有次英語老師拿了張考卷對着答案講了半天,而後對咱們說:異步
這是B卷當作A卷的答案了,咱們從頭講。
今後我就再不相信什麼答案了,甚至遇到作不出的題我都懷疑是否是題出錯了!慢慢的養成了獨立思考的習慣,不知是好是壞。感謝老師苦心一片,至今教誨良言時猶在耳:
JavaScript是動態類型語言,也就是說在代碼編寫時不須要聲明指定其類型,變量類型在代碼運行時才肯定.
沒有方法、不可變
包括:
可使用typeof
判斷數據類型:
typeof "Bill" // 返回 "string"typeof 3.14 // 返回 "number"typeof true // 返回 "boolean"typeof false // 返回 "boolean"typeof x // 返回 "undefined" (假如 x 沒有值)typeof {name:'Bill', age:62} // 返回 "object"typeof [1,2,3,4] // 返回 "object" typeof null // 返回 "object"typeof function myFunc(){} // 返回 "function"123456789
須要注意:
typeof null
返回object
歷史緣由兼容原來的版本
typeof arr
返回 object
數組也是對象
typeof function testFun() {}
返回 function
數據從一個類型轉換到另外一個類型
強制就是你得本身編寫代碼轉換數據類型,隱式的就是有執行引擎幫你轉換.
const x = 17;const explicit = String(x); //使用強制類型轉換把數字x轉換爲字符串explicit const implicit = x + ""; //引擎看到你要把個數字跟字符串相加,就幫你把數字轉爲字符串了.測試題最後一行相似.123
==
會把兩邊的數據轉換爲同一種類型再比較是否相等,也就是忽略數據類型,好比:
1 == '1';//true1
===
先判斷數據類型,數據類型不同就直接false, 不爲其類焉問其值, 好比:
1 === `1`; //false1
注意:
'false'
-> true
此處省略10000字
太多,由於除了上面的都是-
非基本數據類型都有關聯屬性和方法,好比:
Array.prototype.push()
String.prototype.toUpperCase()
注意:
String
是複雜類型,'tianlang'
這個是基本數據類型,有些時候它們行爲同樣,這是由於有自動轉化,專業俗語自動裝箱
. 例如:
1778.toString();//會報錯,由於基本類型沒有方法。這麼粗暴直接冒失的調用執行引擎也很差意思先裝個箱const x = 1788; x.toString(); //"1788" 斯文多了,自動裝箱成對應的複雜類型走起。x.__proto__;//Number...12345
基本數據類型對應的裝箱複雜類型:
String()
Number()
Boolean()
Object()
(Symbol())
Java裏也有這個概念,但JavaScript除了名字跟Java沒半點關係.
做用域就是變量生效的範圍
使用
var
定義的變量有效範圍是從定義開始到所在函數結束.
使用
const
,let
定義的變量有效範圍是從定義開始到所在塊結束.
這個須要先說下程序執行過程:
執行引擎讀取整個程序腳步
解析判斷是否有語法錯誤,若是有錯誤報錯退出執行
把函數保存到內存中
聲明使用var定義的變量(注意:只有聲明沒有初始化賦值)
…
這就是爲何,咱們能夠先調用一個函數後對這個函數進行定義,能夠先使用一個使用var定義的變量,後面才使用var定義變量而不會報錯。由於再執行時把函數定義的代碼和var定義提高了.注意看3,4.
能夠把全局對象想象成一顆大樹,在程序中定義的變量也好函數也好其實都掛在一個全局對象上。
在瀏覽器運行環境中,全局對象是
window
在Node.js運行環境中,全局對象是
global
咋一看,不知道是作什麼的,再一看仍是不能從名字看出這貨具體是作什麼的.看下定義:
小蒙怡情大蒙傷身,仍是看下代碼,回頭看咱們前面提到的測試題1:
var arr = [];for(var i = 0; i < 3; i++) { arr.push(function() {console.log(i)});}arr[0]();arr[1]();arr[2]();1234567
輸出的結果是:
3
3
3
意不意外?
要一次性理解這個可能有點難,咱們先來個簡單的:
function makeHelloFunction() { var message = 'Hello!' function sayHello() { console.log(message) } return sayHello}const sayHello = makeHelloFunction()console.log('typeof message:', typeof message) //undefind 由於message是定義在函數makeHelloFunction內部的,函數外邊是不能訪問的.// but the function sayHello still references a variable called messagesayHello() //sayHello方法卻能夠打印出message的值,由於sayHello函數是定義在makeHelloFunction內部的.123456789101112131415
看了上面的例子是否是對這貨爲何叫閉包
有了些許的感悟。由於ES6
前只能使用var
定義變量,而這貨定義的變量做用域原本就比較寬還有定義提高就更容易形成變量的做用域污染了.
什麼是變量的做用域污染呢?
你定義了個變量name叫張三, 你的同事或者你在其它地方無心識的又定義了name叫李四,後來就變成了你覺得的張三不知道怎麼就變成了李四了.
爲了定義新變量name叫李四時不影響原來的張三,因而咱們能夠把李四關起來定義,關起來也就是封閉
起來。就像上面的演示代碼,只有在函數中定義的函數sayHello
才能訪問到函數中定義的變量message
,message對外部是不可見的,也就不會影響外部原來定義的變量.
上例中使用sayHello時還須要先調用makeHelloFunction建立,若是每次都這樣豈不是挺麻煩的?
咱們可使用當即執行函數定義方式,就是來個小括號,後面的小括號裏還能傳遞參數.就項這個樣子:
const sayHello = (function makeHelloFunction() {//這裏的函數名makeHelloFunction沒有用了,能夠刪除掉 var message = 'Hello!' function sayHello() { console.log(message) } return sayHello})()console.log('typeof message:', typeof message) //undefind 由於message是定義在函數makeHelloFunction內部的,函數外邊是不能訪問的.// but the function sayHello still references a variable called messagesayHello() //sayHello方法卻能夠打印出message的值,由於sayHello函數是定義在makeHelloFunction內部的.1234567891011121314
到此是否是還不知道爲啥子測試1裏輸出的是3,3,3而不是0,1,2? 不要緊,能夠先讓它輸出0,1,2.
var arr = [];for(var i = 0; i < 3; i++) { arr.push((function(j) { return function() { console.log(j) } })(i));//這裏咱們用了閉包和當即執行函數捕獲當前變量i}arr[0]();arr[1]();arr[2]();1234567891011
這下明白了吧,能夠運行下這段代碼看下效果,若是仍是不明白也能夠加羣討論.
那何時, 人分三流九等,士農工商。可是人生而平等嘛,怎麼體現平等呢?基本的權利你們應該都有吧.就像函數,雖然長得跟普通對象啊數字啊不同,可是收到的待遇倒是差很少地。能夠定義變量把一個函數賦值給它,也能夠把函數作爲另外一個函數的參數使用,這個就厲害了,能夠實現不少高級的功能.也稱這種參數是函數的函數爲高階函數
.
JavaScript是單線程同步執行的語言.
若是一個函數執行的時間比較長就會引發頁面的卡頓,好比在運行個這樣的函數,再去點擊頁面裏的按鈕你會發現沒得反應了:
function hang(seconds = 5) { const doneAt = Date.now() + seconds * 1000 while(Date.now() < doneAt) {}}
可是有些函數是能夠異步執行的,好比:
是否是很好奇JavaScript怎麼便是同步單線程的語言又支持異步呢?這主要是內部維護了一個任務隊列,若是關於這塊您有什麼想法也能夠給加羣給你們分享.
原來處理異步代碼的方式是添加回調函數,異步代碼執行完成後會觸發回調函數。好比這樣:
function login(req, res, callback) { User.findOne({email: req.body.email}, function(err, user) { if (err) return callback(err) user.comparePassword(req.body.password, (err, isMatch) => { if (err) return callback(err) if (!isMatch) return res.status(401).send('Incorrect password') // add relevant data to token const payload = {id: user._id, email: user.email} jwt.sign(payload, config.secret, {}, function(err, token) { if (err) return callback(err) user.token = token user.save((err) => { if (err) return callback(err) res.json({token}) }) }) }) })}1234567891011121314151617181920212223
異步函數多了,咱們須要一層一層的嵌套回調函數,就成了回調黑洞,這樣的代碼讀起來麻煩,維護起來也麻煩,一不當心就不知道哪裏少敲了個括號. 因而就引入了Promise編程模型,減小回調嵌套,就像這個樣子:
fetch(url) .then(function(res) { return res.json() }) .then(function(json) { return ({ importantData: json.importantData, }) }) .then(function(data) { console.log(data) }) .catch(function(err) { // handle error })123456789101112131415
是否是清爽了不少?
後來ES2017又新增了async/await關鍵字用於支持異步編程,就像這樣:
async function login(req, res, callback) { try { const user = await User.findOne({email: req.body.email}) const isMatch = await user.comparePassword(req.body.password) if (!isMatch) return res.status(401).send('Incorrect password') const payload = {id: user._id, email: user.email} const token = await jwt.sign(payload, config.secret, {}) user.token = token const success = await user.save() res.json({token}) } catch (err) { callback(err) }}123456789101112131415161718
是否是跟Rust Async有點像? 學語言嘛,這也是爲何我老是勸新同窗要學一門語言學通再學其它的。語言嘛總有相通之處,雖不能一通百通但也是有大量可複用之處的.
初接觸Javascript會以爲this
真是飄忽不定,特別是在事件處理時使用到this
,經常搞不清它這個
糾結指的是那個
;
這裏總結幾條規則:
函數中的this
指向函數的調用時所在對象,如:
obj.fun();// fun中的this指向boj
若是沒有對象那在嚴格模式下this
就指向全局對象windows
或者global
可使用bind
,call
, apply
顯示綁定this
到某個對象.
該煮飯了,有時間再單獨寫篇this
,歡迎關注