整理 javascript
中一些類似的關鍵字、方法、概念。javascript
varjava
//a. 變量提高 console.log(a) // => undefined var a = 123 //b. 做用域 function f() { var a = 123 console.log(a) // => 123 } console.log(a) // => a is not defined for (var i = 0; i < 10; i ++) {} console.log(i) // => 10
let面試
//a. 變量不提高 console.log(a) // => a is not defined let a = 123 //b. 做用域爲所在代碼塊內 for (let i = 0; i < 10; i ++) {} console.log(i) // => i is not defined
constajax
//a. 不能修改的是棧內存在的值和地址 const a = 10 a = 20 // => Assignment to constant variable // 可是如下的賦值確是合法的 const a = { b: 20 } a.b = 30 console.log(a.b) // => 30
function編程
//a. 函數提高 fn() // => 123 function fn() { return 123 } //b. 做用域 function fn() { function fn1 () { return 123456 } fn1() // => 123456 } fn1() // => fn1 is not defined
var a = 1 function fn() { if (!a) { var a = 123 } console.log(a) } fn() ?
// 如何依次打印出0 - 9數組
for (var i = 0; i < 10; i++) { setTimeout(function(){ console.log(i) }) }
function Foo() { getName = function(){ console.log("1"); }; return this; } Foo.getName = function() { console.log("2"); }; Foo.prototype.getName = function(){ console.log("3"); }; var getName = function() { console.log("4"); } function getName(){ console.log("5"); } Foo.getName(); ? getName(); ? Foo().getName(); ? getName(); ? new Foo.getName(); ? new Foo().getName(); ?
答案:
第一題promise
//咱們把它執行順序整理下 var a = 1 function fn() { var a = nudefined if (!a) { var a = 123 } console.log(a) } //因此 答案很明顯 就是 123
第2題服務器
for (var i = 0; i < 10; i++) { print(i) } function print(i) { // 把每一個變量i值傳進來,變成只可當前做用域訪問的局部變量 setTimeout(function(){ console.log(i) }) } // 或者自執行函數簡寫 for (var i = 0; i < 10; i++) { (function(i){ setTimeout(function(){ console.log(i) }) })(i) }
第3題babel
// 咱們整理下它的執行順序 var getName = undefined function Foo() { getName = function(){ console.log("1"); }; return this; } function getName(){ console.log("5"); } Foo.getName = function() { console.log("2"); }; Foo.prototype.getName = function(){ console.log("3"); }; getName = function() { console.log("4"); } Foo.getName(); // 2 /* 函數也是對象, Foo.getName 至關於給 Foo這個對象添加了一個靜態方法 getName,咱們調用的實際上是這個靜態方法,並非調用的咱們實例化的 getName */ getName(); // 4 /* 按照上面的執行順序,其實這個就很好理解了,由於 `getName = function() { console.log("4"); }` 是最後一個賦值, 執行的應該是這個函數 */ Foo().getName(); // 1 /* 這裏爲何是 1 而不是咱們想象的 3 呢? 問題就是出在 調用的是 Foo(); 並無使用 new 這個關鍵字,因此那時候返回的 this 指向的並非 Foo, 而是 window; 至於爲何不用 new 返回的 this 不指向 Foo, 這個隨便去哪查一下就好, 就不在這介紹了 */ getName(); // 1 /* 這裏爲何也是1 呢? 其實緣由就是 上面咱們調用了 `Foo().getName();` 這個方法引發的, 由於咱們執行了 Foo 函數, 觸發了 getName = function(){ console.log("1"); } 這段代碼, 並且並無在Foo裏面聲明 getName 變量, 因而就一直往上查找, 找到外部的 getName 變量 並賦值給它. 因此這裏調用 getName() 方法時, 它的值已經變成 getName = function(){ console.log("1"); } 了 */ new Foo.getName(); // 2 /*這個時候仍是沒有實例化, 調用的仍是它的靜態方法*/ new Foo().getName(); // 3 /*由於實例化了,因此調的是原型上的方法*/
我記得看到過幾個經典的例子,找了半天沒找到, 暫時就這些吧.。併發
== 轉換規則
// 不一樣類型,相同值 var a = 1 var b = '1' console.log(a == b) // => true console.log(a === b) // => false // 對象和字符串 console.log([1,2,3] == '1,2,3') // => true 由於 [1,2,3]調用了 toString()方法進行轉換 // 對象和布爾 console.log([] == true) // => false []轉換爲字符串'',而後轉換爲數字0, true 轉換成1 // 對象和數字 console.log(['1'] == 1) // => true []轉換爲字符串'1' console.log(2 == {valueOf: function(){return 2}}) // => true 調用了 valueOf()方法進行轉換 // null, undefined 不會進行類型轉換, 但它們倆相等 console.log(null == 1) // => false console.log(null == 0) // => false console.log(undefined == 1) // => false console.log(undefined == 0) // => false console.log(null == false) // => false console.log(undefined == false) // => false console.log(null == undefined) // => true console.log(null === undefined) // => false // NaN 跟任何東西都不相等(包括本身) console.log(NaN == NaN) // => false console.log(NaN === NaN) // => false
下面幾張圖表示這些 == === 的關係
==
===
全部對象繼承了這兩個轉換方法toString
: 返回一個反映這個對象的字符串valueOf
: 返回它相應的原始值
toString
var arr = [1,2,3] var obj = { a: 1, b: 2 } console.log(arr.toString()) // => 1,2,3 console.log(obj.toString()) // => [object Object] // 那咱們修改一下它原型上的 toString 方法呢 Array.prototype.toString = function(){ return 123 } Object.prototype.toString = function(){ return 456 } console.log(arr.toString()) // => 123 console.log(obj.toString()) // => 456 // 咱們看下其他類型轉換出來的結果, 基本都是轉換成了字符串 console.log((new Date).toString()) // => Mon Feb 05 2018 17:45:47 GMT+0800 (中國標準時間) console.log(/\d+/g.toString()) // => "/\d+/g" console.log((new RegExp('asdad', 'ig')).toString()) // => "/asdad/gi" console.log(true.toString()) // => "true" console.log(false.toString()) // => "false" console.log(function(){console.log(1)}.toString()) // => "function (){console.log(1)}" console.log(Math.random().toString()) // => "0.2609205380591437"
valueOf
var arr = [1,2,3] var obj = { a: 1, b: 2 } console.log(arr.valueOf()) // => [1, 2, 3] console.log(obj.valueOf()) // => {a: 1, b: 2} // 證實valueOf返回的是自身的原始值 // 一樣咱們修改下 valueOf 方法 Array.prototype.valueOf = function(){ return 123 } Object.prototype.valueOf = function(){ return 456 } console.log(arr.valueOf()) // => 123 console.log(obj.valueOf()) // => 456 // valueOf轉化出來的基本都是原始值,複雜數據類型Object返回都是自己,除了Date 返回的是時間戳 console.log((new Date).valueOf()) // => 1517824550394 //返回的並非字符串的世界時間了,而是時間戳 console.log(/\d+/g.valueOf()) // => 456 當咱們不設置時valueOf時,正常返回的正則表式自己:/\d+/g,只是咱們設置了 Object.prototype.valueOf 因此返回的時:456 console.log(Math.valueOf()) // => 456 同上 console.log(function(){console.log(1)}.valueOf()) // => 456 同上
var a = { toString: function() { console.log('你調用了a的toString函數') return 8 } } console.log( ++a) // 你調用了a的toString函數 // 9 // 當你設置了 toString 方法, 沒有設置 valueOf 方法時,會調用toString方法,無視valueOf方法
var a = { num: 10, toString: function() { console.log('你調用了a的toString函數') return 8 }, valueOf: function() { console.log('你調用了a的valueOf函數') return this.num } } console.log( ++a) // 你調用了a的valueOf函數 // 11 // 而當你二者都設置了的時候,會優先取valueOf方法, 不會執行toString方法
若是以 「||」 和 「&&」 作條件判斷的話
「&&」 必需要全部條件都爲 true 才能知足條件
var a = true,b = false, c = true, d = false var str = 'none' if (b || d || a) { str = '如今是 ||' } console.log(str) // => '如今是 ||' ,由於其中a爲true全部知足條件 var str = 'none' if (b || d ) { str = '如今是 ||' } console.log(str) // => 'none' ,由於b,d都是false, 不知足條件 var str = 'none' if (a && c && d) { str = '如今是 &&' } console.log(str) // => 'none' ,由於d是false, 其中有一個false就不知足條件 var str = 'none' if (a && c) { str = '如今是 &&' } console.log(str) // => '如今是 &&' ,由於b,d都是true, 知足條件
短路原理:
||(或):
1.只要「||」前面是true,結果會返回「||」前面的值
2.若是「||」前面是false,結果都會「||」返回後面的值
var a = true,b = false, c = true, d = false var str = 'none' if (b || d || a) { str = '如今是 ||' } console.log(str) // => '如今是 ||' ,由於其中a爲true全部知足條件 var str = 'none' if (b || d ) { str = '如今是 ||' } console.log(str) // => 'none' ,由於b,d都是false, 不知足條件 var str = 'none' if (a && c && d) { str = '如今是 &&' } console.log(str) // => 'none' ,由於d是false, 其中有一個false就不知足條件 var str = 'none' if (a && c) { str = '如今是 &&' } console.log(str) // => '如今是 &&' ,由於b,d都是true, 知足條件
&&(與):
1.只要「&&」前面是false,不管「&&」後面是true仍是false,結果都將返「&&」前面的值
2.只要「&&」前面是true,不管「&&」後面是true仍是false,結果都將返「&&」後面的值
var a = false, b = true console.log(a && b) // => false 只要「&&」前面是false,不管「&&」後面是true仍是false,結果都將返「&&」前面的值 console.log(b && a) // => false 只要「&&」前面是true,不管「&&」後面是true仍是false,結果都將返「&&」後面的值
var name = '小剛' var person = { name: '小明', fn: function() { console.log(this.name + '擼代碼') } } person.fn() // => 小明擼代碼 // 如何把它變成 「小剛擼代碼」 呢? // 咱們能夠用 call/bind/apply 分別來實現 person.fn.call(window) // => 小剛擼代碼 person.fn.apply(window) // => 小剛擼代碼 person.fn.bind(window)() // => 小剛擼代碼
顯而易見,call 和 apply 更加相似,bind與二者形式不一樣
那 call 和 apply 的區別在哪呢?
obj.call(thisObj, arg1, arg2, ...) obj.apply(thisObj, [arg1, arg2, ...]) // 經過上面的參數咱們能夠看出, 它們之間的區別是apply接受的是數組參數,call接受的是連續參數。 // 因而咱們修改上面的函數來驗證它們的區別 var person = { name: '小明', fn: function(a,b) { if ({}.toString.call(a).slice(8, -1) === 'Array') { console.log(this.name+','+a.toString()+'擼代碼') }else{ console.log(this.name+','+a+','+b+'擼代碼') } } } person.fn.call(this, '小紅', '小黑' ) // => 小剛,小紅,小黑擼代碼 person.fn.apply(this, ['小李', '小謝']) // => 小剛,小李,小謝擼代碼
那麼bind 與call,apply有什麼區別呢 ?
與call和apply不一樣的是,bind綁定後不會當即執行。它只會將該函數的 this 指向肯定好,而後返回該函數
var name = "小紅" var obj = { name: '小明', fn: function(){ console.log('我是'+this.name) } } setTimeout(obj.fn, 1000) // => 我是小紅 // 咱們能夠用bind方法打印出 "我是小明" setTimeout(obj.fn.bind(obj), 1000) // => 我是小明 // 這個地方就不能用 call 或 apply 了, 否則咱們把函數剛一方去就執行了 // 注意: bind()函數是在 ECMA-262 第五版才被加入 // 因此 你想兼容低版本的話 ,得須要本身實現 bind 函數 Function.prototype.bind = function (oThis) { if (typeof this !== "function") { throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable"); } var aArgs = Array.prototype.slice.call(arguments, 1), fToBind = this, fNOP = function () {}, fBound = function () { return fToBind.apply( this instanceof fNOP && oThis ? this : oThis || window, aArgs.concat(Array.prototype.slice.call(arguments)) ); }; fNOP.prototype = this.prototype; fBound.prototype = new fNOP(); return fBound; };
這三個東西牽涉到的可能就是咱們最多見到的 「同步」、「異步」、「任務隊列」、「事件循環」 這幾個概念了
例:
var data; $.ajax({ ... success: function(data) { data = data } }) console.log(data)
當咱們從服務器獲取到數據的時候,爲何打印出來的是undefined ?
解決這個問題以前咱們先來了解javascript的運行環境
JavaScript是單線程語言,JS中全部的任務能夠分爲兩種:同步任務和異步任務。
如圖所示
由此,上面爲何會產生 undefined的緣由了, 由於ajax 是異步任務,而咱們console.log(data)是同步任務,因此先執行的同步任務,纔會去執行 ajax
說了這麼多,咱們來看下 爲何咱們很須要 從 callback
=> promise
=> async/await
由於不少時候咱們須要把一個異步任務的返回值,傳遞給下一個函數,並且有時候是連續的n個
callback
// 只有一個callback的時候 function fn(callback) { setTimeout(function(){ callback && callback() }, 1000) } fn(function(){ console.log(1) }) // 一旦咱們多幾個呢? function fn(a){ // 傳入a 返回a1 function fn1(a1){ function fn2(a2){ function fn3(a3){ console.log(a3) .... } } } } // 當項目一複雜,這滋味。。。
Promise
什麼是promise?
Promise是異步編程的一種解決方案,同時也是ES6的內置對象,它有三種狀態:
Promise方法
基本用法
let promise = new Promise( (resolve, reject) => { setTimeout(function(){ resolve(1) }, 1000) }) promise.then( res => { console.log(res)// 一秒以後打印1 })
咱們把上面的回調地獄轉換下
const fn = a => { return Promise.resolve(a) } const fn1 = a => { return Promise.resolve(a) } const fn2 = a => { // return Promise.resolve(a) return new Promise( (resolve, reject) => { setTimeout(function(){ resolve(a) },1000) }) } const fn3 = a => { // return Promise.resolve(a) return new Promise( (resolve, reject) => { setTimeout(function(){ resolve(a) },1000) }) } fn(123) .then(fn1) .then(fn2) .then(fn3) .then( res => { console.log(res) // => 123 })
這樣就簡單明瞭多了, 咱們就不須要一層一層嵌套callback了,能夠經過鏈式調用來解決callback的問題
然而,僅僅這樣仍是以爲不夠好
由於這種麪條式調用仍是讓人很不爽,並且 then 方法裏面雖然是按前後順序來的,可是其自己仍是異步的
看下面這段代碼
const promise = new Promise( (resolve, reject) => { setTimeout(function(){ resolve(222) }, 1000) }) console.log(111) promise.then( res => { console.log(res) }) console.log(333)
打印結果依然仍是 111 => 333 => 222, 並非咱們想象的 111 => 222 => 333
依然不適合單線程的思惟模式。因此下一個解決方案 又出現了
async/await
這是ES7的語法,固然,在如今這種工程化的時代,基本babel編譯以後也都是能在項目中引用的
基本用法跟規則
async 表示這是一個async函數,
await只能用在這個函數裏面。後面應該跟着是 Promise 對象, 不跟的話也不要緊, 可是await就不會在這裏等待了
await 表示在這裏等待promise返回結果
例:
const fn = () => { return new Promise( (resolve, reject) => { setTimeout(function(){ resolve(222) }, 1000) }) } (async function(){ console.log(111) let data = await fn() console.log(data) console.log(333) })() // 是否是返回 111 => 222 => 333 了呢 // 咱們來試下返回別的東西, 不返回 promise const fn = () => { setTimeout(function(){ console.log(222) }, 1000) } (async function(){ console.log(111) let data = await fn() console.log(data) console.log(333) })() // 打印結果: 111 => undefined => 333 => 222 // 當咱們不是在await 關鍵字後面返回的不是 promise 對象時, 它就不會在原地等待 promise執行完再執行, 而是向正常的JS同樣執行,把異步任務跳過去
await
關鍵字必須包裹在 async
函數裏面,並且async
函數必須是它的父函數
const fn = () => { let promise = new Promise( (resolve, reject) => { setTimeout(function(){ resolve(222) }, 1000) }) } // 這樣是不行的,會報錯,由於的await關鍵字的父函數不是 async 函數 const grand = async () => { return function parent() { let data = await fn() } } // 這樣才行,由於await 的父函數 是一個 async 函數 const grand = () => { return async function parent() { let data = await fn() } }
柯里化
函數柯里化就是對高階函數的降階處理。
柯里化簡單的說,就是把 n 個參數的函數,變成只接受一個參數的 n 個函數function(arg1,arg2)
變成function(arg1)(arg2)
function(arg1,arg2,arg3)
變成function(arg1)(arg2)(arg3)
function(arg1,arg2,arg3,arg4)
變成function(arg1)(arg2)(arg3)(arg4)
柯里化有什麼做用
例:
//求和 function add (a, b, c) { return a + b + c } add(1,2,3)
若是我只改變 c 的值,在求和add(1,2,4)
是否是得多出從新計算 a + b 的部分
咱們是否是能夠提早返回a+b的值, 而後只傳入 c 的值進行計算就好了
修改一下方法
function add (a, b) { return function (c) { return a + b + c } } var sum = add(1, 2) sum(3) sum(4)
在此基礎上咱們在作下修改
function add (a) { return function (b) { return function (c) { return a + b + c } } }
這樣咱們是否是能夠隨時複用某個參數,而且控制在某個階段提早返回
還有一個經典的例子
var addEvent = function(el, type, fn, capture) { if (window.addEventListener) { el.addEventListener(type, function(e) { fn.call(el, e); }, capture); } else if (window.attachEvent) { el.attachEvent("on" + type, function(e) { fn.call(el, e); }); } };
咱們每次調用事件時,都須要判斷兼容問題, 但咱們運用柯里化的方式就只要判斷一次就好了
var addEvent = (function(){ if (window.addEventListener) { return function(el, sType, fn, capture) { el.addEventListener(sType, function(e) { fn.call(el, e); }, (capture)); }; } else if (window.attachEvent) { return function(el, sType, fn, capture) { el.attachEvent("on" + sType, function(e) { fn.call(el, e); }); }; } })();
還有一個做用就是延遲計算
小明天天都會花一部分錢吃飯
小明想知道它5天以後總共會花費多少錢
var total = 0 var fn = function(num) { total += num } fn(50) fn(70) fn(60) fn(100) fn(80)
這樣咱們便能算出它總共花了都少錢
可是小明又忽然想知道 若是他天天花費的的錢翻一倍 會產生多少錢
因而咱們是否是得改下 上面的 函數
var fn = function(num) { total += num*2 } fn(50) fn(70) fn(60) fn(100) fn(80)
那咱們是否是有什麼辦法,先把這些數 存起來,到最後在進行計算
咱們接着來封裝
var curry = function(fn) { var args = [] return function() { if (arguments.length === 0) { return fn.apply(null, args) }else{ args = args.concat([].slice.call(arguments)) return curry.call(null, fn, args) } } } var curryFn = function() { var args = [].slice.call(arguments), total = 0 for (var i = 0; i < args.length; i++) { total += args[i] } return total } var fn = curry(curryFn) fn(50) fn(70) fn(60) fn(100) fn(80) fn() //不傳參數的時候進行計算
這樣咱們只有最後的時候才進行計算。
並且只須要修改 curryFn 裏面的計算方法就行
咱們整理下上面的方法封裝完整的柯里化函數
var curry = function (fn, length) { length = length || fn.length; var sub_curry = function (f) { var args = [].slice.call(arguments, 1); return function () { return f.apply(null, args.concat([].slice.call(arguments))) } } return function () { var args = [].slice.call(arguments); if (length > args.length) { var newArgs = [fn].concat(args); return curry(sub_curry.apply(null,newArgs), length - args.length) }else{ fn.apply(null,arguments) } } }
// 1. var fn = curry( function(a,b,c){ console.log(a, b, c) }) fn('a')('b')('c') // 2. fn1 = curry(function(){ console.log(arguments) }, 3) fn1('a')('b')('c')
反柯里化
反柯里化的做用在與擴大函數的適用性,使原本做爲特定對象所擁有的功能的函數能夠被任意對象所用.
被任意對象使用? 是否是想到了用call, apply 設置this指向
經過 call/apply 被任意對象所用
var obj = { a: 1, fn: function (b) { return this.a + b } } obj.fn(2) // 3 var obj1 = {a:4} obj.fn.call(obj1, 2) // 6
反柯里化版本
var uncurrying= function (fn) { return function () { var context=[].shift.call(arguments); return fn.apply(context,arguments); } } // const uncurrying = fn => (...args) => Function.prototype.call.apply(fn,args) // 簡潔版 var f = function (b) { return this.a + b } var uncurry = uncurrying(f) var obj = {a:1}, obj1 = {a:4} uncurry(obj, 2) // 3 uncurry(obj1, 2) // 3
相信你們已經看出區別了,這丫的就至關於一個外部的call方法
上面不少只是本身的部分理解,不必定準確。若是有不一樣理解,謝謝指出。