在js中,一對{}
其實就是一個對象vue
var person = { name: "tom", age: 23, read: function () { console.log(name, ": read book") } }
經過系統的構造函數建立一個空的對象,而後用js動態語言的特性,若是一個對象沒有某個屬性或者方法,那麼咱們點一下再附上值就行了程序員
var person2 = new Object() person2.name = "jerry" person2.age = 23 person2.say = function () { console.log(person2.name, ": say hello") }
自定義構造方法通常都是首字母大寫的函數瀏覽器
function Person(name, age, sex) { this.name = name this.age = age this.sex = sex this.say = function () { console.log(this.name, " :say hello") } } // 建立對象時,使用 new 關鍵字 p = new Person("tom", 23, "man") console.log(p instanceof Person)
自定義的構造方法建立對象,會經歷以下幾個步驟函數
function Person(name,age,sex) { // new Object 做爲當前的返回值 var obj = new Object() obj.name = name obj.age = age obj.sex = sex obj.say = function () { console.log(this.name," :say hello") } // 手動將對象返回出去 return obj } // 工廠模式建立對象,不須要使用new 關鍵字 var p = Person("tom",23,"man") console.log(p instanceof Person) // false
看下面的例子:this
// 構造函數和實例的關係 function Person(name) { this.name = name this.say = function () { console.log(this.name," :say hello") } } // 對象p是經過 自定義的構造函數Person建立出來的 var p = new Person("tom") console.dir(p) console.dir(Person)
打印的結果以下:prototype
__proto__
屬性中有constructor屬性,上面記錄着本身的構造方法。p是Person類型
。__prototype__實際上就是原型對象,在下文中會詳細的說code
仍是上面的例子,看以下的輸出也就能理解了對象
console.log(p.constructor === Person) // true console.log(p.__proto__.constructor == Person) // true console.log(p.__proto__.constructor == Person.prototype.constructor) // true // 由此推斷出,p === Person console.log(p instanceof Person) // true
其實有個小問題,看上面代碼的第一行console.log(p.constructor === Person)
咱們經過上面的代碼也看不到實例對象p constructor屬性啊,怎麼就能用,也不報錯undefined呢?blog
其實這就是牽扯到js對象的原型鏈了,(下面的章節會說),總的來講,就是js的對象會優先使用構造方法中的屬性和方法,若是構造函數中不存在咱們使用的屬性和方法的話,就嘗試去這個對象所對應的構造方法中的原型對象中的屬性和方法,再沒有就會報錯。事件
爲何會忽然再來看js的原型呢?
由於看到了vue的源碼中,大量的方法都被添加再vm的原型上,因此,回顧一下原型確定是躲不過去了。
通常咱們使用原型就是爲了節省空間。
想理解節省了什麼空間? 那就看看下面這個不節省空間的例子。
// 構造函數建立對象帶來的問題 function Person(name) { this.name = name this.say = function () { console.log(this.name,": say hello") } } var p1 = new Person("tom") var p2 = new Person("jerry") p1.say() // tom : say hello p2.say() // jerry : say hello // todo 返回false, 表示說,p1和p2的say方法,並非同一份, 其實這並非一件好事 console.log(p1.say == p2.say)
上面的p1 和 p2 都是經過一個構造函數建立出來的不一樣對象,他們裏面都有say這個函數,當咱們輸出 p1.say == p2.say
時,返回了false,說明每一個對象中都有一份say方法,那假設有1000個對象,豈不是就有1000個say方法了? 這確定是浪費空間的。
那麼有沒有辦法可讓每次new出來的對象都使用一份say方法呢?
固然,以下:
// 共享函數,引出原型 function Say() { console.log(this.name, ": say hellp") } function Person(name) { this.name = name this.say = Say } var p1 = new Person("tom") var p2 = new Person("jerry") p1.say()// tom : say hellp p2.say()// jerry : say hellp // 這樣的話,確實能實現節省空間,可是容易出問題 console.log(p1.say == p2.say) // ture
如今確實實現了咱們的需求,可是不夠優雅,並且統一出現問題,js是動態類型的語言,那咱們像下面這樣,假設不知道已經有Say這個函數了,而後將var Say = "hello"
放置在第Say函數以後,就會產生覆蓋。
看下的例子:咱們往構造方法的原型對象上添加一個say方法。
其實這塊也不是很差理解,你想啊,js的對象經過構造方法建立出來,咱們把公共的方法,屬性放在構造方法的原型對象中,是否是就可讓他們共享這些方法和屬性呢?
function Person(name) { this.name = name } // 在原型上添加方法 // 爲何能夠說原型是對象呢? 想一想js中一個對象能夠經過 點 , 動態點添加屬性和方法? Person.prototype.say = function () { console.log(this.name,":say hello") } var p1 = new Person("tom") var p2 = new Person("jerry") p1.say()//tom :say hello p2.say()//jerry :say hello console.log(p1.say == p2.say) // true
經過console.dir()
打印下上面的實例對象和構造函數,獲得以下圖:
console.dir(p1) console.dir(p2) console.dir(Person)
經過上圖能夠看到,能夠獲得下面的結論:
__proto__
, 這個屬性是用來給瀏覽器使用的,而不是給程序員使用,因此咱們稱它爲非標準屬性。 此外谷歌瀏覽器是支持這個屬性的,可是在IE8瀏覽器中,咱們執行這句console.log(p1.__proto__)
會報錯,說undefined實例對象是經過 new 構造函數建立出來的,因此構造函數是建立實例對象的模版。
構造函數就是那個首字母大寫的函數,在js裏面咱們能直接console.log(構造函數)
由於這個構造函數其實也是個對象。
原型的做用咱們說了,就是爲了將公共的方法抽取出來,所有存放在構造函數的原型對象中,而實現數據的共享,節省內存。
實例對象的·__proto__
, 是個非標準屬性,也是個對象,這個對象指向了 構造方法的prototype
屬性。
構造方法的 prototype
屬性是個標準屬性, 同時也是個對象,咱們對經過 構造方法.prototype.屬性/方法 = XXX
的方式爲其添加屬性和方法。
咱們經過 對象.屬性/方法
時, 會優先從對象的構造方法中查找,若是找不到的會再嘗試從原型中查找,這也是爲何會出現一個明明沒有爲一個對象添加相應的屬性或者方法可是對象卻能點出來,而且能正常使用。固然若是原型中也不存在的話,就會報錯說 undefined
下面出現的this並不難理解, 就是咱們new 出來的對象自己
function Person(name) { // 考慮一下,這個this是誰? this.name = name console.log(this) } var p = new Person("tom")
咱們在構造方法的原型對象上添加一個方法say,在這say方法中使用的this對象指的一樣是 咱們new 出來的對象自己。即方法的調用者。
function Person(name) { // 考慮一下,這個this是誰? this.name = name console.log("n10: ",this) } Person.prototype.say = function () { // todo 這裏的this指的是誰呢? // 首先,方法是添加在原型對象上, 那麼this指的是原型對象嗎? // 經過控制檯能夠看到,this.name ,其實不是原型對象,而是say()方法的調用者(實例對象) console.log("n16: ",this.name,": say hello") } var p1 = new Person("tom") var p2 = new Person("jerry") p1.say() p2.say()
下面在給構造方法的原型對象添加方法時,不只出現了this, 還出現了that。
this對象依然是咱們手動new出來的對象自己。
that一樣是指向了咱們new出來的對象自己,之因此須要中轉一下,是由於在按鈕的點擊事件裏面,this指向的是按鈕自己。
// 用面向對象的方式封裝構造函數 function ChangeStyle(btnId, dvId, color) { this.btnObj = document.getElementById(btnId) this.dv = document.getElementById(dvId) this.color = color } // 在構造方法的原型上添加方法 ChangeStyle.prototype.init = function () { // 這裏面的this表示的是 調用init方法的實例對象 var that = this this.btnObj.onclick = function () { // todo 爲何原型中的函數中,就不能使用this,而是that呢??? // todo 或者問下,當前函數中的this是誰呢? that.dv.style.backgroundColor = that.color } }
function Person(name) { this.name = name } // 前面的例子中咱們都是像下面這樣寫代碼, 這實際上是對原來的 原型對象屬性的累加 // 原來的原型對象中有個屬性,叫作consturctor Person.prototype.say = function(){ //todo }
這樣設置原型的話,其實是對原來的原型對象的覆蓋,因此說須要像下面這樣從新添加constructor的指向。
固然我也試了一下,若是說覆蓋原來的原型對象,且不添加contructor的指向,咱們使用 instanceof 判斷實例對象是不是對應的構造函數類型時,仍是能獲得正確的結果。
Person.prototype = { constructor:Person, // 手動修改構造器的指向 height:"20", weight:"20", say:function () { // todo } }
function Person(name) { this.name = name this.say = function () { console.log("say") // 經過這個例子,能夠看到,對象的方法中能夠直接調用對象的方法 this.eat() } this.eat = function () { console.log("eat") } }
function Person(name) { this.name = name } Person.prototype.say = function(){ console.log("say") // 原型中的方法也能夠相互訪問 this.eat() } Person.prototype.eat = function(){ console.log("eat") } var p1 = new Person("tom") p1.say()
像這樣就能夠實現對原型中的方法進行覆蓋的操做。
固然能夠經過在原型上添加方法實現對原有封裝類的拓展。
// 在現有的js封裝類上幹這件事,也算是在修改源碼 String.prototype.myReverse = function () { for (var i = 0; i < this.length; i++) { console.log("發生倒敘") } } var str = "123" str.myReverse()