(一)封裝
- Javascript是一種基於對象(object-based)的語言,你遇到的所有東西幾乎都是對象。
- 但是,它又不是一種真正的面向對象編程(OOP)語言,因爲它的語法中沒有class(類)。
1.1. 生成實例對象的原始模式
var Cat = {
name: '',
color: ''
}
// 現在,我們需要根據這個原型對象的規格(schema),生成兩個實例對象。
var cat1 = {} // 創建一個空對象
cat1.name = '大毛' // 按照原型對象的屬性賦值
cat1.color = '黃色'
// var cat2 = {}
// cat2.name = '二毛'
// cat2.color = '黑色'
1.2. 原始模式的改進
function Cat(name, color) {
return {
name: name,
color: color
}
}
var cat1 = Cat('大毛', '黃色')
var cat2 = Cat('二毛', '黑色')
這種方法的問題依然是,cat1和cat2之間沒有內在的聯繫,不能反映出它們是同一個原型對象的實例
1.3. 構造函數模式
- 謂"構造函數",其實就是一個普通函數,但是內部使用了this變量。對構造函數使用new運算符,就能生成實例,並且this變量會綁定在實例對象上。
function Cat(name, color) {
this.name = name
this.color = color
}
var cat1 = new Cat('大毛', '黃色')
var cat2 = new Cat('二毛', '黑色')
console.log(cat1.name) // 大毛
console.log(cat1.color) // 黃色
// console.log('cat1', cat1)
// console.log('cat2', cat2)
// 這時cat1和cat2會自動含有一個constructor屬性,指向它們的構造函數。
// console.log(cat1.constructor === Cat)
// console.log(cat2.constructor === Cat)
console.log(cat1 instanceof Cat)
console.log(cat1 instanceof Cat.prototype.constructor)
console.log(' Cat.prototype.constructor', Cat.prototype.constructor)
console.log(' Cat.prototype.constructor ==', Cat.prototype.constructor === Cat)
console.log(' Cat.prototype.constructor ojb', Cat.prototype.constructor === Object)
console.log(' Cat.prototype.constructor instanceof ojb', Cat.prototype.constructor instanceof Object)
1.4.構造函數模式的問題
function Cat(name, color) {
this.name = name
this.color = color
this.type = '貓科動物'
this.eat = function() { console.log('喫老鼠') }
}
var cat1 = new Cat('大毛', '黃色')
var cat2 = new Cat('二毛', '黑色')
console.log(cat1.type) // 貓科動物
cat1.eat() // 喫老鼠
console.log(cat1.eat === cat2.eat) // false
面上好像沒什麼問題,但是實際上這樣做,有一個很大的弊端。
那就是對於每一個實例對象,type屬性和eat()方法都是一模一樣的內容,每一次生成一個實例,
都必須爲重複的內容,多佔用一些內存。這樣既不環保,也缺乏效率。
1.5.Prototype模式
- Javascript規定,每一個構造函數都有一個prototype屬性,
- 指向另一個對象。這個對象的所有屬性和方法,都會被構造函數的實例繼承。
function Cat(name, color) {
this.name = name
this.color = color
}
Cat.prototype.type = '貓科動物'
Cat.prototype.eat = function() { console.log('喫老鼠') }
var cat1 = new Cat('大毛', '黃色')
var cat2 = new Cat('二毛', '黑色')
console.log(cat1.type) // 貓科動物
cat1.eat() // 喫老鼠
console.log('cat1 instanceof Cat', cat1 instanceof Cat)
console.log('Cat.prototype.isPrototypeOf(cat1)', Cat.prototype.isPrototypeOf(cat1))
console.log('Cat.prototype.isPrototypeOf(cat2)', Cat.prototype.isPrototypeOf(cat2))
console.log('cat1.hasOwnProperty(color)', cat1.hasOwnProperty('color'))
console.log('cat1.hasOwnProperty(type)', cat1.hasOwnProperty('type'))
console.log("'color' in cat1", 'color' in cat1)
(二)構造函數實現繼承
/**
* new實例化對象會創建一個空對象繼承構造函數的對象屬性:值 和prototype上的方法以及隱式原型上的方法
*/
function Animal() {
this.species = '動物'
}
function Cat(name, color) {
this.name = name
this.color = color
}
2.1.構造函數綁定
function Cat(name, color) {
Animal.apply(this, arguments)
this.name = name
this.color = color
}
var cat1 = new Cat('大毛', '黃色')
console.log(cat1.species) // 動物
2.2.prototype模式
Cat.prototype = new Animal() // 將Cat的prototype 指向 Amimal實例
// 相當於完全刪除了prototype 原先的值,然後賦了一個新值
Cat.prototype.constructor = Cat
// 任何一個prototype對象都有一個constructor屬性,指向它的構造函數
// 每一個實例也有一個constructor屬性,默認調用protorype對象的constructor
// 不加這一行,cat1.constructor也指向Animal!
// 這顯然會導致繼承鏈的紊亂(cat1明明是用構造函數Cat生成的),
// 因此我們必須手動糾正,將Cat.prototype對象的constructor值改爲Cat。這就是第二行的意思。
var cat1 = new Cat('大毛', '黃色')
console.log('cat1.species', cat1.species)
console.log('Cat.prototype.constructor', Cat.prototype.constructor)
console.log('cat1.constructor', cat1.constructor)
console.log('Cat.prototype.constructor === cat1.constructor', Cat.prototype.constructor === cat1.constructor)
2.3.直接繼承prototype
/**
- 第三種方法是對第二種方法的改進。由於Animal對象中,不變的屬性都可以直接寫入Animal.prototype。
- 所以,我們也可以讓Cat()跳過 Animal(),直接繼承Animal.prototype。 */
function Animal() { }
Animal.prototype.species = '動物'
function Cat(name, color) {
this.name = name
this.color = color
}
Cat.prototype = Animal.prototype
Cat.prototype.constructor = Cat // 這一句實際上把Animal.prototype對象的constructor屬性也改掉了!
var cat1 = new Cat('大毛', '黃色')
console.log(cat1.species) // 動物
console.log(Animal.prototype.constructor)
2.4. 利用空對象作爲中介
/**
* 由於"直接繼承prototype"存在上述的缺點,所以就有第四種方法,利用一個空對象作爲中介
*/
/**
* 這樣做的優點是效率比較高(不用執行和建立Animal的實例了),比較省內存。
* 缺點是 Cat.prototype和Animal.prototype現在指向了同一個對象,
* 那麼任何對Cat.prototype的修改,都會反映到Animal.prototype。
*/
function Animal() { }
Animal.prototype.species = '動物'
function Cat(name, color) {
this.name = name
this.color = color
}
var F = function() {}
F.prototype = Animal.prototype
Cat.prototype = new F()
Cat.prototype.constructor = Cat
var cat1 = new Cat('小喵', 'white')
console.log('cat1', cat1)
console.log('cat1.species', cat1.species)
console.log('cat1.constructor', cat1.constructor)
console.log('Animal.prototype.constructor', Animal.prototype.constructor === Animal)
console.log('cat1.prototype.constructor', cat1.prototype)
console.log('F.prototype.constructor', F.prototype.constructor)
console.log('Animal.prototype.__proto__', Animal.prototype.__proto__)
console.log('Animal.prototype.__proto__.__proto__', Animal.prototype.__proto__.__proto__) // Object.prototype
console.log('Animal.prototype.__proto__.constructor', Animal.prototype.__proto__.constructor) // Object
console.log('Animal.prototype.__proto__.__proto__.constructor', Animal.prototype.__proto__.__proto__) // null
2.5.拷貝繼承
function Animal() {}
Animal.prototype.species = '動物'
function Cat(name, color) {
this.name = name
this.color = color
}
function extends2(Child, Parent) {
var p = Parent.prototype
var c = Child.prototype
for (var i in p) {
c[i] = p[i]
}
c.uber = p
}
extends2(Cat, Animal)
var cat1 = new Cat('大毛', '黃色')
console.log('cat1.species', cat1.species)
(三)非構造函數繼承
- 兩個對象都是普通對象,不是構造函數,無法使用構造函數方法實現"繼承"。
3.1.object() 方法
/**
* 把生成子對象的prototype屬性,指向傳入的父對象
*/
var Chinese = {
nation: '中國'
}
function object(o) {
function F() {}
F.prototype = o
return new F()
}
var Doctor = object(Chinese)
Doctor.job = '醫生'
console.log('Doctor', Doctor)
console.log('Doctor.nation', Doctor.nation)
3.2.淺拷貝
/**
* 除了使用"prototype鏈"以外,還有另一種思路:把父對象的屬性,全部拷貝給子對象,也能實現繼承。
*/
function extendCopy(p) {
var c = {}
for (var i in p) {
c[i] = p[i]
}
c.uber = p
return c
}
var Doctor = extendCopy(Chinese)
Doctor.career = '醫生'
console.log(Doctor.nation) // 中國
但是,這樣的拷貝有一個問題。那就是,如果父對象的屬性等於數組或另一個對象,
那麼實際上,子對象獲得的只是一個內存地址,而不是真正拷貝,因此存在父對象被篡改的可能。
extendCopy()只是拷貝基本類型的數據
3.3.深拷貝
function deepCopy(o, c) {
// 注意點1:深度拷貝 遞歸第二個參數c是必傳的
// 注意點2:o[i].constructor === Array Array 是Array 對象,不是字符串'Array'
c = c || {}
for (var i in o) {
if (typeof o[i] === 'object') {
c[i] = o[i].constructor === Array ? [] : {}
deepCopy(o[i], c[i])
} else {
c[i] = o[i]
}
}
return c
}
Chinese.birthPlaces = ['北京', '上海', '香港']
var Doctor = deepCopy(Chinese)
console.log('Doctor', Doctor)
Doctor.birthPlaces.push('廈門')
// 在子對象上修改這個屬性,父對象不會受影響
console.log(Doctor.birthPlaces) // 北京, 上海, 香港, 廈門
console.log(Chinese.birthPlaces) // 北京, 上海, 香港
function extend(Child, Parent) {
var F = function() {}
F.prototype = Parent.prototype
Child.prototype = new F()
Child.prototype.constructor = Child
Child.uber = Parent.prototype // 意思是爲子對象設一個uber屬性,
// 這個屬性直接指向父對象的prototype屬性。
// 這一行放在這裏,只是爲了實現繼承的完備性,純屬備用性質。
}
extend(Cat, Animal)
var cat1 = new Cat('大毛', '黃色')
console.log(cat1.species) // 動物
console.log('Animal.prototype.constructor', Animal.prototype.constructor === Animal)
console.log('cat1.prototype.constructor', cat1.__proto__.constructor === Cat)
console.log('Cat.prototype.constructor === Cat', Cat.prototype.constructor === Cat)
console.log('cat1.uber', cat1)
js 定義類的三種方法
1.構造函數法
/**
* 實例化new的過程
* 1. 創建一個新對象
* 2. 將構造函數中的作用域賦值給新對象( 將this指向了實例對象)
* 3. 執行構造函數中的代碼(爲這個新的實例對象添加屬性)
* 2. 返回這個實例對象
*/
function Cat(name, color) {
this.name = name
this.color = color
}
Cat.prototype.makeSound = function() {
console.log('喵喵1~~~')
}
const cat1 = new Cat('喵1', 'white')
console.log('cat1', cat1.name)
console.log('cat1', cat1.color)
cat1.makeSound()
2. Object.create()
var Cat = {
name: '大毛',
makeSound: function() {
console.log('喵喵2~~~')
}
}
const cat2 = Object.create(Cat)
console.log(cat2.name)
cat2.makeSound()
if (!Object.create) {
Object.create = function(o) {
function F() {}
F.prototype = o
return new F()
}
}
3.1 極簡主義法
var Cat = {
createNew: function() {
var cat = {}
cat.name = '大毛'
cat.makeSound = function() {
console.log('喵喵3~~~')
}
return cat
}
}
var cat1 = Cat.createNew()
cat1.makeSound()
3.2 繼承
var Animal = {
creatNew: function() {
var animal = {}
animal.sleep = function() {
console.log('睡覺覺')
}
return animal
}
}
var Cat = {
createNew: function() {
var cat = Animal.creatNew()
cat.name = '大毛'
cat.color = 'white'
cat.makeSound = function() {
console.log('喵喵4')
}
return cat
}
}
var cat1 = Cat.createNew()
cat1.sleep()
3.3私有屬性和私有方法
var Cat = {
createNew: function() {
var cat = {}
var sound = '喵喵喵'
var color = '小白'
cat.makeSound = function() {
console.log('sound', sound)
return color
}
return cat
}
}
var cat1 = Cat.createNew()
console.log('cat1.sound', cat1.makeSound())
3.4 數據共享
var Cat = {
sound: '喵喵喵5',
createNew: function() {
var cat = {}
cat.makeSound = function() {
console.log('Cat.sound', Cat.sound)
}
cat.changeSound = function(param) {
console.log('this', this)
this.sound = param
// Cat.sound = param
}
return cat
}
}
var cat1 = Cat.createNew()
cat1.makeSound()
cat1.changeSound('wang')
var cat2 = Cat.createNew()
cat2.makeSound()
cat1.makeSound()
test1
function Animal() {
this.makesound = function() {
console.log('miao~~~')
}
}
function Cat(name, color) {
Animal.apply(this, arguments)
this.name = name
this.color = color
}
const cat1 = new Cat('小喵喵', 'white')
const cat2 = new Cat('小花花', 'black')
console.log('cat1', cat1.name, cat1.color)
console.log('cat2', cat2.name, cat2.color)
cat1.makesound()