前言:你們好,我叫邵威儒,你們都喜歡喊我小邵,學的金融專業卻憑藉興趣愛好入了程序猿的坑,從大學買的第一本vb和自學vb,我就與編程結下不解之緣,隨後自學易語言寫遊戲輔助、交易軟件,至今進入了前端領域,看到很多朋友都寫文章分享,本身也弄一個玩玩,如下文章純屬我的理解,便於記錄學習,確定有理解錯誤或理解不到位的地方,意在站在前輩的肩膀,分享我的對技術的通俗理解,共同成長!javascript
後續我會陸陸續續更新javascript方面,儘可能把javascript這個學習路徑體系都寫一下
包括前端所經常使用的es六、angular、react、vue、nodejs、koa、express、公衆號等等
都會從淺到深,從入門開始逐步寫,但願能讓你們有所收穫,也但願你們關注我~前端
文章列表:juejin.im/user/5a84f8…vue
Author: 邵威儒
Email: 166661688@qq.com
Wechat: 166661688
github: github.com/iamswr/java
es6的class能夠看 juejin.im/post/5b7b95…node
面向對象在面試中會常常問起,特別是對於繼承的理解,關於面向對象的定義我就不說了,我主要從繼承方面來說面向對象的好處,更重要的是收穫一種編程思惟。react
或許光看文字不太好理解,也能夠對應着代碼敲一下,來感覺一下繼承是怎樣的~git
接下來我給你們講下我對javascript面向對象的理解。es6
好處:github
特性:面試
// 1.封裝
// 假設須要登記學籍,分別記錄小明和小紅的學籍號、姓名
let name1 = "小明"
let num1 = "030578001"
let name2 = "小紅"
let num2 = "030578002"
// 若是須要登記大量的數據,則弊端會很是明顯,並且很差維護,那麼咱們會使用如下方法來登陸,這也是面向對象的特性之一:封裝
let p1 = {
name:"小明",
num:"030578001"
}
let p2 = {
name:"小紅",
num:"030578002"
}
// 2.繼承
// 從已有的對象上,獲取屬性、方法
function Person(){
this.name = "邵威儒"
}
Person.prototype.eat = function(){
console.log("吃飯")
}
let p1 = new Person()
p1.eat() // 吃飯
let p2 = new Person()
p2.eat() // 吃飯
// 3.多態
// 同一操做,針對不一樣對象,會有不一樣的結果
let arr = [1,2,3]
arr.toString() // 1,2,3
let obj = new Object()
obj.toString() // [object Object]
複製代碼
// 1.字面量
// 該方式的劣勢比較明顯,就是沒法複用,若是建立大量同類型的對象,則代碼會很是冗餘
let person = {
name:"邵威儒",
age:28,
eat:function(){
console.log('吃飯')
}
}
// 2.利用內置對象的方式建立對象
// 該方式的劣勢也比較明顯,就是沒辦法判斷類型
function createObj(name,age){
let obj = new Object()
obj.name = name
obj.age = age
return obj
}
let p1 = createObj("邵威儒",28)
let p2 = createObj("swr",28)
console.log(p1 === p2) // false
console.log(p1.constructor) // Object 指向的構造函數是Object
console.log(p2.constructor) // Object 指向的構造函數是Object
// 那麼爲何說沒辦法判斷類型呢?那麼咱們建立一條狗的對象
// 能夠看出,狗的constructor也是指向Object,那麼咱們人和狗的類型就沒辦法去區分了
let dog = createObj('旺財',10)
console.log(dog.constructor) // Object 指向的構造函數是Object
// 3.利用構造函數的方式建立對象
// 其執行的過程:
// 3.1 使用new這個關鍵詞來建立對象
// 3.2 在構造函數內部把新建立出來的對象賦予給this
// 3.3 在構造函數內部把新建立(未來new的對象)的屬性方法綁到this上
// 3.4 默認是返回新建立的對象,特別須要注意的是
// 若是顯式return一個對象數據類型,那麼未來new的對象,就是顯式return的對象
function Person(name,age){
// 1.系統自動建立對象,而且把這個對象賦值到this上,此步不須要咱們操做
// let this = new Object()
// 2.給這個對象賦屬性、方法,須要咱們本身操做
this.name = name
this.age = age
this.eat = function(){
console.log(name + '吃飯')
}
// 3.系統自動返回建立的對象
// return this
}
let p1 = new Person("邵威儒",28)
console.log(p1.constructor) // Person 指向的構造函數是Person
function Dog(name,age){
this.name = name
this.age = age
}
let dog = new Dog("旺財",10)
console.log(dog.constructor) // Dog 指向的構造函數是Dog
複製代碼
// 默認是返回新建立的對象,特別須要注意的是
// 若是顯式return一個對象數據類型,那麼未來new的對象,就是顯式return的對象
// 這個是以前一個小夥伴問的,咱們看下面的例子
// 當咱們顯式return一個原始數據類型
function Person(name,age){
this.name = name
this.age = age
return "1"
}
let p = new Person("邵威儒",28) // { name: '邵威儒', age: 28 }
// 當咱們顯式return一個對象數據類型時
function Person(name,age){
this.name = name
this.age = age
return [1,2,3]
}
let p = new Person("邵威儒",28) // [ 1, 2, 3 ]
// 咱們發現,當顯式return一個對象數據類型時,咱們new出來的對象,獲得的是return的值
複製代碼
都是綁定在未來經過構造函數建立的實例上,而且須要經過這個實例來訪問的屬性、方法
function Person(name,age){
// 實例屬性
this.name = name
this.age = age
// 實例方法
this.eat = function(){
console.log(this.name + '吃飯')
}
}
// 經過構造函數建立出實例p
let p = new Person("邵威儒",28)
// 經過實例p去訪問實例屬性
console.log(p.name) // 邵威儒
// 經過實例p去訪問實例方法
p.eat() // 邵威儒吃飯
複製代碼
綁定在構造函數上的屬性方法,須要經過構造函數訪問
// 好比咱們想取出這個Person構造函數建立了多少個實例
function Person(name, age) {
this.name = name
this.age = age
if (!Person.total) {
Person.total = 0
}
Person.total++
}
let p1 = new Person('邵威儒',28)
console.log(Person.total) // 1
let p2 = new Person('swr',28)
console.log(Person.total) // 2
複製代碼
構造函數new出來的實例,都共享這個構造函數的原型對象上的屬性方法,相似共享庫。
function Person(name,age){
this.name = name
this.age = age
}
Person.prototype.eat = function(){ // 使用prototype找到該Person的原型對象
console.log(this.name + '吃飯')
}
let p1 = new Person("邵威儒",28)
let p2 = new Person("swr",28)
console.log(p1.eat === p2.eat) // true
p1.eat() // 邵威儒吃飯
複製代碼
由於經過new生成的實例,至關因而從新開闢了一個堆區,雖然是同類型,擁有相似的屬性和方法,可是這些屬性和方法,並非相同的
function Person(name,age){
this.name = name
this.age = age
this.eat = function(){
console.log('吃飯')
}
}
let p1 = new Person("邵威儒",28)
let p2 = new Person("swr",28)
console.log(p1.eat === p2.eat) // fasle
複製代碼
從上面能夠得出,p1和p2的eat方法,行爲是一致的,可是他們卻不等,是由於他們不一樣在一個堆區,若是隻有一、2個實例還好,若是大量的實例,那麼會大量生成這種本來能夠複用共用的屬性方法,很是耗費性能,不利於複用,此時咱們就須要一個相似共享庫的對象,讓實例可以沿着原型鏈,去找。
function Person(name){
this.name = name
}
Person.prototype.eat = functoin(){ // 經過構造函數Person的prototype屬性找到Person的原型對象
console.log('吃飯')
}
let p1 = new Person("邵威儒",28)
let p2 = new Person("swr",28)
console.log(p1.eat === p2.eat) // true
複製代碼
這樣能夠增長複用性,可是還存在一個問題,若是咱們要給原型對象添加大量屬性方法時,咱們不斷的Person.prototype.xxx = xxx、Person.prototype.xxxx = xxxx,這樣也是很繁瑣,那麼咱們該怎麼解決這個問題?
function Person(name){
this.name = name
}
// 讓Person.prototype指針指向一個新的對象
Person.prototype = {
eat:function(){
console.log('吃飯')
},
sleep:function(){
console.log('睡覺')
}
}
複製代碼
function Person(name){
this.name = name
}
Person.prototype = {
eat:function(){
console.log('吃飯')
},
sleep:function(){
console.log('睡覺')
}
}
let p = new Person('邵威儒',28)
// 訪問原型對象
console.log(Peroson.prototype)
console.log(p.__proto__) // __proto__僅用於測試,不能寫在正式代碼中
複製代碼
// 1.hasOwnProperty 在對象自身查找屬性而不到原型上查找
function Person(){
this.name = '邵威儒'
}
let p = new Person()
let key = 'name'
if((key in p) && p.hasOwnProperty(key)){
// name僅在p對象中
}
// 2.isPrototypeOf 判斷一個對象是不是某個實例的原型對象
function Person(){
this.name = '邵威儒'
}
let p = new Person()
let obj = Person.prototype
obj.isPrototypeOf(p) // true
複製代碼
原型對象默認是有一個指針constructor指向其構造函數的,
若是咱們把構造函數的原型對象,替換成另一個原型對象,那麼這個新的原型
對象的constructor則不是指向該構造函數,會致使類型判斷的錯誤
function Person(){
this.name = '邵威儒'
}
Person.prototype = { // 把Person構造函數的原型對象替換成該對象
eat:function(){
console.log('吃飯')
}
}
console.log(Person.prototype.constructor) // Object
// 咱們發現,該原型對象的constructor指向的是Object而不是Person
// 那麼咱們如今解決一下這個問題,把原型對象的constructor指向到Person
Person.prototype.constructor = Person
console.log(Person.prototype.constructor) // Person
複製代碼
面向對象的繼承方式有不少種,原型鏈繼承、借用構造函數繼承、組合繼承、原型式繼承、寄生式繼承、寄生式組合繼承、深拷貝繼承等等。
利用原型鏈的特性,當在自身找不到時,會沿着原型鏈往上找。
function Person(){
this.name = '邵威儒'
this.pets = ['旺財','小黃']
}
Person.prototype.eat = function(){
console.log('吃飯')
}
function Student(){
this.num = "030578000"
}
let student = new Student()
console.log(student.num) // '030578000'
console.log(student.name) // undefined
console.log(student.pets) // undefined
student.eat() // 報錯
複製代碼
從上面咱們能夠看到,Student沒有繼承Person,此時它們之間的聯繫是這樣的。
既然要讓實例student訪問到Person的原型對象屬性方法,
咱們會想到,把Student.prototype改寫爲Person.prototype
function Person(){
this.name = '邵威儒'
this.pets = ['旺財','小黃']
}
Person.prototype.eat = function(){
console.log('吃飯')
}
function Student(){
this.num = "030578000"
}
// * 改寫Student.prototype指針指向
Student.prototype = Person.prototype
let student = new Student()
console.log(student.num) // '030578000'
console.log(student.name) // undefined
console.log(student.pets) // undefined
student.eat() // * '吃飯'
複製代碼
此時關係圖爲
如今修改了Student.prototype指針指向爲Person.prototype後,能夠訪問Person.prototype上的eat方法,可是student還不能繼承Person.name和Person.pets,那我會想到,是Person的實例,纔會同時擁有實例屬性方法和原型屬性方法。
function Person(){
this.name = '邵威儒'
this.pets = ['旺財','小黃']
}
Person.prototype.eat = function(){
console.log('吃飯')
}
function Student(){
this.num = "030578000"
}
// * new一個Person的實例,同時擁有其實例屬性方法和原型屬性方法
let p = new Person()
// * 把Student的原型對象指向實例p
Student.prototype = p
// * 把Student的原型對象的constructor指向Student,解決類型判斷問題
Student.prototype.constructor = Student
let student = new Student()
console.log(student.num) // '030578000'
console.log(student.name) // * '邵威儒'
console.log(student.pets) // * '[ '旺財', '小黃' ]'
student.eat() // '吃飯'
複製代碼
由於實例p是由Person構造函數實例化出來的,因此同時擁有其實例屬性方法和原型屬性方法,而且把這個實例p做爲Student的原型對象,此時的關係圖以下
經過這樣的方式,會有一個問題,原型對象相似一個共享庫,全部實例共享原型對象同一個屬性方法,若是原型對象上有引用類型,那麼會被全部實例共享,也就是某個實例更改了,則會影響其餘實例,咱們能夠看一下
function Person(){
this.name = '邵威儒'
this.pets = ['旺財','小黃']
}
Person.prototype.eat = function(){
console.log('吃飯')
}
function Student(){
this.num = "030578000"
}
let p = new Person()
Student.prototype = p
Student.prototype.constructor = Student
let student = new Student()
let student2 = new Student() // * new多一個實例
console.log(student.num) // '030578000'
console.log(student.name) // '邵威儒'
console.log(student.pets) // '[ '旺財', '小黃' ]'
student.eat() // '吃飯'
// 此時咱們修改某一個實例,pets是原型對象上的引用類型 數組
student.pets.push('小紅')
console.log(student.pets) // * [ '旺財', '小黃', '小紅' ]
console.log(student2.pets) // * [ '旺財', '小黃', '小紅' ]
複製代碼
從上面能夠看出,student的pets(實際就是原型對象上的pets)被修改後,相關的實例student2也會受到影響。
那麼咱們能不能把Person上的屬性方法,添加到Student上呢?以防都存在原型對象上,會被全部實例共享,特別是引用類型的修改,會影響全部相關實例。
能夠利用call來實現。
function Person(){
this.name = '邵威儒'
this.pets = ['旺財','小黃']
}
Person.prototype.eat = function(){
console.log('吃飯')
}
function Student(){
Person.call(this) // * 利用call調用Person上的屬性方法拷貝一份到Student
this.num = "030578000"
}
let p = new Person()
Student.prototype = p
Student.prototype.constructor = Student
let student = new Student()
let student2 = new Student()
console.log(student.num) // '030578000'
console.log(student.name) // '邵威儒'
console.log(student.pets) // '[ '旺財', '小黃' ]'
student.eat() // '吃飯'
// * 此時咱們修改某一個實例,pets是原型對象上的引用類型 數組
student.pets.push('小紅')
console.log(student.pets) // * [ '旺財', '小黃', '小紅' ]
console.log(student2.pets) // * [ '旺財', '小黃' ]
複製代碼
那麼還有個問題,當父構造函數須要接收參數時,怎麼處理?
function Person(name,pets){ // * 父構造函數接收name,pets參數
this.name = name // * 賦值到this上
this.pets = pets // * 賦值到this上
}
Person.prototype.eat = function(){
console.log('吃飯')
}
function Student(num,name,pets){ // * 在子構造函數中也接收參數
Person.call(this,name,pets) // * 在這裏把name和pets傳參數
this.num = num // * 賦值到this上
}
let p = new Person()
Student.prototype = p
Student.prototype.constructor = Student
let student = new Student("030578000","邵威儒",["旺財","小黃"])
let student2 = new Student("030578001","iamswr",["小紅"])
console.log(student.num) // '030578000'
console.log(student.name) // '邵威儒'
console.log(student.pets) // '[ '旺財', '小黃' ]'
student.eat() // '吃飯'
student.pets.push('小紅')
console.log(student.pets) // * [ '旺財', '小黃', '小紅' ]
console.log(student2.pets) // * [ '小紅' ]
複製代碼
function Person(name,pets){
this.name = name
this.pets = pets
}
Person.prototype.eat = function(){
console.log('吃飯')
}
function Student(num,name,pets){
Person.call(this,name,pets)
this.num = num
}
// * 寄生式繼承
function Temp(){} // * 聲明一個空的構造函數,用於橋樑做用
Temp.prototype = Person.prototype // * 把Temp構造函數的原型對象指向Person的原型對象
let temp = new Temp() // * 用構造函數Temp實例化一個實例temp
Student.prototype = temp // * 把子構造函數的原型對象指向temp
temp.constructor = Student // * 把temp的constructor指向Student
let student1 = new Student('030578001','邵威儒',['旺財','小黃'])
console.log(student1) // Student { name: '邵威儒',
pets: [ '旺財', '小黃' ],
num: '030578001' }
let student2 = new Student('030578002','iamswr',['小紅'])
console.log(student2) // Student { name: 'iamswr',
pets: [ '小紅' ],
num: '030578002' }
複製代碼
// 原型式繼承
function createObjWithObj(obj){ // * 傳入一個原型對象
function Temp(){}
Temp.prototype = obj
let o = new Temp()
return o
}
// * 把Person的原型對象當作temp的原型對象
let temp = createObjWithObj(Person.prototype)
// * 也可使用Object.create實現
// * 把Person的原型對象當作temp2的原型對象
let temp2 = Object.create(Person.prototype)
複製代碼
// 寄生式繼承
// 咱們在原型式的基礎上,但願給這個對象新增一些屬性方法
// 那麼咱們在原型式的基礎上擴展
function createNewObjWithObj(obj) {
let o = createObjWithObj(obj)
o.name = "邵威儒"
o.age = 28
return o
}
複製代碼
// 方法一:利用JSON.stringify和JSON.parse
let swr = {
name:"邵威儒",
age:28
}
let swrcopy = JSON.parse(JSON.stringify(swr))
console.log(swrcopy) // { name:"邵威儒",age:28 }
// 此時咱們修改swr的屬性
swr.age = 29
console.log(swr) // { name:"邵威儒",age:29 }
// 可是swrcopy卻不會受swr影響
console.log(swrcopy) // { name:"邵威儒",age:28 }
// 這種方式進行深拷貝,只針對json數據這樣的鍵值對有效
// 對於函數等等反而無效,很差用,接着繼續看方法2、三。
複製代碼
// 方法二:
function deepCopy(fromObj,toObj) { // 深拷貝函數
// 容錯
if(fromObj === null) return null // 當fromObj爲null
if(fromObj instanceof RegExp) return new RegExp(fromObj) // 當fromObj爲正則
if(fromObj instanceof Date) return new Date(fromObj) // 當fromObj爲Date
toObj = toObj || {}
for(let key in fromObj){ // 遍歷
if(typeof fromObj[key] !== 'object'){ // 是否爲對象
toObj[key] = fromObj[key] // 若是爲原始數據類型,則直接賦值
}else{
toObj[key] = new fromObj[key].constructor // 若是爲object,則new這個object指向的構造函數
deepCopy(fromObj[key],toObj[key]) // 遞歸
}
}
return toObj
}
let dog = {
name:"小白",
sex:"公",
firends:[
{
name:"小黃",
sex:"母"
}
]
}
let dogcopy = deepCopy(dog)
// 此時咱們把dog的屬性進行修改
dog.firends[0].sex = '公'
console.log(dog) // { name: '小白',
sex: '公',
firends: [ { name: '小黃', sex: '公' }] }
// 當咱們打印dogcopy,會發現dogcopy不會受dog的影響
console.log(dogcopy) // { name: '小白',
sex: '公',
firends: [ { name: '小黃', sex: '母' } ] }
複製代碼
// 方法三:
let dog = {
name:"小白",
sex:"公",
firends:[
{
name:"小黃",
sex:"母"
}
]
}
function deepCopy(obj) {
if(obj === null) return null
if(typeof obj !== 'object') return obj
if(obj instanceof RegExp) return new RegExp(obj)
if(obj instanceof Date) return new Date(obj)
let newObj = new obj.constructor
for(let key in obj){
newObj[key] = deepCopy(obj[key])
}
return newObj
}
let dogcopy = deepCopy(dog)
dog.firends[0].sex = '公'
console.log(dogcopy)
複製代碼