熟悉ES6語法的開發者,箭頭函數在涉及this綁定時的行爲和普通函數的行爲徹底不一致。跟普通this綁定規則不同,它使用了當前的詞法做用域覆蓋了this
原本的值。javascript
this
理解成指向函數自己,函數對象的屬性(Fun.X)不是this.X 【X】 this
指向函數的做用域 【X】 this
是運行時進行綁定的,而不是編寫時綁定的,它的執行上下文取決於函數調用時的各類條件,this
的綁定和函數聲明的位置沒有任何關係,只取決於函數的調用方式(調用位置),具備動態性,簡單地說,函數執行過程當中的調用位置決定了this的綁定對象。java
函數被調用時,會建立一個活動記錄,這個也稱做執行上下文,這個記錄包含了函數在哪裏被調用(調用棧)、函數調用方式、傳入的參數等信息,而this
就是執行上下文的一個屬性,函數調用時會被用到。數組
- 默認綁定,this指向全局對象(嚴格模式,不容許使用)
- 隱式綁定,會把函數調用的this綁定到所在的上下文對象,對於隱式綁定,經常伴隨着隱式丟失的問題,
函數別名
、傳入回調函數
、函數傳入內置函數
均會形成此問題- 顯式綁定,就是常見
call
、apply
方法,仍沒法解決綁定丟失問題,但有個變種方法,叫作硬綁定
└── 硬綁定,函數bar中強制綁定(顯示綁定)綁定foo在obj中執行安全
├── 包裹函數 ├── 輔助函數(bind基本實現原理,下面有詳細實現)
- new綁定,過程以下(發生在構造函數調用時):
一、建立(或者說構造)一個新對象
二、這個新對象會被執行[[Porototype]]鏈接
三、這個新對象會被綁定到函數調用的this
四、若是函數沒有返回其餘的對象,那麼new表達式的函數調用會自動返回新對象app
規則優先級:默認綁定 < 隱式綁定 < 顯式綁定 < new綁定函數
☆☆☆☆☆☆ 輔助函數與bind函數的實現 ☆☆☆☆☆☆this
function bind(fn, obj) { return function() { fn.apply(obj, arguments); } }
摘自MDN:prototype
if (!Function.prototype.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() { // this instanceof fBound === true時,說明返回的fBound被當作new的構造函數調用 // 關鍵代碼,若是是new調用,就是使用新建立的this替換硬綁定的this,說明了new綁定優先級大於硬綁定 return fToBind.apply(this instanceof fBound ? this : oThis, // 獲取調用時(fBound)的傳參.bind 返回的函數入參每每是這麼傳遞的 aArgs.concat(Array.prototype.slice.call(arguments))); }; // 維護原型關係 if (this.prototype) { fNOP.prototype = this.prototype; } // 下行的代碼使fBound.prototype是fNOP的實例,所以 // 返回的fBound若做爲new的構造函數,new生成的新對象做爲this傳入fBound,新對象的__proto__就是fNOP的實例 fBound.prototype = new fNOP(); return fBound; }; }
bind
輔助函數修改改變this的同時,可使用Object.create(null)
,如.call/.apply(Object.create(null), ...argument)
,這樣能夠有效防止修改全局對象。指針
null/undefined做爲this的綁定對象傳入call
、apply
,使用的是默認綁定規則。code
上述對硬綁定的介紹,會強制把this強制綁定到指定的對象上,防止了函數調用應用默認綁定規則,可是形成的問題就是,不夠靈活,使用不了隱式綁定和顯式綁定來修改this,於此同時,軟綁定
便出現了。
if (!Function.prototype.softBind) { Function.prototype.softBind = function(obj) { var fn = this; // 捕獲全部curried參數 var curried = [].slice.call(arguments, 1); var bound = function() { return fn.apply( (!this || this === (window || global)) ? obj : this, curried.concat.apply(curried, arguments) ); } bound.prototype = Object.create(fn.prototype); return bound; } }
軟綁定DEMO:
function foo() { console.log("name:" + this.name); } var obj = { name: "obj" }; var obj2 = { name: "obj2" }; var obj3 = { name: "obj3" }; var fooBj = foo.softBind(obj); fooBj(); // name: obj obj2.foo = foo.softBind(obj2); obj2.foo(); // name: obj2 fooBj.call(obj3); // name: obj3 setTimeout(obj2.foo, 10); // name: obj
基本類型:string、number、boolean、null、undefined、object
內置對象(也是內置函數): String、Number、Boolean、Object、Function、Array、Date、RegExp、Error
平常開發,你們可能會有疑問,好比var a = 'i am string'
中a
變量,它可使用a.length、a.charAt(0)
等屬性和方法。它原本是一個字符串,爲何會有相似對象的特性呢,這裏會涉及到一個基本包裝對象
,能夠怎麼理解這樣的一個概念性知識,一瞬間的引用類型,用完即毀,真正的引用類型會一直存在內存中,而基本包裝對象只會存在一瞬間。
衍生方法:typeof
、instanceof
、Object.prototype.toString.call
對象的內容是由一些存儲在特定命名位置的值組成的,叫作屬性(指針),有兩種訪問方式,myObject['a']
、myObject.a
,對象屬性同時也是無序的。
ES6可計算屬性名:
const prefix = 'a'; const myObject = { [prefix + 'bar']: "xxxx" }
記住,函數永遠不屬於一個對象,只是一個引用,只是函數的this,會由於隱性綁定,在上下文作一層綁定而已。
- 深複製,
JSON.parse(JSON.stringify(obj))
- 淺複製,
Object.assign(...)
,會遍歷一個或多個源對象的全部可枚舉的自由鍵到目標對象,源對象的一些特性(如writable)不會被複制
屬性描述符,即writable
、configuable
、enumerable
、value
得到屬性描述符,Object.getOwnPropertyDescriptor(myObject, 'a')
設置屬性描述符,Object.defineProperty(myObject, "a", {...})
configuable
配置爲false,不能使用delete關鍵字 enumerable
控制屬性是否可枚舉
訪問描述符,(Getter/Setter),能夠改寫默認的value
,
var myObject = { get a() { return 2; } } myObject.a // 2 Object.defineProperty(myObject, "b", { get: function() { return this.a * 2 // 指向當前對象 } }); myObject.b // 4
for...in
,遍歷對象自身及原型上可枚舉的屬性Object.keys()
,遍歷對象自身可枚舉的屬性Object.getOwnPropertyNames
,遍歷對象自身的屬性Object.getOwnPropertySymbol
,遍歷對象自身Symbol類型屬性Reflect.ownkeys
,遍歷對象自身的屬性(包含不可枚舉屬性,Symbol類型屬性)
若是你但願對象屬性或是對象是不可改變,能夠經過配置對象的屬性描述符來實現,可是這種不可變是一種淺不可變,若是對象中屬性的引用是對象、數組、函數,那麼它們是可變的,實現方式有以下:
- 對象常量,
writable:false
|configuable:false
- 禁止擴展,
Object.preventExtensions(obj)
- 密封,
Object.seal(obj)
調用禁止擴展,且不能從新配置,及刪除屬性,及configuable:false
- 凍結,
Object.freeze(obj)
,在密封的基礎上將writable:false
- in操做符,檢查屬性是否存在對象裏或是[[Prototype]]原型鏈上
Object.hasOwnProperty
,只會檢查對象問題:myObject.hasOwnProperty()可能會報錯,myObject多是
Object.create(null)
生成不帶[[Prototype]]原型鏈,而Object.hasOwnProperty
是由[[Prototype]]委託,因此能夠這樣,Object.prototype.hasOwnProperty.call(myObject, "a")
有坑:
4 in [2,4,6] // false 4 in [2,2,6,8,0] // true
判斷是否可枚舉 myObject.propertyIsEnumerable('a')
for...of
被訪問的對象請求一個迭代器對象,而後經過.next()方法遍歷全部返回來的值
數組內置有@@iterator
,因此能夠直接使用for...of
。
使用內置@@iterator
遍歷數組:
var myArray = [1,2,3]; var it = myArray[Symbol.iterator](); // 返回迭代器函數 it.next(); // { value: 1, done: false } it.next(); // { value: 2, done: false } it.next(); // { value: 3, done: false } it.next(); // { done: true }
普通對象不含有@@iterator
,沒法使用for...of
,能夠進行改造,
var myObject = { a: 2, b: 3 }; Object.defineProperty(myObject, Symbol.iterator, { enumerable: false, writable: false, configuabale: false, value: function() { var o = this; var idx = 0; var ks = Object.keys(o); return { next: function() { return { value: o[ks[idx++]], done: (idx > ks.length) }; } } } }); vat it = myObject[Symbol.iterator](); it.next(); // { value: 2, done: false } it.next(); // { value: 3, done: false } it.next(); // { done: true }