JavaScript原型與繼承

寫在之前

距離上次整理日記整整一個周了溺忧,本來想再上周把這部分的內容完成咏连,兌現(xiàn)之前吹過的牛皮。遲遲沒有寫一是因為上周項目比較忙鲁森,另一個是這一部分真的不好寫祟滴,我寫著寫著感覺這一塊的內容都不確定了(自己的基礎真的有點差),去驗證自己的想法的時候花了不少時間歌溉。這次的內容以《Javascript高級程序設計》第六章為主垄懂,先說原型與原型鏈,再說常用的繼承的方式以及優(yōu)缺點痛垛,最后結合實例看原型與繼承在網頁編程中的實際應用草慧。

原型基礎

原型對象

Javascript是面向對象的語言,在JS的世界里面萬物皆對象匙头。每個對象都有一個原型 prototype 對象漫谷,通過函數創(chuàng)建的對象也將擁有這個原型對象。原型是一個指向對象的指針蹂析。

  • 可以將原型理解為對象的父親抖剿,對象從原型對象繼承屬性。
  • 原型就是對象识窿,除了是某個對象的父母之外沒什么特別之處斩郎。
  • Object是所有對象的爸爸(爺爺,祖宗)喻频,所以對象都可以使用 toString/toValues/isPrototypeOf等方法(子代可以使用父輩的資產)缩宜。
  • 使用原型可以解決,通過構造函數創(chuàng)造對象的時復制多個函數造成的內存占用問題。
  • 原型包含constructor屬性锻煌,指向構造函數
  • 對象包含 __proto__指向他的原型對象
    下例就是使用數組原型對象的concat 方法完成的連接操作妓布。
let arr = ["a"];
console.log(arr.concat("b"));

如果 console.dir(arr) 我們就會發(fā)現(xiàn)他的結構信息

圖片1

可以看到__proto__指向原型對象,原型對象包含 constructor 它指向構造函數宋梧。上面的內容可以 arr.__proto__ 指向 Array.prototype 即 arr.__proto__ === Array.protptypeArray.prototype.constructor 指向 Array()

默認情況下創(chuàng)建的對象都是有原型的匣沼。obj的原型都為元對象Object,可以用 Object.getPrototypeOf(obj)檢驗捂龄,它會返回 true释涛。

圖片2

我們也可以創(chuàng)建一個極簡對象(純數據字典對象),沒有原型(原型為null)

let obj = Object.create(null,{
  name:{
    value:'zhangsan'
  }
})
console.log(obj.hasOwnProperty('name')); //Error

prototype用于實例對象使用倦沧,__prototype__用于函數對象使用唇撬。JS萬物皆對象,構造函數User 本身就是一個對象展融,通過構造函數User new出來的對象就是構造函數User的實例對象窖认。

function User() {}
User.__proto__.view = function() {
  console.log("User function view method");
};
User.view(); //User function view method

User.prototype.show = function() {
  console.log("zhansan");
};
let user = new User();
user.show(); // zhangsan
console.log(User.prototype === user.__proto__); //true

下面是原型關系分析圖

圖片3

下面是使用構造函數床架對象的原型體現(xiàn)

  • 構造函數擁有原型
  • 創(chuàng)建對象時構造函數把原型對象賦予實例對象


    圖片4
function User() {}
let user = new User();
console.log(user.__proto__ === User.prototype); // true

下面使用數組會產生多級繼承

let arr = [];
console.log(arr.__proto__ === Array.prototype); // true
console.log(Array.prototype.__proto__ === Object.prototype); //true

圖片5

可以使用setPrototypeOfgetPrototypeOf 獲取與設置原型

let child = {};
let parent = {name:'parent'}
Object.setPrototype(child,parent);
console.log(Object.getPrototypeOf(child); //指向parent

下面使用自動構造函數創(chuàng)建的對象的原型體現(xiàn)可以參考圖一。

function User(){}
let user = new User();

上面說到構造函數存在于原型對象中告希,是指向構造函數的引用

function User() {
  this.show = function() {
    return "show method";
  };
}
const obj = new User(); //true
console.log(obj instanceof User); //true

console.log(obj.__proto__.constructor === User); //true
console.log(User.prototype.constructor === User); //true

const obj2 = new obj.constructor();
console.dir(obj2.show()); //show method

既然 constructor 是指向構造函數的引用扑浸,那我們就可以使用 constructor 創(chuàng)建對象

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

function createByObject(obj, ...args) {
  const constructor = Object.getPrototypeOf(obj).constructor;
  return new constructor(...args);
}

let obj1 = new User("sinochem");
let obj2 = createByObject(obj1, "sinochemtech", 12);
console.log(xj);

原型鏈

通過引用類型的原型,繼承另一個引用類型的屬性和方法燕偶,這個也是實現(xiàn)繼承的步驟

圖片6

使用Object.setPrototypeOf可是設置對象的原型喝噪,下面的示例中繼承關系為 obj > child > parent。

Object.getPrototype 用于獲取一個對象的原型杭跪。

let obj = {
  name: "zhangsan"
};
let child = {
  ccc: "child-child"
};
let parent = {
  ppp: "parent-parent"
};
//讓obj繼承hd,即設置obj的原型為hd
Object.setPrototypeOf(obj, child);
Object.setPrototypeOf(child, parent);
console.log(obj.ccc); // child-child
console.log(Object.getPrototypeOf(child) == parent); //true

原型檢測

instanceof 檢測構造函數的 prototype 屬性是否出現(xiàn)在某個實例對象的原型鏈上

function A() {}
function B() {}
function C() {}

const c = new C();
B.prototype = c;
const b = new B();
A.prototype = b;
const a = new A();

console.dir(a instanceof A); //true
console.dir(a instanceof B); //true
console.dir(a instanceof C); //true
console.dir(b instanceof C); //true
console.dir(c instanceof B); //false

使用isPrototypeOf檢測一個對象是否在另一個對象的原型鏈中

const a = {};
const b = {};
const c = {};

Object.setPrototypeOf(a, b);
Object.setPrototypeOf(b, c);

console.log(b.isPrototypeOf(a)); //true
console.log(c.isPrototypeOf(a)); //true
console.log(c.isPrototypeOf(b)); //true

屬性遍歷

使用in 檢測原型鏈上是否存在屬性驰吓,使用 hasOwnProperty 只檢測當前對象

let a = { url: "baidu.com" };
let b = { name: "百度" };
Object.setPrototypeOf(a, b);
console.log("name" in a);
console.log(a.hasOwnProperty("name")); //true
console.log(a.hasOwnProperty("url")); //true

使用 for/in 遍歷時同時會遍歷原型上的屬性如下例:

let parent = { name: "zhangsan" };
let child = Object.create(parent, {
  url: {
    value: "www.baidu.com",
    enumerable: true
  }
});
for (const key in child) {
  console.log(key);
}

hasOwnProperty 方法判斷對象是否存在屬性涧尿,而不會查找原型。所以如果只想遍歷對象屬性使用以下代碼:

let parent = { name: "后盾人" };
let child = Object.create(parent, {
  url: {
    value: "baidu.com",
    enumerable: true
  }
});
for (const key in child) {
  if (child.hasOwnProperty(key)) {
    console.log(key);
  }
}

借用原型

使用 callapply 可以借用其他原型方法完成功能檬贰。

下面的bar對象不能使用max方法姑廉,但可以借用 foo 對象的原型方法

let foo = {
  data: [1, 2, 3, 4, 5]
};
Object.setPrototypeOf(foo, {
  max: function(data) {
    return data.sort((a, b) => b - a)[0];
  }
});
console.log(hd.max(hd.data));

let bar = {
  lessons: { js: 100, php: 78, node: 78, linux: 125 }
};
console.log(foo.__proto__.max.call(bar, Object.values(bar.lessons)));

因為 Math.max就是獲取最大值的方法,所以代碼可以再次簡化

let foo = {
  data: [1, 2, 3, 4, 5]
};
console.log(Math.max.apply(null, Object.values(foo.data)));

let bar = {
  lessons: { js: 100, php: 78, node: 78, linux: 125 }
};
console.log(Math.max.apply(bar, Object.values(bar.lessons)));

下面是獲取設置了 class 屬性的按鈕翁涤,但DOM節(jié)點不能直接使用數組的filter 等方法桥言,但借用數組的原型方法就可以操作了。

<body>
  <button message="foo" class="red">FOO</button>
  <button message="bar">BAR</button>
</body>
<script>
  let btns = document.querySelectorAll("button");
  btns = Array.prototype.filter.call(btns, item => {
    return item.hasAttribute("class");
  });
</script>

原型總結

函數也是對象葵礼,但是比較特殊号阿,有多個原型(__proto__prototype)兩個一定要分清楚。

通常說為構造函數設置原型指的是設置 prototype 當使用構造函數創(chuàng)建對象時把這個原型賦給這個對象鸳粉。

function User(name) {
  this.name = name;
}
User.prototype = {
  show() {
    return this.name;
  }
};
let user = new User("zhangsan");
console.log(user.show());

函數默認prototype 指包含一個屬性 constructor 的對象扔涧,constructor 指向當前構造函數

function User(name) {
  this.name = name;
}
let user = new User("向軍");
console.log(uer);
console.log(User.prototype.constructor == User); //true
console.log(user.__proto__ == User.prototype); //true

let lisi = new user.constructor("李四");
console.log(lisi.__proto__ == user.__proto__); //true

原型中保存引用類型會造成對象共享屬性,所以一般只會在原型中定義方法。

function User() {}
User.prototype = {
  lessons: ["JS", "VUE"]
};
const lisi = new User();
const wangwu = new User();

lisi.lessons.push("CSS");

console.log(lisi.lessons); //["JS", "VUE", "CSS"]
console.log(wangwu.lessons); //["JS", "VUE", "CSS"]

為Object原型對象添加方法枯夜,將影響所有函數

<body>
  <button onclick="this.hide()">BUTTON</button>
</body>
<script>
  Object.prototype.hide = function() {
    this.style.display = "none";
  };
</script>

了解了原型后可以為系統(tǒng)對象添加方法弯汰,比如為字符串添加了一截斷函數。

String.prototype.truncate = function (len = 5) {
    return this.length <= len ? this : this.substr(0, len) + '...';
}
console.log('1234567890'.truncate(3)); //123...

使用 Object.create創(chuàng)建一個新對象時使用現(xiàn)有對象做為新對象的原型對象湖雹。它第一個參數必須是一個對象或者null(沒有原型的對象)咏闪。

使用Object.create 設置對象原型

let user = {
  show() {
    return this.name;
  }
};

let obj = Object.create(user);
obj.name = "zhangsan";
console.log(obj.show());

在設置時使用第二個參數設置新對象的屬性

let user = {
  show() {
    return this.name;
  }
};
let obj = Object.create(user, {
  name: {
    value: "后盾人"
  }
});
console.log(obj);

在實例化對象上存在__proto__ 記錄了原型,所以可以通過對象訪問到原型的屬性或方法摔吏。

  • __proto__ 不是對象屬性鸽嫂,理解為prototypegetter/setter 實現(xiàn),他是一個非標準定義
  • __proto__ 內部使用getter/setter 控制值舔腾,所以只允許對象或null
  • 建議使用 Object.setPrototypeOfObject.getProttoeypOf 替代 __proto__

下面修改對象的 __proto__ 是不會成功的溪胶,因為_proto__ 內部使用getter/setter 控制值,所以只允許對象或null稳诚。

let obj = {};
obj.__proto__ = "123";
console.log(obj);

下面定義的__proto__ 就會成功哗脖,因為這是一個極簡對象,沒有原型對象所以不會影響__proto__賦值扳还。

let obj = Object.create(null);
obj.__proto__ = "123";
console.log(obj); //{__proto__: "123"}

下面通過改變對象的 __proto__ 原型對象來實現(xiàn)繼承才避,繼承可以實現(xiàn)多層,

let person = {
  name: "zhangsan"
};
let bar = {
  show() {
    return this.name;
  }
};
let foo = {
  handle() {
    return `用戶: ${this.name}`;
  }
};
bar.__proto__ = foo;
person.__proto__ = bar;
console.log(person.show());
console.log(person.handle());
console.log(person);

構造函數中的 __proto__ 使用

function User(name, age) {
  this.name = name;
  this.age = age;
}
User.prototype.show = function () {
    return `姓名:${this.name},年齡:${this.age}`;
};
let lisi = new User('李四', 12);
let wangwu = new User('王武', 16);
console.log(lisi.__proto__ == User.prototype); //true

可以使用 __proto__Object.setPrototypeOf 設置對象的原型氨距,使用Object.getProttoeypOf 獲取對象原型桑逝。

function Person() {
  this.getName = function() {
    return this.name;
  };
}
function User(name, age) {
  this.name = name;
  this.age = age;
}
let lisi = new User("李四", 12);
Object.setPrototypeOf(lisi, new Person());
console.log(lisi.getName()); //李四

對象設置屬性,只是修改對象屬性并不會修改原型屬性俏让,使用hasOwnProperty 判斷對象本身是否含有屬性并不會檢測原型楞遏。

function User() {}
const lisi = new User();
const wangwu = new User();

lisi.name = "李四";
console.log(lisi.name); //李四
console.log(lisi.hasOwnProperty("name")); //true

//修改原型屬性后
lisi.__proto__.name = "張三";
console.log(wangwu.name); //張三

//刪除對象屬性后
delete lisi.name;
console.log(lisi.hasOwnProperty("name")); //false
console.log(lisi.name); // 張三

使用 in 會檢測原型與對象,而 hasOwnProperty 只檢測對象首昔,所以結合后可判斷屬性是否在原型中

使用建議:通過前介紹我們知道可以使用多種方式設置原型

  1. prototype 構造函數的原型屬性
  2. Object.create 創(chuàng)建對象時指定原型
  3. __proto__ 聲明自定義的非標準屬性設置原型寡喝,解決之前通過 Object.create 定義原型,而沒提供獲取方法
  4. Object.setPrototypeOf 設置對象原型

這幾種方式都可以管理原型勒奇,一般以我個人情況來講使用 prototype 更改構造函數原型预鬓,使用 Object.setPrototypeOfObject.getPrototypeOf 獲取或設置原型。

構造函數

原型屬性

構造函數在被new 時把構造函數的原型(prototype)賦值給新對象赊颠。如果對象中存在屬性將使用對象屬性格二,不再原型上查找方法。

構造函數只會產生一個原型對象

function foo() {
  this.show = function() {
    return "show in object";
  };
}
foo.prototype.show = function() {
  return "show in prototype";
};
const obj = new foo();
console.log(obj.show());

對象的原型引用構造函數的原型對象竣蹦,是在創(chuàng)建對象時確定的顶猜,當構造函數原型對象改變時會影響后面的實例對象。

function foo() {}
foo.prototype.name = "foofoo";
const obj1 = new foo();
console.log(obj1.name); //foofoo

foo.prototype = {
  name: "123"
};
const obj2 = new hd();
console.dir(obj2.name); //123

以下代碼直接設置了構造函數的原型將造成 constructor 丟失

function User(name) {
  this.name = name;
}
User.prototype = {
  show: function() {}
};

let u1 = new User("u1u1");
let u2 = new u1.constructor("u2u2");
console.log(u2); //String {"u2u2"}

正確的做法是要保證原型中的 constructor指向構造函數

function User(name) {
  this.name = name;
}
User.prototype = {
  constructor: User,
  show: function() {}
};

let u1 = new User("u1u1");
let u2 = new hd.constructor("u2u2");
console.log(u2);

構造函數的優(yōu)化使用

使用構造函數會產生函數復制造成內存占用痘括,及函數不能共享的問題驶兜。

function User(name) {
  this.name = name;
  this.get = function() {
    return this.name;
  };
}
let lisi = new User("小明");
let wangwu = new User("王五");
console.log(lisi.get == wangwu.get); //false

將方法定義在原型上為對象共享,解決通過構造函數創(chuàng)建對象函數復制的內存占用問題

function User(name) {
  this.name = name;
}
User.prototype.get = function() {
  return "姓名:" + this.name;
};
let lisi = new User("小明");

let wangwu = new User("王五");
console.log(lisi.get == wangwu.get); //true
//通過修改原型方法會影響所有對象調用,因為方法是共用的
lisi.__proto__.get = function() {
  return "姓名123:" + this.name;
};
console.log(lisi.get());
console.log(wangwu.get());

下面演示使用原型為多個實例共享屬性

function User(name, age) {
  this.name = name;
  this.age = age;
  this.show = () => {
    return `你在${this.site}的姓名:${this.name}抄淑,年齡:${this.age}`;
  }
}
User.prototype.site = '中化';
let lisi = new User('李四', 12); 
let xiaoming = new User('小明', 32);

console.log(lisi.show()); //你在中化的姓名:李四屠凶,年齡:12
console.log(xiaoming.show()); //你在中化的姓名:小明,年齡:32

使用Object.assign一次設置原型方法來復用肆资,后面會使用這個功能實現(xiàn)Mixin模式

function User(name, age) {
  this.name = name;
  this.age = age;
}
Object.assign(User.prototype, {
  getName() {
      return this.name;
  },
  getAge() {
      return this.age;
  }
});
let lisi = new User('李四', 12);
let xiaoming = new User('小明', 32);
console.log(lisi.getName()); //李四
console.log(lisi.__proto__)

通過上面這種方法設置可以避《JavaScript高級程序設計》提到了通過原型字面量批量設置原型帶來的構造函數指針問題和如果在字面量設置原型之前在原型上設置方法的方法丟失問題(這是一個順序問題矗愧,因為原型指向了新對象,原原型對象就不再生效了)郑原。如果想了解那一部分的內容還是建議讀一下高級編程這本書唉韭。

繼承

體驗繼承

下面為 Stu 更改了原型為User 的實例對象,lisi是通過構造函數Stu創(chuàng)建的實例對象

  • lisi在執(zhí)行getName 方法時會從自身并向上查找原型犯犁,這就是原型鏈特性
  • 當然如果把 getName 添加到對象上属愤,就不繼續(xù)追溯原型鏈了
function User() {}
User.prototype.getName = function() {
  return this.name;
};

function Stu(name) {
  this.name = name;
}
Stu.prototype = new User();
const lisi = new Stu("李四");

console.log(lisi.__proto__);
console.log(lisi.getName());

當對象中沒使用的屬性時,JS會從原型上獲取這就是繼承在JavaScript中的實現(xiàn)酸役。

繼承實現(xiàn)

下面使用Object.create 創(chuàng)建對象住诸,做為Admin、Member的原型對象來實現(xiàn)繼承涣澡。

圖片7
function User() {}
User.prototype.getUserName = function() {};

function Admin() {}
Admin.prototype = Object.create(User.prototype);
Admin.prototype.role = function() {};

function Member() {}
Member.prototype = Object.create(User.prototype);
Member.prototype.email = function() {};
console.log(new Admin());
console.log(new Member());

不能使用以下方式操作贱呐,因為這樣會改變User的原型方法,這不是繼承入桂,這是改變原型

...
function User() {}
User.prototype.getUserName = function() {};

function Admin() {}
Admin.prototype = User.prototype;
Admin.prototype.role = function() {};
...

上一節(jié)提到有多種方式通過構造函數創(chuàng)建對象

function Admin() {}
console.log(Admin == Admin.prototype.constructor); //true

let obj1 = new Admin.prototype.constructor();
console.log(obj1);

let obj2 = new Admin();
console.log(obj2);

因為有時根據得到的對象獲取構造函數奄薇,然后再創(chuàng)建新對象所以需要保證構造函數存在,但如果直接設置了 Admin.prototype 屬性會造成constructor丟失抗愁,所以需要再次設置constructor值馁蒂。

function User() {}
function Admin() {}

Admin.prototype = Object.create(User.prototype);
Admin.prototype.role = function() {};

let obj1 = new Admin();
console.log(obj1.constructor); //constructor丟失,返回User構造函數

Admin.prototype.constructor = Admin;

let obj2 = new Admin();
console.log(obj2.constructor); //正確返回Admin構造函數

//現(xiàn)在可以通過對象獲取構造函數來創(chuàng)建新對象了
console.log(new obj2.constructor());

上面通過顯示的方式指定了 constructor 導致成了 constructor成為了可遍歷的屬性蜘腌,我們可以使用Object.defineProperty定義來禁止遍歷constructor屬性沫屡。

function User() {}
function Admin(name) {
  this.name = name;
}

Admin.prototype = Object.create(User.prototype);

Object.defineProperty(Admin.prototype, "constructor", {
  value: Admin,
  enumerable: false //禁止遍歷
});

let hd = new Admin("偉哥有話說");
for (const key in hd) {
  console.log(key);
}

剛次提到這種繼承方式就是《JavaScript高級程序編程》中提到寄生組合繼承。該繼承方式是開發(fā)人員認為最理想的繼承方式逢捺。

function SuperType(name){
        this.name = name;
        this.colors = ['red', 'blue', 'green']
    }
    SuperType.prototype.sayName = function(){
        alert(this.name)
    };
    function SubType(name,age){
        SubType.call(this,name);
        this.age = age;
    }
    // 繼承方法
    SubType.prototype = Object.create(SuperType.prototype);
    SubType.prototype.constructor = SubType;
    SubType.prototype.sayAge = function(){
        alert(this.age);
    }
    let instance = new SubType('Nocholas',26)
    instance.sayName();
    instance.sayAge();

對于這種繼承可以如下封裝:

function inheritPrototype(subType, superType){
        const prototype = Object.create(superType);
        prototype.constructor = subType;
        subType.prototype = prototype;
}

看過《JavaScript高級程序編程》的同學可能覺得這個上面這個封裝及熟悉又陌生谁鳍。這個就是你覺得的那個癞季,變得就是Object.create劫瞳。其實這個就是書上的實現(xiàn)。冷不丁的是不是又搞清楚了一個原理??绷柒。下面這個寫法更簡單志于。

function inheritPrototype(subType, superType){
        subType.prototype = Object.create(superType);
        subType.prototype.constructor = subType;
}

方法重寫

下而展示的是子類需要重寫父類方法的技巧。

function Person() {}
Person.prototype.getName = function() {
  console.log("parent method");
};

function User(name) {}
User.prototype = Object.create(Person.prototype);
User.prototype.constructor = User;

User.prototype.getName = function() {
  //調用父級同名方法
  Person.prototype.getName.call(this);
  console.log("child method");
};
let instance = new User();
instance.getName();

多態(tài)

根據多種不同的形態(tài)產生不同的結果废睦,下而會根據不同形態(tài)的對象得到了不同的結果伺绽。

function User() {}
User.prototype.show = function() {
  console.log(this.description());
};

function Admin() {}
Admin.prototype = Object.create(User.prototype);
Admin.prototype.description = function() {
  return "管理員在此";
};

function Member() {}
Member.prototype = Object.create(User.prototype);
Member.prototype.description = function() {
  return "我是會員";
};

function Enterprise() {}
Enterprise.prototype = Object.create(User.prototype);
Enterprise.prototype.description = function() {
  return "企業(yè)帳戶";
};

for (const obj of [new Admin(), new Member(), new Enterprise()]) {
  obj.show();
}

實例操作

使用 call/apply 制作選項卡

<!DOCTYPE html>
<html>
<head>
    <title>組合繼承</title>
    <style>
      * {
        padding: 0;
        margin: 0;
      }

      body {
        display: flex;
        justify-content: center;
        align-items: center;
        width: 100vw;
        height: 100vh;
      }

      main {
        width: 400px;
        flex-direction: column;
        position: relative;
        margin-right: 20px;
      }

      main nav {
        display: flex;
        height: 50px;
        align-items: center;
      }

      main nav a {
        background: #95a5a6;
        margin-right: px;
        padding: 10px 20px;
        border: solid 1px #333;
        color: #fff;
        text-decoration: none;
      }

      main nav a:first-of-type {
        background: #e67e22;
      }

      section {
        height: 200px;
        width: 100%;
        background: #f1c40f;
        position: absolute;
        font-size: 2em;
        display: none;
      }

      .hd-tab section:first-of-type {
        display: block;
      }

      section:nth-child(even) {
        background: #27ae60;
      }
    </style>
</head>
<body>
<main class="tab1">
    <nav>
      <a href="javascript:;">用戶管理</a>
      <a href="javascript:;">配置管理</a>
    </nav>
    <section>用戶管理</section>
    <section>配置管理</section>
  </main>
  <main class="tab2">
    <nav>
      <a href="javascript:;">用戶管理</a>
      <a href="javascript:;">配置管理</a>
    </nav>
    <section>用戶管理</section>
    <section>配置管理</section>
  </main>
<script type="text/javascript">
    //繼承工廠
  function extend(sub, sup) {
    sub.prototype = Object.create(sup.prototype);
    sub.prototype.constructor = sub;
  }
  
  //動作類
  function Animation() {}
  Animation.prototype.show = function() {
    this.style.display = "block";
  };
  //隱藏所有元素
  Animation.prototype.hide = function() {
    this.style.display = "none";
  };
  //必變元素集合背景
  Animation.prototype.background = function(color) {
    this.style.background = color;
  };
    
    //選項卡類
  function Tab(tab) {
    this.tab = tab;
    this.links = null;
    this.sections = null;
  }
  extend(Tab, Animation);
  Tab.prototype.run = function() {
    this.links = this.tab.querySelectorAll("a");
    this.sections = this.tab.querySelectorAll("section");
    this.bindEvent();
    this.action(0);
  };
  //綁定事件
  Tab.prototype.bindEvent = function() {
    this.links.forEach((el, i) => {
      el.addEventListener("click", () => {
        this.reset();
        this.action(i);
      });
    });
  };
  //點擊后觸發(fā)動作
  Tab.prototype.action = function(i) {
    this.background.call(this.links[i], "#e67e22");
    this.show.call(this.sections[i]);
  };
  //重置link與section
  Tab.prototype.reset = function() {
    this.links.forEach((el, i) => {
      this.background.call(el, "#95a5a6");
      this.hide.call(this.sections[i]);
    });
  };
  
  new Tab(document.querySelector(".tab1")).run();
  new Tab(document.querySelector(".tab2")).run();
</script>
</body>
</html>

寫在最后

這一篇終于寫完了。。感覺對JS這個語言重新熟悉了一遍奈应。ES6逐漸普及澜掩,新式的語法讓繼承變得很簡單,似乎這些東西不用我們再去考慮了杖挣。但是理解原理會對這門語言更加深刻肩榕。JavaScript相對其他編程語言來講,靈活的難以想象惩妇,給我一個對象株汉,就能還你一個世界。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末歌殃,一起剝皮案震驚了整個濱河市乔妈,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌氓皱,老刑警劉巖路召,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異匀泊,居然都是意外死亡优训,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進店門各聘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來揣非,“玉大人,你說我怎么就攤上這事躲因≡缇矗” “怎么了?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵大脉,是天一觀的道長搞监。 經常有香客問我,道長镰矿,這世上最難降的妖魔是什么琐驴? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮秤标,結果婚禮上绝淡,老公的妹妹穿的比我還像新娘。我一直安慰自己苍姜,他們只是感情好牢酵,可當我...
    茶點故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著衙猪,像睡著了一般馍乙。 火紅的嫁衣襯著肌膚如雪布近。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天丝格,我揣著相機與錄音撑瞧,去河邊找鬼。 笑死显蝌,一個胖子當著我的面吹牛季蚂,可吹牛的內容都是我干的。 我是一名探鬼主播琅束,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼扭屁,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了涩禀?” 一聲冷哼從身側響起料滥,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎艾船,沒想到半個月后葵腹,有當地人在樹林里發(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡屿岂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年践宴,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片爷怀。...
    茶點故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡阻肩,死狀恐怖,靈堂內的尸體忽然破棺而出运授,到底是詐尸還是另有隱情烤惊,我是刑警寧澤,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布吁朦,位于F島的核電站柒室,受9級特大地震影響,放射性物質發(fā)生泄漏逗宜。R本人自食惡果不足惜雄右,卻給世界環(huán)境...
    茶點故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望纺讲。 院中可真熱鬧擂仍,春花似錦、人聲如沸刻诊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽则涯。三九已至樊零,卻和暖如春掘猿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工聚蝶, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人歪玲。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓赚抡,卻偏偏與公主長得像,于是被迫代替她去往敵國和親呻澜。 傳聞我的和親對象是個殘疾皇子递礼,可洞房花燭夜當晚...
    茶點故事閱讀 43,724評論 2 351