一. 創(chuàng)建對(duì)象
創(chuàng)建對(duì)象的四個(gè)步驟
- 創(chuàng)建一個(gè)新的對(duì)象
- 綁定作用域(將構(gòu)造函數(shù)的作用域綁定到對(duì)象上)
- 初始化對(duì)象(執(zhí)行構(gòu)造函數(shù)的代碼)
- 返回新對(duì)象
1. 工廠(chǎng)模式
function createPerson(name) {
let obj = new Object()
obj.name = name
obj.sayName = function () {
console.log(this.name)
}
reutrn obj
}
let p = createPerson('張三')
2. 構(gòu)造函數(shù)模式
function Person (name, age) {
this.name = name
this.sayName= function () {
console.log(this.name)
}
}
let p = new Person('張三')
3. 原型模式
function Person () {
}
Person.prototype.name = '張三'
Person.prototype.sayName = function () {
console.log(this.name)
}
4. 組合模式
構(gòu)造函數(shù)用于定義實(shí)例屬性惕橙,原型用于定義方法和共享屬性
function Person (name) {
this.name = name
}
Person.prototype = {
constructor: Person,
sayName () {
console.log(this.name)
}
}
5. 動(dòng)態(tài)原型模式
把所有信息都封裝在構(gòu)造函數(shù)中,在構(gòu)造函數(shù)中初始化原型
function Person (name) {
this.name = name
if (typeof this.sayName !== 'function') {
Person.prototype.sayName = function () {
console.log(this.name)
}
}
}
6. 寄生構(gòu)造函數(shù)模式
創(chuàng)建一個(gè)函數(shù),該函數(shù)封裝創(chuàng)建對(duì)象的代碼锐墙,然后返回新創(chuàng)建的對(duì)象佩番,除了使用new操作符外吗冤,這個(gè)模式跟工廠(chǎng)模式一模一樣
function Person(name) {
let obj = new Object()
obj.name = name
obj.sayName = function () {
console.log(this.name)
}
return obj
}
let p = new Person('張三')
7. 穩(wěn)妥構(gòu)造函數(shù)模式
沒(méi)有公共屬性缓艳,其方法也不引用this對(duì)象槽奕,不使用new操作符調(diào)用構(gòu)造函數(shù)
function Person (name) {
let obj = new Object()
obj.sayName = function () {
console.log(name)
}
return obj
}
let p = Person('張三')
二. 數(shù)據(jù)屬性與訪(fǎng)問(wèn)器屬性
1.數(shù)據(jù)屬性
數(shù)據(jù)屬性有4個(gè)特性嘴纺,configurable败晴,表示能否通過(guò)delete刪除,能否修改其特性栽渴。enumerable尖坤,表示能否通過(guò)for-in遍歷。writable闲擦,表示能否修改屬性的值慢味。value场梆,表示屬性的值
直接定義在對(duì)象上的屬性為數(shù)據(jù)屬性,它們的configurable纯路、enumerable及writable都為true或油,通過(guò)Object.defineProperty添加的數(shù)據(jù)屬性,以上三個(gè)值都為false
2.訪(fǎng)問(wèn)器屬性
訪(fǎng)問(wèn)器屬性有4個(gè)特性感昼,前兩個(gè)與數(shù)據(jù)屬性相同装哆。get,讀取該屬性時(shí)調(diào)用的函數(shù)定嗓,默認(rèn)為undefined蜕琴。set,寫(xiě)入該屬性時(shí)調(diào)用的函數(shù)宵溅,默認(rèn)為undefined
屬性的set/get方法通過(guò)Object.defineProperty()方法來(lái)設(shè)置
該方法接收三個(gè)參數(shù)凌简,第一個(gè)參數(shù)為需要設(shè)置的對(duì)象,第二個(gè)參數(shù)為對(duì)象的屬性恃逻,第三個(gè)參數(shù)為設(shè)置的內(nèi)容
function Product () {
Object.defineProperty(this, 'price', {
get: function () {return price * 0.9},
set: function (value) {
if (value <= 0) {
console.log('金額必須大于0')
} else {
price = value
}
}
})
}
3.getOwnPropertyDescriptor
該方法可以獲取給定屬性的描述雏搂,返回的是一個(gè)對(duì)象,如果是訪(fǎng)問(wèn)器屬性寇损,這個(gè)對(duì)象的屬性有configurable凸郑、enumerable、get和set矛市。如果是數(shù)據(jù)屬性芙沥,這個(gè)對(duì)象的屬性有configurable、enumerable浊吏、writable及value
三. 公有/私有屬性
公有屬性:使用對(duì)象的人可以訪(fǎng)問(wèn)的對(duì)象屬性
私有屬性:使用對(duì)象的人無(wú)法訪(fǎng)問(wèn)的對(duì)象屬性
function Person () {
this.name = name // 公有屬性
var age = 10 // 私有屬性
//公有方法
this.showName = function () {
console.log(‘我叫' + this.name)
}
//私有方法
var that = this
var showInfo = function () {
console.log('我是' + that.name) // 私有方法中使用this無(wú)法獲取到當(dāng)前對(duì)象而昨,所以用that替代
}
}
四. 類(lèi)型判斷
有如下變量
var a = 1
var b = 'abc'
var c = []
var d = {}
1.typeof
typeof弊端:無(wú)論引用的是什么類(lèi)型的對(duì)象,都返回object
typeof a // number
typeof b // string
typeof c // object
typeof d // object
//new Number(1)找田、new String('abc') 與 new Boolean(false) 返回的也是 object
2.toString.call()
通過(guò)調(diào)用toString方法來(lái)判斷類(lèi)型
toString.call(a) // [object Number]歌憨,與 new Number(1)結(jié)果一樣
toString.call(b) // [object String],與 new String('ab')結(jié)果一樣
toString.call(c) // [object Array]
toString.call(d) // [object Object]
3.instanceof
instanceof 后面只能跟對(duì)象類(lèi)型
a instanceof Number // false墩衙,new Number(1) 為 true
b instanceof String // false务嫡,new String('abc') 為 true
c instanceof Array // true
d instanceof Object // ture
4.constructor
通過(guò)該屬性可以判斷是通過(guò)那個(gè)構(gòu)造函數(shù)創(chuàng)建的
a.constructor === Number // new Number(1)一樣
b.constructor === String // new String('abc')一樣
c.constructor === Array
d.constructor === Object
五. 原型
1.雙對(duì)象法則
其實(shí)所謂的對(duì)象包含兩個(gè)獨(dú)立的對(duì)象:構(gòu)造函數(shù)對(duì)象,原型對(duì)象(prototype)
原型對(duì)象只分配一次內(nèi)存
實(shí)例化的過(guò)程其實(shí)就是拷貝構(gòu)造函數(shù)屬性的過(guò)程
實(shí)例的某些屬性漆改,值不一樣需要單獨(dú)的空間植袍,但是某些方法可能完全一樣,如果每個(gè)實(shí)例都有一份則會(huì)浪費(fèi)內(nèi)存空間籽懦,所以使用原型來(lái)定義方法,所有實(shí)例對(duì)象共享一份氛魁。
當(dāng)我們new 一個(gè)實(shí)例后暮顺,系統(tǒng)自動(dòng)做了如下事情
- 創(chuàng)建一個(gè)空對(duì)象 var p = {}
- 拷貝構(gòu)造函數(shù)中的方法屬性到空對(duì)象中
- 自動(dòng)生成一個(gè)__proto__屬性指向原型對(duì)象
p.__proto__ = xxx.prototype
2.屬性搜索機(jī)制
訪(fǎng)問(wèn)對(duì)象的某個(gè)屬性時(shí)厅篓,會(huì)先在對(duì)象自身的屬性列表中尋找,如果找不到捶码,則通過(guò)隱藏的proto屬性(該屬性保存原型對(duì)象的內(nèi)存地址)找到原型對(duì)象羽氮,然后從原型對(duì)象的屬性列表中尋找,直到找到該屬性或proto為null為止
var fun = function () {}
fun.prototype = {
info: {
name: 'peter',
age: 25
},
height: 170
}
Object.defineProperty(fun.prototype, 'weight', {
get () { return weight },
set (value) { weight = value }
})
var a = new fun()
var b = new fun()
a.info.name = 'jack'
b.info.name = 'tom'
a.height = 175
b.height = 180
a.weight = 60
b.weight = 70
//info為原型對(duì)象屬性惫恼,所有實(shí)例共享一份档押,所以修改b的值后,a的值也會(huì)發(fā)生變化
a.info.name // tom
b.info.name // tom
//height雖然也是原型對(duì)象屬性祈纯,但是為值類(lèi)型令宿,所以不會(huì)共享,每個(gè)實(shí)例都有一份
a.height // 175
b.height // 180
//weight雖然是值類(lèi)型的原型對(duì)象屬性腕窥,但是通過(guò)defineProperty方式添加的粒没,所以會(huì)共享
a.weight // 70
b.weight // 70
3.屬性屏蔽理論
構(gòu)造函數(shù)中的屬性(方法)會(huì)屏蔽掉原型中的同名屬性
function Person () {
this.name = '張三'
this.show = function () {
console.log('我是構(gòu)造函數(shù)的方法')
}
}
Person.prototype = {
name: '李四',
show: function () {
console.log('我是原型的方法')
}
}
var p = new Person()
console.log(p.name) //張三
p.show() // 我是構(gòu)造函數(shù)的方法
如果需要訪(fǎng)問(wèn)原型中的同名屬性,可以通過(guò)delete清除構(gòu)造函數(shù)中的屬性
delete p.name
delete p.show
console.log(p.name) // 李四
p.show() // 我是原型的方法
也可以直接通過(guò) 類(lèi)名.prototype.屬性名 來(lái)訪(fǎng)問(wèn)
console.log(Person.prototype.name) // 李四
Person.prototype.show() // 我是原型的方法
六. call簇爆、apply及bind方法
1.call方法
會(huì)將借用對(duì)象方法中的this改為自身
var Person = function () {
this.show = function () {
console.log('這是Person的方法癞松,年齡' + this.age)
}
}
var Student = function () {
this.show = function () {
console.log('這是Student的方法,年齡' + this.age)
}
}
var s = new Student()
s.age = 20
s.show() // 這是Student的方法入蛆,年齡20
var p = new Person()
p.show.call(s) // 這是Person的方法响蓉,年齡20
借用方法時(shí)也可以傳遞參數(shù)
var add = function (num1, num2) {
console.log(num1 + num2)
}
var show = function () {
console.log('哈哈哈哈哈')
}
add.call(show, 1, 2) // 3
2.apply方法
apply與call的用法幾乎一樣
call的傳參是離散的,而apply是把所有參數(shù)放在一個(gè)數(shù)組傳遞
apply雖然傳遞的是數(shù)組哨毁,但是使用時(shí)是把數(shù)組拆開(kāi)的
var arr1 = [1, 2, 3, 4]
Array.prototype.push.apply(arr1, [5, 6, 7, 8])
====>
Array.prototype.push.call(arr1, 5, 6, 7, 8)
====>
arr1.push(5, 6, 7, 8)
3.bind方法
bind方法與apply和call很相似枫甲,也是可以改變函數(shù)體內(nèi)this的指向
bind方法會(huì)創(chuàng)建一個(gè)新函數(shù),稱(chēng)為綁定函數(shù)挑庶,當(dāng)調(diào)用這個(gè)綁定函數(shù)時(shí)言秸,綁定函數(shù)會(huì)以第一個(gè)參數(shù)作為this,第二個(gè)及后面的參數(shù)加上本身的參數(shù)作為原函數(shù)的參數(shù)來(lái)調(diào)用原函數(shù)
var bar = function () {
console.log(this.x)
}
var foo = { x: 3 }
bar() // undefined
bar.bind(foo)() // 3
七. callee/caller屬性
1.callee屬性
callee 是 arguments 的一個(gè)屬性成員迎捺,它表示對(duì)函數(shù)對(duì)象本身的引用
function test(a, b) {
console.log(arguments.length) // 3举畸,實(shí)參的個(gè)數(shù)
console.log(test.length) // 2,形參的個(gè)數(shù)
console.log(arguments.callee) // arguments.callee === test
}
test(1, 2, 3)
callee可以用來(lái)遞歸函數(shù)
function sum(n) {
if (n > 0) return n + arguments.callee(n - 1)
// 等價(jià)于 if (n > 0) return n + sum(n - 1)
return 0
}
console.log(sum(5)) // 15
2.偽數(shù)組
偽數(shù)組就是一個(gè)包含length屬性的json對(duì)象凳枝,key都是0, 1, 2, 3......
偽數(shù)組每次都要自己計(jì)算length個(gè)數(shù)抄沮,arguments就是一個(gè)偽數(shù)組
我們可以通過(guò)下面的方式將偽數(shù)組轉(zhuǎn)換成數(shù)組
var arr1 = {0: 'a', 1: 'b', length: 2}
Array.prototype.slice.call(arr1) // ["a", "b"]
var arr2 = {length: 2}
Array.prototype.slice.call(arr2) // [undefined, undefined]
3.caller屬性
caller屬性表示當(dāng)前函數(shù)被哪個(gè)函數(shù)調(diào)用
function show () {
console.log(show.caller.toString())
}
function test () {
show()
}
test() // f test () { show() }
show() // null
八. 原型鏈
所謂原型,就是prototype岖瑰、proto 和 constructor 的三角關(guān)系
function Foo() {}
var f1 = new Foo
1.prototype
構(gòu)造函數(shù)有一個(gè)prototype屬性叛买,指向它的原型對(duì)象
實(shí)例對(duì)象沒(méi)有prototype屬性
2.constructor
構(gòu)造函數(shù):用來(lái)初始化新創(chuàng)建的對(duì)象的函數(shù),在例子中蹋订,F(xiàn)oo就是構(gòu)造函數(shù)
通過(guò)同一個(gè)構(gòu)造函數(shù)實(shí)例化的多個(gè)對(duì)象具有相同的原型對(duì)象
原型對(duì)象有一個(gè)constructor屬性率挣,指向該原型對(duì)象對(duì)應(yīng)的構(gòu)造函數(shù)
Foo.prototype.constructor === Foo
重新定義原型對(duì)象后,其constructor屬性會(huì)指向Object()
Foo.prototype = {} // Foo.prototype.constructor === Object
如果重新定義原型對(duì)象不希望改變其constructor露戒,可設(shè)置為之前的constructor
Foo.prototype = {
constructor: Foo
... // 其他屬性
}
實(shí)例本身沒(méi)有constructor屬性椒功,但由于實(shí)例可以繼承原型對(duì)象的屬性捶箱,所以也擁有了constructor屬性,同樣指向構(gòu)造函數(shù)
f1.constructor === Foo.prototype.constructor === Foo
function Foo () {}
====> 等價(jià)于
var Foo = new Function()
//所以可以看做Foo是Function的實(shí)例對(duì)象动漾,F(xiàn)oo本身沒(méi)有constructor丁屎,而是繼承自Function.prototype,所以
Foo.constructor === Function.prototype.constructor === Function
3.proto
實(shí)例對(duì)象有一個(gè)proto屬性旱眯,指向構(gòu)造函數(shù)的原型對(duì)象
f1.__proto__ === Foo.prototype // Foo是創(chuàng)建f1的構(gòu)造函數(shù)
任何函數(shù)都可以看成是Function的實(shí)例晨川,F(xiàn)unction也可以看成是調(diào)用自身的new操作實(shí)例化的,所以
Foo.__proto__ === Function.prototype
Function.__proto__ === Function.prototype
Foo.prototype作為原型對(duì)象删豺,同時(shí)也是實(shí)例對(duì)象共虑。任何對(duì)象都可以看做是通過(guò)Object()構(gòu)造函數(shù)實(shí)例化的對(duì)象
Foo.prototype.__proto__ === Object.prototype
Function.prototype.__proto__ === Object.prototype
Object()也是函數(shù),它的構(gòu)造函數(shù)就是Function
Object.__proto__ === Function.prototype
九. 繼承
1.原型鏈繼承
function Super () {
this.name = '張三'
}
function Sub (age) {
this.age = age
}
Sub.prototype = new Super()
let sub = new Sub(20)
console.log(sub.name) // 張三
2.構(gòu)造函數(shù)繼承
function Super (name) {
this.name = name
}
function Sub (name, age) {
Super.call(this, name)
this.age = age
}
let sub = new Sub('張三', 20)
3.組合繼承
function Super (name) {
this.name = name
}
Super.prototype.job = 'teacher'
function Sub (name) {
Super.call(this, name)
}
Sub.prototype = new Super()
let sub = new Sub('張三')
console.log(sub.job) // 'teacher'
4.原型式繼承
function object (obj) {
function F() {}
F.prototype = obj
reutrn new F()
}
let person = {
name: '張三'
age: 20
}
let p = object(person)
console.log(p.name) // 張三
5.寄生式繼承
function object (obj) {
function F() {}
F.prototype = obj
return new F()
}
function createAnother (obj) {
let another = object(obj)
another.sayName = function () {
console.log(this.name)
}
return another
}
let person = { name: '張三' }
let p = createAnother(person)
p.sayName() // 張三
6.寄生組合式繼承
function object (obj) {
function F() {}
F.prototype = obj
return new F()
}
function inheritPrototype(sub, super) {
let prototype = object(super.prototype)
prototype.constructor = sub
sub.prototype = prototype
}
function Super(name) {
this.name = name
}
Super.prototype.sayName = function () {
console.log(this.name)
}
function Sub(name, age) {
Super.call(this, name)
this.age = age
}
inheritPrototype(Sub, Super)
let sub = new Sub('張三', 20)
sub.sayName() // 張三