日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

JavaScript 之封装、继承、多态

發布時間:2023/12/20 javascript 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JavaScript 之封装、继承、多态 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

封裝

??封裝的目的是要隱藏對象的屬性和實現細節,僅對外公開接口,控制在程序中屬性的讀和修改的訪問級別。在許多語言中也許提供了 private、public、protected 等關鍵字來提供不同的訪問權限,但是 JavaScript 沒有提供對這些關鍵字的支持,只能依賴變量的作用域來實現封裝特性,而且也只能模擬出 public 和 private 這兩種封裝性。

??在 ES5 中實現封裝:使用閉包方式實現

var Person = (function() {// 靜態私有變量var shemale = 0;var abnormalityPerson = 0;// 靜態私有方法function privateMethod() {console.log("調用靜態私有方法 privateMethod.");}// 返回構造函數function _person(newId, newName, newAge, newGender) {// 私有變量var name, age, gender;// 私有方法 (對私有屬性的操作)function checkId(id) {console.log("私有方法 checkId, id = " + id);}// 特權方法this.getName = function () {return name;}this.setName = function (newName) {name = newName; }this.getAge = function () {return age;}this.setAge = function (newAge) {if(newAge >= 0 && newAge <= 120) {age = newAge; } else {abnormalityPerson ++;}}this.getGender = function () {return gender;}this.setGender = function (newGender) {if(newGender != "male" && newGender != "female") {shemale ++;} else {gender = newGender;} }// 共有屬性this.id = newId;// 公有方法this.getParents = function () {console.log("公有方法 getParents 被調用。");}// 構造器this.setName(newName);this.setAge(newAge);this.setGender(newGender);// console.log("abnormalityPerson count: " + abnormalityPerson); // console.log("shemale count: " + shemale);}// 構建原型_person.prototype = {// 靜態共有屬性staticPerson: false,// 靜態共有方法play : function () {console.log(this.getName() + "play.");}}// 返回類return _person; })();

測試代碼:

var person1 = new Person(1, "zhangsan", 15, "male"); var person2 = new Person(3, "lisi ", 14, "female"); //var person3 = new Person(1, "xiaohong", -1, "male"); //var person4 = new Person(3, "xiaowang ", 14, "shemale");console.log(person1) console.log(person2)person1.play(); person2.play();

打印結果為:

總結: 通過 JavaScript 函數級作用域的特征來實現在函數內部創建外界訪問不到的私有變量和私有方法。通過 new 關鍵字實例化對象時,this 指向的屬性和方法都會得到相應的創建,而通過 property 繼承的屬性或者方法是每個對象都能通過 property 訪問到,所以我們每次通過類創建一個新對象時這些屬性和方法不會再次創建。

在 ES6 中定義類:

const _name = Symbol('name'); // 使用 Symbol 創建對象私有成員 const _age = Symbol('age');class Person {// 原型方法static func () {console.log("static func.")}// 構造方法constructor(newName, newAge) {this[_name] = newName; this[_age] = newAge; }getName () {return this[_name];}setName (name) {this[_name] = name;}getAge () {return this[_age];}setAge (age) {this[_age] = age;}// 實例方法play () {console.log(this[_name] + "play.");} }

測試:

let person1 = new Person("xiaoming", 12); let person2 = new Person("xiaohong", 13);Person.func(); console.log(person1.getName()); person1.setName("xiaohuang"); console.log(person1.getName()); console.log(person2); person1.play(); person2.play();

結果顯示:

總結:ES6 中,prototype 仍舊存在,在類中定義方法,其實方法還是定義在 prototype 上的。由于Symbol可作為屬性名并且不能被for…in…、Object.key()、Object.getOwnPropertyNames遍歷,所以可以實現私有屬性。

繼承

1.類式繼承

// 聲明父類 function SuperClass () {this.superVal = 1; } // 為父類添加公有方法 SuperClass.property.getSuperVal = function () {return this.superVal; } // 聲明子類 function SubClass () {this.subVal = 2; } // 繼承父類 SubClass.property = new SuperClass(); // 為子類添加公有方法 SubClass.property.getSubVal = function () {return this.subVal; }

??類的原型對象的作用就是為類的原型添加共有方法,但類不能直接訪問這些屬性和方法,必須通過原型 prototype 來訪問。我們實例化一個父類的時候,新創建的對象復制了父類的構造函數內的屬性和方法并且將原型 _ proto _ 指向了父類的原型對象,這樣就擁有了父類的原型對象上的屬性和方法,并且這個新創建的對象可直接訪問到父類原型對象上的屬性與方法。 如果將這個新創建的對象復值給子類的原型,那么子類的原型就可以訪問到父類的原型屬性和方法。

但是這種類式繼承有兩個缺點:

??其一,由于子類通過其原型 prototype 對父類實例化,繼承了父類。所以說父類中的共有屬性要是引用類型,就會在子類中被所有實例共用,因此一個子類的實例更改子類原型從父類構造函數中繼承而來的公有屬性就會直接影響到其他子類:例如:

function SuperClass () {this.superVal = [1,2,3]; } function SubClass () {}SubClass.prototype = new SuperClass();var instance1 = new SubClass(); var instance2 = new SubClass();console.log(instance1.superVal); // 1,2,3 instance1.superVal.push(4); console.log(instance2.superVal); // 1,2,3,4

??其二,由于子類實現的繼承是靠其原型 prototype 對父類的實例化實現的,因此在創建父類的時候,是無法向父類傳遞參數的,因而在實例化父類的時候也無法對父類構造函數內的屬性進行初始化。針對這種情況我們可以使用構造函數繼承。

2.構造函數繼承

function Person (name, age) {this.name = name;this.age = age;this.arrays = [1,2,3]; } Person.prototype.eat = function () {console.log('eat'); }function Student (name, age, teacher) {Person.call(this, name, age);this.teacher = teacher; }var stu1 = new Student('xiaoming', 12, "teacher1"); var stu2 = new Student('xiaohong', 13, "teacher2");console.log(stu1); console.log(stu2); stu1.arrays.push(4); console.log(stu1.arrays); // 1 2 3 console.log(stu2.arrays); // 1 2 3 4

??stu1修改了arrays 并沒有影響到 stu2,只因為 call 這個方法可以更改函數的作用環境,因此在子類中,對 Person 調用這個方法就是將子類中的變量在父類中執行一遍,由于父類中是給 this 綁定屬性的,因此子類自然也就繼承父類的共有屬性。由于這種類型的繼承沒有涉及原型 prototype ,所以父類的原型方法自然不會被子類繼承,子類都不包含 eat 這個函數。為了綜合這兩種模式,后來有了組合繼承。

3. 組合繼承

function Person (name, age) {this.name = name;this.age = age;this.arrays = [1,2,3]; } Person.prototype.eat = function () {console.log('eat.'); }function Student (name, age, teacher) {Person.call(this, name, age);this.teacher = teacher; }Student.prototype = new Person(); // 第一次調用父類構造函數 Student.prototype.study = function () {console.log("study."); }var stu1 = new Student('xiaoming', 12, "teacher1"); // 第二次調用父類構造函數

??組合繼承看似完美,但是還是存在瑕疵,使用組合繼承我們可以發現父類的構造函數會被調用兩次,一次是在子類原型實現類式繼承時調用了一次父類構造函數,一次是在使用構造函數時又調用了一次,也就是new Student() 的時候,所以還不是最完美的方式。

4.原型式繼承

function inheritObject(o) {// 聲明一個過渡函數對象function F() {}// 過渡對象的原型繼承父對象F.prototype = o;// 返回過渡對象的一個實例,該實例的原型繼承了父對象return new F(); }

??其實就是對類式繼承做了一次封裝,對于引用類型的屬性還是會被共用。為了解決這個問題,在此基礎上做了一次增強,推出了寄生式繼承。

5.寄生式繼承

var person = {name = "xiaoming",arrays: [1, 2, 3] }; function createPerson(obj) {// 通過原型繼承方式創建新對象var o = new inheritObject(obj);// 拓展新對象o.getName = function () {console.log(name);}// 返回拓展后的對象return o; }

??寄生式繼承就是對原型繼承的第二次封裝,并且在這第二次封裝過程中對繼承的對象進行了拓展,這樣新創建的對象不僅有父類中的屬性和方法,也可以拓展自己的屬性和方法。

6. 寄生組合式繼承

是寄生式繼承與構造函數繼承的組合。

function inheritPrototype (subClass, superClass) {// 復制一份父類的原型副本保存在變量中var p = inheritObject(superClass.prototype);// 修正因為重寫子類原型導致子類的 constructor 屬性被修改p.constructor = subClass;// 設置子類的原型subClass.prototype = p; }

??這里我們只繼承父類的原型,因此我們需要的就是父類原型的一個副本,而這個副本我們我們可以通過原型繼承便可得到,如果直接賦值給子類會有問題,因為對父類原型對象復制得到的復制對象 p 中的 constructor 指向的不是 subClass 子類對象,因此在寄生式繼承中要對復制對象 p 做一次增強,修復其 contructor 屬性指向不正確的問題,最后將得到的復制對象 p 賦值給子類的原型,這樣子類的原型就繼承了父類的原型并且沒有執行父類的構造函數。

測試:

function Person (name, age) {this.name = name;this.age = age;this.arrags = [1, 2, 3];console.log("111"); }Person.prototype.eat = function () {console.log("eat."); }function Student (name, age, teacher) {Person.call(this, name, age);this.teacher = teacher; }inheritPrototype(Student, Person); Student.prototype.study = function () {console.log("study."); }var stu1 = new Student('xiaoming', 13, "teacher1"); var stu2 = new Student('xiaohong', 13, "teacher2");console.log(stu1); console.log(stu2);

結果顯示:

多態

??多態的實際含義是:同一操作作用于不同的對象上面,可以產生不同的解釋和不同的執行結果。換句話說,給不同的對象發送同一個消息的時候,這些對象會根據這個消息分別給出不同的反饋。
??假設我們要編寫一個地圖應用,現在有兩家可選的地圖 API 提供商供我們接入自己的應用。目前我們選擇的是谷歌地圖,谷歌地圖的 API 中提供了 show 方法,負責頁面上渲染地圖。示例代碼:

var googleMap = {show: function () {console.log('開始渲染谷歌地圖');} };var renderMap = function () {googleMap.show(); }renderMap();

??后來因為某些原因,要把谷歌地圖換成百度地圖,為了讓 renderMap 函數保持一定的彈性,我們用一些條件分支來讓 renderMap 函數同時支持兩種地圖:

var googleMap = {show: function () {console.log('開始渲染谷歌地圖');} };var baiduMap = {show: function () {console.log('開始渲染百度地圖');} };var renderMap = function (type) {if( type === 'google' ) {googleMap.show();} else if ( type === 'baidu' ) {baiduMap.show();} }renderMap('baidu'); renderMap('google');

??需求是會隨時變化的,如果哪一天需要增加另外一種地圖,我們又要去改動 renderMap 函數,繼續增加條件分支,使代碼不易維護。對此我們可以把相同的部分抽象出來:

var renderMap = function (map) {if ( map.show instanceof Function ) {map.show();} }renderMap(googleMap); renderMap(baiduMap);

??當我們向谷歌地圖對象和百度地圖對象分別發出 “渲染地圖” 的消息時,會分別調用它們的 show 方法,就會產生各自不同的執行結果,如果要新加地圖,只需增加新的地圖對象即可,而不需要改動 renderMap 函數,實現了多態性。

總結

以上是生活随笔為你收集整理的JavaScript 之封装、继承、多态的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。