JS高級:面向對象解析

1 實例屬性/方法

都是綁定在使用構造函數建立出來的對象p上; 最終使用的時候也是使用對象p來進行訪問;javascript

function Person(name, age, doFunc) {
        this.name = name;
        this.age = age;
        this.doFunc = doFunc;
    }

    var p1 = new Person('sz', 18, function () {
        console.log('sz在上課');
    });
    var p2 = new Person('王二小', 18, function () {
        console.log('王二小在放羊');
    });

2 靜態屬性/方法

函數本質也是一個對象, 既然是個對象, 那麼就能夠動態的添加屬性和方法java

只要函數存在, 那麼綁定在它身上的屬性和方法, 也會一直存在編程

eg,記錄總共建立了多少我的對象:segmentfault

2.1 全局變量

// 1. 設置一個全局的變量
    var personCount = 0;
    function Person(name, age, doFunc) {
        this.name = name;
        this.age = age;
        this.doFunc = doFunc;
        personCount++;
    }

    var p1 = new Person('sz', 18, function () {
        console.log('sz在上課');
    });
    var p2 = new Person('王二小', 18, function () {
        console.log('王二小在放羊');
    });

    console.log('總共建立了'+ personCount + '我的');  //2

2.2 靜態屬性/方法

function Person(name, age, doFunc) {
        this.name = name;
        this.age = age;
        this.doFunc = doFunc;
        if (!Person.personCount) {
            Person.personCount = 0; //建立靜態屬性
        }
        Person.personCount++;   
    }
    //建立靜態方法
    Person.printPersonCount = function () {
        console.log('總共建立了'+ Person.personCount + '我的');
    };


    var p1 = new Person('sz', 18, function () {
        console.log('sz在上課');
    });
    var p2 = new Person('王二小', 18, function () {
        console.log('王二小在放羊');
    });

    Person.printPersonCount();

3 類型獲取

3.1 內置對象類型獲取

內置對象
• String
• Number
• Boolean
• Object
• Function
• Array
• Date
• RegExp
• Error數組

獲取根據本身聲明的構造函數建立的對象瀏覽器

console.info("-->str")
    var str = "aaa";
    console.log(typeof str);                            //string
    console.log(str.constructor.name);                  //String
    console.log(Object.prototype.toString.call(str));   //[object String]

    console.info("-->obj")
    var obj = { 'name': '張三' };
    console.log(typeof obj);                            //object
    console.log(obj.toString());                        //[object Object]
    console.log(obj.constructor.name);                  //Object
    console.log(Object.prototype.toString.call(obj));   //[object Object]

    console.info("-->arr")
    var arr = [1, 2, 3];
    console.log(typeof arr);                          //object  
    console.log(arr.toString());                      //1,2,3
    console.log(arr.constructor.name);                //Array  
    console.log(Object.prototype.toString.call(arr)); //[object Array]  

    console.info("-->date")
    var date = new Date();
    console.log(typeof date);                           //object  
    console.log(date.toString());                        //Wed Oct 09 2019 00:08:15 GMT+0800 (中國標準時間)
    console.log(date.constructor.name);                 //Date  
    console.log(Object.prototype.toString.call(date));  //[object Date]

3.2 自定義類型獲取

仍是使用 實例化對象.constructor.name函數

function Person(name, age) {
        // var this = new Object();  自定義類型系統都把this指向Object,因此Object.prototype.toString.call(...) 獲取都是 [object Object]
        this.name = name;
        this.age = age;
    }

    function Dog(name, age) {
        this.name = name;
        this.age = age;
    }

    function Cat(name, age) {
        this.name = name;
        this.age = age;
    }

    // 1. 實例化-->實例
    var p = new Person('zs', 18);
    var d = new Dog('小花', 8);
    var c = new Cat('小貓', 3);

    //  object
    console.log(typeof p);
    console.log(typeof d);
    console.log(typeof c);

    // [object Object]
    console.log(p.toString());
    console.log(d.toString());
    console.log(c.toString());

    // [object Object]
    console.log(Object.prototype.toString.call(p));
    console.log(Object.prototype.toString.call(d));
    console.log(Object.prototype.toString.call(c));

    console.log(p.constructor.name);    //Person
    console.log(d.constructor.name);    //Dog
    console.log(c.constructor.name);    //Cat

3.3 類型驗證 instanceof

類型驗證使用 instanceofpost

function Person(name, age) {
        this.name = name;
        this.age = age;
    }

    function Dog(name, age) {
        this.name = name;
        this.age = age;
    }

    function Cat(name, age) {
        this.name = name;
        this.age = age;
    }

    // 1. 實例化-->實例
    var p = new Person('zs', 18);
    var d = new Dog('小花', 8);
    var c = new Cat('小貓', 3);

    //true
    console.log(p instanceof Person);
    console.log(d instanceof Dog);
    console.log(c instanceof Cat);
    
    //true
    console.log(p instanceof Object);
    console.log(d instanceof Object);
    console.log(c instanceof Object);
    
    //false
    console.log(p instanceof Dog);  
    console.log(d instanceof Cat);
    console.log(c instanceof Person);

    console.log(p);
    console.log(d);
    console.log(c);
    
    //true
    console.log([] instanceof Object);

4 訪問函數原型對象

function Person(name, age) {
        this.name = name;
        this.age = age;
    }
    // 原型對象
    Person.prototype.run = function () {
        console.log('跑');
    };

4.1 方式一:函數名.prototype

console.log(Person.prototype);

瀏覽器學習

> {run: ƒ, constructor: ƒ}
    > run: ƒ ()
    > constructor: ƒ Person(name, age)
    > __proto__: Object

4.2 方式二:對象.__proto__(不推薦)

.__proto__看起來很像一個屬性,可是實際上它更像一個getter/setter測試

var p = new Person();
    console.log(p.__proto__);

瀏覽器

> {run: ƒ, constructor: ƒ}
    > run: ƒ ()
    > constructor: ƒ Person(name, age)
    > __proto__: Object

__proto__是一個非標準屬性
即ECMAScript中並不包含該屬性,這只是某些瀏覽器爲了方便開發人員開發和調試而提供的一個屬性,不具有通用性
建議:在調試的時候可使用該屬性,但不能出如今正式的代碼中

4.2.1 __proto__能夠設置

.__proto__是可設置屬性,可使用ES6的Object.setPrototypeOf(..)進行設置。然而,一般來講你不須要修改已有對象的[[Prototype]]。

var newYX = {
        'add': function () {
            console.log('sum');
        }
    };

    p.__proto__ = newYX;
    console.log(p.__proto__);

瀏覽器

> {add: ƒ}
    > add: ƒ ()
    > __proto__: Object

5 判斷原型對象是否存在某屬性

5.1 in 屬性

in 判斷一個對象, 是否擁有某個屬性(若是對象身上沒有, 會到原型對象裏面查找)

function Person(name, age) {
        this.name = name;
        this.age = age;
    }
    Person.prototype.address = '上海';

    var p = new Person('撩課', 18);
    // console.log(name in p); // false
    console.log('name' in p); // true
    console.log('address' in p); // true

5.2 hasOwnProperty 屬性

只到對象自身查找

function Person(name, age) {
        this.name = name;
        this.age = age;
    }
    Person.prototype.address = '上海';

    var p = new Person('張三', 20);
    console.log(p.hasOwnProperty('name')); // true
    console.log(p.hasOwnProperty('address')); // false

6 判斷一個對象的原型 isPrototypeOf 和 instanceOf

是的,它們執行相同的操做,都遍歷原型鏈以查找其中的特定對象。

二者的區別在於它們是什麼,以及如何使用它們,例如isPrototypeOf是對象上可用的函數。它容許您測試一個特定對象是否在另外一個對象的prototype鏈中,由於此方法是在object上定義的原型,它對全部對象均可用。
instanceof是一個操做符,它須要兩個操做數,一個對象和一個構造函數,它將測試傳遞的函數原型屬性是否存在於對象鏈上(經過[[HasInstance]](V)內部操做,僅在函數對象中可用)。

B.isPrototypeOf(a) 判斷的是A對象是否存在於B對象的原型鏈之中,檢查B

a instanceof B 判斷的是B.prototype是否存在與A的原型鏈之中,檢查B.prototype

isPrototypeOf() 與 instanceof 運算符不一樣。在表達式 "object instanceof AFunction"中,object 的原型鏈是針對 AFunction.prototype 進行檢查的,而不是針對 AFunction 自己。

function A () {
  this.a = 1;
}
function B () {
  this.b = 2;
}
B.prototype = new A();
B.prototype.constructor = B;

function C () {
  this.c = 3;
}
C.prototype = new B();
C.prototype.constructor = C;

var c = new C();

// instanceof expects a constructor function

c instanceof A; // true
c instanceof B; // true
c instanceof C; // true

// isPrototypeOf, can be used on any object
A.prototype.isPrototypeOf(c); // true
B.prototype.isPrototypeOf(c); // true
C.prototype.isPrototypeOf(c); // true

7 繼承(MDN)

7.1 定義 Teacher() 構造器函數

function Person(first, last, age, gender, interests) {
  this.name = {
    first,
    last
  };
  this.age = age;
  this.gender = gender;
  this.interests = interests;
};

Person.prototype.greeting = function() {
  console.log('Hi! I\'m ' + this.name.first + '.');
};


function Teacher(first, last, age, gender, interests, subject) {
  Person.call(this, first, last, age, gender, interests);   //用的是傳送給Teacher(),而不是Person()的值

  this.subject = subject;
}

var p = new Person("張","三",22,"男",["籃球","KTV"]);

var t = new Teacher("李","四",18,"男",["英語","計算機"],"編程");

t.greeting(); 
//TypeError: t.greeting is not a function
//這裏 Teacher 不能獲取 greeting...

這裏主要調用Person.call(this, first, last, age, gender, interests);定義 Teacher() 構造器函數,
咱們頗有效的在Teacher()構造函數裏運行了Person()構造函數,獲得了和在Teacher()裏定義的同樣的屬性,可是用的是傳送給Teacher(),而不是Person()的值(咱們簡單使用這裏的this做爲傳給call()的this,意味着this指向Teacher()函數)。

7.2 設置 Teacher() 的原型

咱們須要讓Teacher()從Person()的原型對象裏繼承方法

Teacher.prototype = Object.create(Person.prototype);

在這個例子裏咱們用這個函數來建立一個和Person.prototype同樣的新的原型屬性值(這個屬性指向一個包括屬性和方法的對象),而後將其做爲Teacher.prototype的屬性值。

7.2.1 爲何不能使用 Teacher.prototype = Person.prototype

看看比較

7.3 設置 Teacher() 構造器引用

如今Teacher()的prototype的constructor屬性指向的是Person(), 這是由咱們生成Teacher()的方式決定的。

這或許會成爲很大的問題,因此咱們須要將其正確設置——您能夠回到源代碼,在底下加上這一行代碼來解決:

Teacher.prototype.constructor = Teacher;

注:每個函數對象(Function)都有一個prototype屬性,而且只有函數對象有prototype屬性,由於prototype自己就是定義在Function對象下的屬性。當咱們輸入相似var person1=new Person(...)來構造對象時,JavaScript實際上參考的是Person.prototype指向的對象來生成person1。另外一方面,Person()函數是Person.prototype的構造函數,也就是說Person===Person.prototype.constructor(不信的話能夠試試)。

在定義新的構造函數Teacher時,咱們經過function.call來調用父類的構造函數,可是這樣沒法自動指定Teacher.prototype的值,這樣Teacher.prototype就只能包含在構造函數裏構造的屬性,而沒有方法。所以咱們利用Object.create()方法將Person.prototype做爲Teacher.prototype的原型對象,並改變其構造器指向,使之與Teacher關聯。

任何您想要被繼承的方法都應該定義在構造函數的prototype對象裏,而且永遠使用父類的prototype來創造子類的prototype,這樣纔不會打亂類繼承結構。

new 與 Object.create 區別

  1. new的話只能是class(即函數),可是Object.create()的參數能夠爲對象也能夠爲函數,
  2. 若是Object.create()的參數是對象的話,那麼新的對象會繼承原對象的屬性;若是參數是類(函數)的話,Object.create()的參數爲類(函數)原型,如您所說的,它沒有綁定this,並無屬性被繼承。
  3. Object.create(null)能夠實現一個空對象,即沒有原型的對象,但使用new就辦不到。

7.4 多對象繼承

Object.assign(目標對象, ...源對象) 方法用於將全部可枚舉屬性的值從一個或多個源對象複製到目標對象。它將返回目標對象。

//再建立一個基類
function Animal(age) {
 this.age = age;
}
Animal.prototype.say = function(language) { 
    console.log('you say ' + language);
}

function Student(name, sex, age) {
    Person.call(this, name, sex);
    Animal.call(this, age);
}
//原型鏈拼接
Student.prototype = Object.create(Person.prototype);
Object.assign(Student.prototype, Animal.prototype);
Student.prototype.constructor = Student;
Student.prototype.getInfo = function() {
    console.log('getInfo: [name:' + this.name + ', sex:' + this.sex + ', age:' +this.age + '].');
};
var s = new Student('coco', 'femal', 25);

7.5 JS繼承練習

function Animal(name, age) {
        this.name = name;
        this.age = age;
    }

    Animal.prototype.eat = function () {
        console.log('吃');
    };

    Animal.prototype.run = function () {
        console.log('跑');
    }

    function Person(name, age, job) {
        Animal.call(this, name, age);
        this.job = job;
    }

    Person.prototype = Object.create(Animal.prototype);
    Person.prototype.constructor = Person;
    
    //Person.prototype.jump 原型方法必須寫在繼承後
    Person.prototype.jump = function () {
        console.log('跳');
    };
    function Student(name, age, job, className) {
        Animal.call(this, name, age);
        Person.call(this, name, age, job);
        this.className = className;
    }

    Student.prototype = Object.create(Person.prototype);
    Student.prototype.constructor = Student;
    //原型方法必須寫在繼承後
    Student.prototype.study = function () {
        console.log('學習');
    }


    function Adolescent(name, age, job, className,sex) {
        Animal.call(this, name, age);
        Person.call(this, name, age, job);
        Person.call(this, name, age, job,className);
        this.sex = sex;
    }

    Adolescent.prototype = Object.create(Student.prototype);
    Adolescent.prototype.constructor = Adolescent;
    //原型方法必須寫在繼承後
    Student.prototype.play = function () {
        console.log('跳繩');
    }

    this.a2 = new Adolescent('張同窗',18,'學生','高2.1版',"女");
    this.s1 = new Student('張同窗',18,'學生','高2.1版');
    this.p1 = new Person('王老師',26,'教師');
    this.a1 = new Animal('校長',44);

原型方法必須寫在設置原型 XXX.prototype = Object.create(...)和設置構造器引用XXX.prototype.constructor = xxx以後,不容會丟失。

7.6 內置對象的擴展

方式1(失敗)

直接給對象動態添加屬性和方法
    弊端:
        若是操做不少個對象, 則沒法共享
        代碼比較冗餘

例子:arr2的run不能執行~

var arr = [1,2,3];
    arr.run = function () {
        console.log('跑');
    }
    arr.run();


    var arr2 = [2];
    arr.run = function () {
        console.log('跑');
    }

方式2(容易覆蓋)

直接給Array原型對象添加方法
    弊端:
        可能會產生覆蓋的狀況

例子:覆蓋push

Array.prototype.push = function () {
        console.log('跑');
    };

    var arr = [1,2,3];
    arr.push();


    var arr2 = [2];
    arr2.push();

方式3(推薦)

提供一個新的構造函數
修改構造函數的原型指向爲數組的原型對象
    爲了可以獲取數組裏面的屬性和方法
    問題: 
        依然會修改數組原型對象內容
優化
    原型對象就是一個對象
    能夠直接根據Array建立一個對象, 給新建立的函數原型對象進行賦值
function MyArray() {}
    MyArray.prototype = new Array();
    MyArray.prototype.run = function () {
        console.log('跑');
    };
    var arr = new MyArray();
    arr.run();

8 深拷貝與淺拷貝

8.1 淺拷貝

8.1.1 遍歷

var obj1 = {name: '張三', age: 18};
    var obj2 = {};

    for (var key in obj1) {
        obj2[key] = obj1[key];
    }
    console.log(obj2);

8.1.2 Object.assign(目標對象, ...源對象)

方法用於將全部可枚舉屬性的值從一個或多個源對象複製到目標對象。它將返回目標對象。

var obj3 = {className: '六班'};
    Object.assign(obj3, obj1, {address: '重慶'});
    console.log(obj3);

8.2 深拷貝

深拷貝/淺拷貝

區別:在於對引用值的處理

  • 淺拷貝, 直接拷貝一份地址
  • 深拷貝-拷貝地址對應的具體內容

深拷貝實現

  1. 提供一個函數,兩個參數(元對象,要拷貝屬性的對象)
  2. 在函數中先檢查第一個參數是否有值,若是沒有值那麼就初始化一個空的對象
  3. for..in循環來變量參數2
    1. 檢查當前的屬性值是什麼類型
    2. 若是是值類型,那麼就直接拷貝賦值
    3. 若是是引用類型,那麼就再調用一次這個方法,去內部拷貝這個對象的全部屬性
    var obj = {
         name: "唐三三",
         age: 30,
         friend: ['張森', '夏贏'],
         address: {
             city: '重慶',
             county: '渝北'
         },
         play: function () {
             console.log(this.name + '喜歡桌球');
         }
     }
    
     //deep Copy
     function deepCopySource2Target(source, target) {
         for (var key in source) {
             var sourceValue = source[key];
    
             if (!(sourceValue instanceof Object)) {
                 //value object
                 target[key] = sourceValue;
             }
             else {
                 //object object
                 var temp = new sourceValue.constructor;
                 deepCopySource2Target(sourceValue, temp);
                 target[key] = temp;
             }
         }
     }
    
     var newObj = {};
     deepCopySource2Target(obj, newObj);
     console.log(newObj);
     newObj.play();

    參考:

    js繼承實現之Object.create - 太陽的眼睛
    javascript中繼承-MDN
    繼承與原型鏈-MDN
    ObjectPlayground.com - 一個很是有用的、用於瞭解對象的交互式學習網站。
    完全搞懂JavaScript中的繼承- 掘金
相關文章
相關標籤/搜索