上面的章節中我們看到了JavaScript的對象模型是基于原型實現的,特點是簡單,缺點是理解起來比傳統的類-實例模型要困難,最大的缺點是繼承的實現需要編寫大量代碼,并且需要正確實現原型鏈。
有沒有更簡單的寫法?有!
新的關鍵字class從ES6開始正式被引入到JavaScript中。class的目的就是讓定義類更簡單。
我們先回顧用函數實現Student的方法:
function Student(name) { this.name = name; } Student.prototype.hello = function () { alert('Hello, ' + this.name + '!'); }
如果用新的class關鍵字來編寫Student,可以這樣寫:
class Student { constructor(name) { this.name = name; } hello() { alert('Hello, ' + this.name + '!'); } }
比較一下就可以發現,class的定義包含了構造函數constructor和定義在原型對象上的函數hello()(注意沒有function關鍵字),這樣就避免了Student.prototype.hello = function () {...}這樣分散的代碼。
最后,創建一個Student對象代碼和前面章節完全一樣:
var xiaoming = new Student('小明'); xiaoming.hello();
用class定義對象的另一個巨大的好處是繼承更方便了。想一想我們從Student派生一個PrimaryStudent需要編寫的代碼量。現在,原型繼承的中間對象,原型對象的構造函數等等都不需要考慮了,直接通過extends來實現:
class PrimaryStudent extends Student { constructor(name, grade) { super(name); // 記得用super調用父類的構造方法! this.grade = grade; } myGrade() { alert('I am at grade ' + this.grade); } }
注意PrimaryStudent的定義也是class關鍵字實現的,而extends則表示原型鏈對象來自Student。子類的構造函數可能會與父類不太相同,例如,PrimaryStudent需要name和grade兩個參數,并且需要通過super(name)來調用父類的構造函數,否則父類的name屬性無法正常初始化。
PrimaryStudent已經自動獲得了父類Student的hello方法,我們又在子類中定義了新的myGrade方法。
ES6引入的class和原有的JavaScript原型繼承有什么區別呢?實際上它們沒有任何區別,class的作用就是讓JavaScript引擎去實現原來需要我們自己編寫的原型鏈代碼。簡而言之,用class的好處就是極大地簡化了原型鏈代碼。
你一定會問,class這么好用,能不能現在就用上?
現在用還早了點,因為不是所有的主流瀏覽器都支持ES6的class。如果一定要現在就用上,就需要一個工具把class代碼轉換為傳統的prototype代碼,可以試試Babel這個工具。
需要瀏覽器支持ES6的class,如果遇到SyntaxError,則說明瀏覽器不支持class語法,請換一個最新的瀏覽器試試。
首先,構造函數其實就是JavaScript程序中定義好的函數,我們可以直接使用。構造函數實際也是一種函數,它是專門用于生成定義對象,通過構造函數生成的對象,稱為實例化對象。
構造函數分為兩種,一種是JavaScript程序定義好的構造函數,我們成為稱為內置構造函數,另外一種是程序員自己定義的構造函數,稱為自定義構造函數。
構造函數雖然也是一種函數,但是和普通函數是區別的:
1、構造函數一定要和關鍵詞new一起使用,new關鍵詞會自動的給構造函數定義一個對象,并且返回這個對象,我們只要對這個對象設定屬性,設定方法就可以使用。
2、構造函數為了和其他函數區別,語法規范規定構造函數的函數名稱,第一個字母必須大寫,使用大駝峰命名法。
3、構造函數給對象定義屬性和方法的語法,與一般函數不同
//自定義構造函數
function Person(name,sex,age,addr){
// 定義屬性
this.name = name;
this.sex = sex;
this.age = age;
this.addr = addr;
// 定義方法
this.fun = function(){
console.log(this.name,this.sex,this.age,this.addr);
}
}
// 生成實例化對象
const person = new Person('終結者','男',28,'杭州');
console.log(person); //輸出實例化對象
普通構造函數實現繼承
//普通構造函數繼承
//動物
function Animal() {
this.eat = function () {
console.log('animal eat')
}
}
//狗
function Dog() {
this.bark = function () {
console.log('dog bark')
}
}
//綁定原型,實現繼承
Dog.prototype = new Animal()
//哈士奇
var hashiqi = new Dog()
ES6構造函數語法:ES6與ES5構造函數語法對比,其功能作用完全相同,只是語法不同。ES6提供了更接近傳統語言的寫法,引入了Class(類)這個概念,作為對象的模板。通過class關鍵字,可以定義類。
class Perosn{// ES6class方法定義構造函數
//constructor是構造器,定義實例化對象的屬性和屬性值, ()中的是參數
constructor (name,age,sex){
this.name = name;
this.age = age;
this.sex = sex;
}
//定義方法,雖然沒聲明,但是也是定義在構造函數中的prototype中
printAll(){
console.log(this.name,this.age,this.sex);
}
}
// 生成實例化對象
const test = new Perosn('終結者','男',28,'杭州');
console.log(person);//輸出實例化對象
person.printAll(); //輸出方法
上面代碼定義了一個“類”,可以看到里面有一個constructor方法,這就是構造方法,而this關鍵字則代表實例對象。也就是說,ES5的構造函數Person,對應ES6的Person類的構造方法。
定義“類”的方法的時候,前面不需要加上function這個關鍵字,直接把函數定義放進去了就可以了。另外,方法之間不需要逗號分隔,加了會報錯。
Class實現繼承
class Animal{
constructor(name){
this.name=name
}
eat(){
console.log(this.name +'eat');
}
}
class Dog extends Animal{
constructor(name){
super(name)//訪問和調用Dog對象的父對象Animal上的函數。
this.name=name
// 在派生類中, 必須先調用 super() 才能使用 "this",忽略這個,將會導致一個引用錯誤。
}
bark(){
console.log(this.name+'bark');
}
}
const dog=new Dog("泰迪");
dog.eat();
dog.bark();
構造函數的prototype屬性,在ES6的“類”上面繼續存在。事實上,類的所有方法都定義在類的prototype屬性上面。
ES6中的class語法就是ES5中構造函數的另一種寫法,一種更高級的寫法,
class語法的底層還是ES5中的構造函數,只是把構造函數進行了一次封裝而已。
ES6出現的目的為了讓我們的讓對象原型的寫法更加清晰、在語法上更加貼合面向對象的寫法、更像面向對象編程讓JavaScript更加的符合通用編程規范,即大部分語言對于類和實例的寫法,class實現繼承更加容易理解,更易于后端語言使用。
類不存在變量提升(hoist),這一點與 ES5 完全不同。
// ES5
var bar = new Bar(); // 可行
function Bar() {
this.bar = 42;
}
//ES6
const foo = new Foo(); // Uncaught ReferenceError
class Foo {
constructor() {
this.foo = 42;
}
}
類和模塊的內部,默認就是嚴格模式,所以不需要使用use strict指定運行模式。只要你的代碼寫在類或模塊之中,就只有嚴格模式可用。考慮到未來所有的代碼,其實都是運行在模塊之中,所以 ES6 實際上把整個語言升級到了嚴格模式。
// ES5
function Bar() {
// 引用一個未聲明的變量
baz = 42; // it's ok
}
var bar = new Bar();
// ES6
class Foo {
constructor(){
// 引入一個未聲明的變量
fol = 42;
// Uncaught ReferenceError: fol is not defined
}
}
let foo = new Foo();
ES6 中的 class,它的方法(包括靜態方法和實例方法)默認是不可枚舉的,而構造函數默認是可枚舉的。細想一下,這其實是個優化,讓你在遍歷時候,不需要再判斷 hasOwnProperty 了
// ES5
function Bar() {}
Bar.answer = function () {};
Bar.prototype.print = function () {};
console.log(Object.keys(Bar));// ["answer"]
console.log(Object.keys(Bar.prototype))// ["print"]
// ES6
class Foo {
constructor(){}
static answer() {}
print(){}
}
console.log(Object.keys(Foo))// []
console.log(Object.keys(Foo.prototype));// []
class 必須使用new調用,否則會報錯。這是它跟普通構造函數的一個主要區別,后者不用new也可以執行。
// ES5
function Bar(){ }
var bar = Bar();// it's ok;
// ES6
class Foo {
}
let foo = Foo();
// Uncaught TypeError: Class constructor Foo cannot be invoked without 'new'
// ES5
function Bar() {
Bar = 'Baz';
this.bar = 42;
}
var bar = new Bar();
console.log(bar);// Bar {bar: 42}
console.log(Bar);// 'Baz'
// ES6
class Foo {
constructor(){
this.foo = 42;
Foo = 'Fol'; // Uncaught TypeError: Assignment to constant variable.
}
}
let foo = new Foo();
Foo = 'Fol';// it's ok
一條是:子類的__proto__指向父類
另一條:子類prototype屬性的__proto__屬性指向父類的prototype屬性.
ES6的子類可以通過__proto__屬性找到父類,而ES5的子類通過__proto__找到Function.prototype
// ES5
function Super() {}
function Sub() {}
Sub.prototype = new Super();
Sub.prototype.constructor = Sub;
var sub = new Sub();
console.log( Sub.__proto__ === Function.prototype);// true
// ES6
class Super{}
class Sub extends Super {}
let sub = new Sub();
console.log( Sub.__proto__ === Super);// true
ES5的繼承是先建立子類實例對象this,再調用父類的構造函數修飾子類實例(Surper.apply(this))。
ES6的繼承是先建立父類實例對象this,再調用子類的構造函數修飾this。即在子類的constructor方法中必須使用super(),之后才能使用this.
// ES5
function MyES5Array() {
Array.apply(this, arguments);
// 原生構造函數會忽略apply方法傳入的this,
//即this無法綁定,先生成的子類實例,拿不到原生構造函數的內部屬性。
}
MyES5Array.prototype = Object.create(Array.prototype, {
constructor: {
value: MyES5Array,
writable: true,
configurable: true,
enumerable: true
}
})
var arrayES5 = new MyES5Array();
arrayES5[0] = 3;
console.log(arrayES5.length);// 0
arrayES5.length = 0;
console.log(arrayES5[0]);// 3
// ES6
class arrayES6 extends Array {
constructor(...args){
super(...args);
}
}
let arrayes6 = new arrayES6();
arrayes6[0] = 3;
console.log(arrayes6.length);// 1
arrayes6.length = 0;
console.log(arrayes6[0]);// undefined
需要注意一點:
ES6的class語法無法執行預解析,是不能被提前調用;
ES5的function函數可以提前調用,但是只能調用屬性不能調用方法。
天在寫頁面的時候,發現class="1212-sale"開頭的樣式無法調用,后來大群里有同學說class樣式不能以數字的開頭。試了一下果然是的!為了避免犯同樣的錯誤,我上網查了一下css命名規范。整理了一下:
一、命名規則說明:1、所有的命名最好都小寫2、屬性的值一定要用雙引號("")括起來,且一定要有值如class="divcss5",id="divcss5"3、每個標簽都要有開始和結束,且要有正確的層次,排版有規律工整4、空元素要有結束的tag或于開始的tag后加上"/"5、表現與結構完全分離,代碼中不涉及任何的表現元素,如style、font、bgColor、border等6、<h1到<h5>的定義,應遵循從大到小的原則,體現文檔的結構,并有利于搜索引擎的查詢7、給每一個表格和表單加上一個唯一的、結構標記id8、給圖片加上alt標簽9、盡量使用英文命名原則10、盡量不縮寫,除非一看就明白的單詞11、類名的第一個字符不能使用數字!它無法在 Mozilla 或 Firefox 中起作用
二、相對網頁外層重要部分CSS樣式命名:外套 wrap ------------------用于最外層頭部 header ----------------用于頭部主要內容 main ------------用于主體內容(中部)左側 main-left -------------左側布局右側 main-right -----------右側布局導航條 nav -----------------網頁菜單導航條內容 content ---------------用于網頁中部主體底部 footer -----------------用于底部
CSS命名其它說明:DIV+CSS命名小結:無論是使用“.”(小寫句號)選擇符號開頭命名,還是使用“#”(井號)選擇符號開頭命名都無所謂,但我們最好遵循,主要的、重要的、特殊的、最外層的盒子用“#”(井號)選擇符號開頭命名,其它都用“.”(小寫句號)選擇符號開頭命名,同時考慮命名的CSS選擇器在HTML中重復使用調用。通常我們最常用主要命名有:wrap(外套、最外層)、header(頁眉、頭部)、nav(導航條)、menu(菜單)、title(欄目標題、一般配合h1\h2\h3\h4標簽使用)、content (內容區)、footer(頁腳、底部)、logo(標志、可以配合h1標簽使用)、banner(廣告條,一般在頂部)、copyRight(版權)。其它可根據自己需要選擇性使用。
讓我們來看下這個類名定義: .right-red { color:red; } 你可能很明確的知道這個class選擇符的所起的作用。但是這里還有個問題,當你在一星期的時間需要重新設計。在重新設計的時候,這個模塊被放置到了左邊,而且還是綠色。這個類就不再有存在的價值。所以現在不得不選擇,要么改變所有的屬性值,要么放著它不動,這可能導致混亂。 最好不要在你的類名或者ID名中去加入顏色或者長寬的尺寸等帶有屬性的名字。你應該避免任何的屬性值都是直接的詞匯。(如box)直接屬性可能會導致內容的分離。讓我們來看看最合理ID/CLASS的命名規范: .product-description { color: red; } 用這種樣式定義的product-description(產品描述),不管你怎么改變,她都是那么的干凈清晰。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。