Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
類是用于創建對象的模板。JavaScript中生成對象實例的方法是通過構造函數,這跟主流面向對象語言(java,C#)寫法上差異較大,如下:
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function () {
return '(' + this.x + ', ' + this.y + ')';
};
var p = new Point(1, 1);
ES6 提供了更接近Java語言的寫法,引入了 Class(類)這個概念,作為對象的模板。通過class關鍵字,可以定義類。
如下:constructor()是構造方法,而this代表實例對象:
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
類的數據類型就是函數,它本身就是指向函數的構造函數:
// ES5 函數聲明
function Point() {
//...
}
// ES6 類聲明
class Point {
//....
constructor() {
}
}
typeof Point // "function"
Point === Point.prototype.constructor // true
在類里面定義的方法是掛到Point.prototype,所以類只是提供了語法糖,本質還是原型鏈調用。
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
Point.prototype = {
//....
toString()
}
var p = new Point(1, 1);
p.toString() // (1,1)
類的另一種定義方式類表達式
// 未命名/匿名類
let Point = class {
constructor(x, y) {
this.x = x;
this.y = y;
}
};
Point.name // Point
函數聲明和類聲明有個重要區別,函數聲明會提升,類聲明不會提升。
constructor()方法是類的默認方法,new生成實例對象時會自動調用該方法。
一個類必須有constructor()方法,如果沒有顯式定義,引擎會默認添加一個空的constructor()。
constructor()方法默認返回實例對象(即this)。
class Point {
}
// 自動添加
class Point {
constructor() {}
}
與 ES5 一樣,在類的內部可以使用get和set關鍵字,對某個屬性設置存值函數和取值函數,攔截該屬性的存取行為。
class User {
constructor(name) {
this.name = name;
}
get name() {
return this.name;
}
set name(value) {
this.name = value;
}
}
類的方法內部的this,它默認指向類的實例,在調用存在this的方法時,需要使用 obj.method()方式,否則會報錯。
class User {
constructor(name) {
this.name = name;
}
printName(){
console.log('Name is ' + this.name)
}
}
const user = new User('jack')
user.printName() // Name is jack
const { printName } = user;
printName() // 報錯 Cannot read properties of undefined (reading 'name')
如果要單獨調用又不報錯,一種方法可以在構造方法里調用bind(this)。
class User {
constructor(name) {
this.name = name;
this.printName = this.printName.bind(this);
}
printName(){
console.log('Name is ' + this.name)
}
}
const user = new User('jack')
const { printName } = user;
printName() // Name is jack
bind(this) 會創建一個新函數,并將傳入的this作為該函數在調用時上下文指向。
另外可以使用箭頭函數,因為箭頭函數內部的this總是指向定義時所在的對象。
class User {
constructor(name) {
this.name = name;
}
printName = () => {
console.log('Name is ' + this.name)
}
}
const user = new User('jack')
const { printName } = user;
printName() // Name is jack
靜態屬性指的是類本身的屬性,而不是定義在實例對象this上的屬性。
class User {
}
User.prop = 1;
User.prop // 1
可以在類里面定義靜態方法,該方法不會被對象實例繼承,而是直接通過類來調用。
靜態方法里使用this是指向類。
class Utils {
static printInfo() {
this.info();
}
static info() {
console.log('hello');
}
}
Utils.printInfo() // hello
關于方法的調用范圍限制,比如:私有公有,ES6暫時沒有提供,一般是通過約定,比如:在方法前面加下劃線_print()表示私有方法。
Java中通過extends實現類的繼承。ES6中類也可以通過extends實現繼承。
繼承時,子類必須在constructor方法中調用super方法,否則新建實例時會報錯。
class Point3D extends Point {
constructor(x, y, z) {
super(x, y); // 調用父類的constructor(x, y)
this.z = z;
}
toString() {
return super.toString() + ' ' + this.z ; // 調用父類的toString()
}
}
父類的靜態方法,也會被子類繼承。
class Parent {
static info() {
console.log('hello world');
}
}
class Child extends Parent {
}
Child.info() // hello world
在子類的構造函數必須執行一次super函數,它代表了父類的構造函數。
class Parent {}
class Child extends Parent {
constructor() {
super();
}
}
在子類普通方法中通過super調用父類的方法時,方法內部的this指向當前的子類實例。
class Parent {
constructor() {
this.x = 1;
this.y = 10
}
printParent() {
console.log(this.y);
}
print() {
console.log(this.x);
}
}
class Child extends Parent {
constructor() {
super();
this.x = 2;
}
m() {
super.print();
}
}
let c = new Child();
c.printParent() // 10
c.m() // 2
初學JavaScript時,_proto_和prototype 很容易混淆。首先我們知道每個JS對象都會對應一個原型對象,并從原型對象繼承屬性和方法。
下圖是一些擁有prototype內置對象。
prototype
根據上面描述,看下面代碼
var obj = {} // 等同于 var obj = new Object()
// obj.__proto__指向Object構造函數的prototype
obj.__proto__ === Object.prototype // true
// obj.toString 調用方法從Object.prototype繼承
obj.toString === obj.__proto__.toString // true
// 數組
var arr = []
arr.__proto__ === Array.prototype // true
對于function對象,聲明的每個function同時擁有prototype和__proto__屬性,創建的對象屬性__proto__指向函數prototype,函數的__proto__又指向內置函數對象(Function)的prototype。
function Foo(){}
var f = new Foo();
f.__proto__ === Foo.prototype // true
Foo.__proto__ === Function.prototype // true
類作為構造函數的語法糖,也會同時有prototype屬性和__proto__屬性,因此同時存在兩條繼承鏈。
class Parent {
}
class Child extends Parent {
}
Child.__proto__ === Parent // true
Child.prototype.__proto__ === Parent.prototype // true
子類實例的__proto__屬性,指向子類構造方法的prototype。
子類實例的__proto__屬性的__proto__屬性,指向父類實例的__proto__屬性。也就是說,子類的原型的原型,是父類的原型。
class Parent {
}
class Child extends Parent {
}
var p = new Parent();
var c = new Child();
c.__proto__ === p.__proto__ // false
c.__proto__ === Child.prototype // true
c.__proto__.__proto__ === p.__proto__ // true
JavaScript中的Class更多的還是語法糖,本質上繞不開原型鏈。歡迎大家留言交流。
上面的章節中我們看到了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語法,請換一個最新的瀏覽器試試。
面向對象編程中,類(class)是對象(object)的模板,定義了同一組對象(又稱"實例")共有的屬性和方法。Javascript語言不支持"類",但是可以用一些變通的方法,模擬出"類"。
一、構造函數法
這是經典方法,也是教科書必教的方法。它用構造函數模擬"類",在其內部用this關鍵字指代實例對象。
function Cat() {
this.name = "張三";
}
生成實例的時候,使用new關鍵字。
var cat1 = new Cat();
alert(cat1.name); // 張三
類的屬性和方法,還可以定義在構造函數的prototype對象之上。
Cat.prototype.makeSound = function(){
alert("你好我是類");
}
二、Object.create()法
為了解決"構造函數法"的缺點,更方便地生成對象,Javascript的國際標準ECMAScript第五版(目前通行的是第三版),提出了一個新的方法Object.create()。
用這個方法,"類"就是一個對象,不是函數。
var Cat = {
name: "李四",
makeSound: function(){ alert("早上好啊!!"); }
};
然后,直接用Object.create()生成實例,不需要用到new。
var cat1 = Object.create(Cat);
alert(cat1.name); // 張三
cat1.makeSound(); // 早上好啊!!
主流瀏覽器比較適配此方法。若遇到舊版本瀏覽器(如:IE6),可以用下面的代碼自行部署。
if (!Object.create) {
Object.create = function (o) {
function F() {}
F.prototype = o;
return new F();
};
}
這種方法比"構造函數法"簡單,但是不能實現私有屬性和私有方法,實例對象之間也不能共享數據,對"類"的模擬不夠全面。
三、極簡主義法
3.1 封裝
這種方法不使用this和prototype,代碼部署起來非常簡單,這大概也是它被叫做"極簡主義法"的原因。
首先,它也是用一個對象模擬"類"。在這個類里面,定義一個構造函數createNew(),用來生成實例。
var Cat = {
createNew: function(){
// some code here
}
};
然后,在createNew()里面,定義一個實例對象,把這個實例對象作為返回值。
var Cat = {
createNew: function(){
var cat = {};
cat.name = "張三";
cat.makeSound = function(){ alert("沙沙沙"); };
return cat;
}
};
使用的時候,調用createNew()方法,就可以得到實例對象。
var cat1 = Cat.createNew();
cat1.makeSound(); // 沙沙沙
這種方法的好處是,容易理解,結構清晰優雅,符合傳統的"面向對象編程"的構造,因此可以方便地部署下面的特性。
3.2 繼承
讓一個類繼承另一個類,實現起來很方便。只要在前者的createNew()方法中,調用后者的createNew()方法即可。
先定義一個Animal類。
var Animal = {
createNew: function(){
var animal = {};
animal.sleep = function(){ alert("沙沙沙"); };
return animal;
}
};
然后,在Cat的createNew()方法中,調用Animal的createNew()方法。
var Cat = {
createNew: function(){
var cat = Animal.createNew();
cat.name = "張三";
cat.makeSound = function(){ alert("沙沙沙"); };
return cat;
}
};
這樣得到的Cat實例,就會同時繼承Cat類和Animal類。
var cat1 = Cat.createNew();
cat1.sleep(); // 沙沙沙
3.3 私有屬性和私有方法
在createNew()方法中,只要不是定義在cat對象上的方法和屬性,都是私有的。
var Cat = {
createNew: function(){
var cat = {};
var sound = "沙沙沙";
cat.makeSound = function(){ alert(sound); };
return cat;
}
};
上例的內部變量sound,外部無法讀取,只有通過cat的公有方法makeSound()來讀取。
var cat1 = Cat.createNew();
alert(cat1.sound); // undefined
3.4 數據共享
有時候,我們需要所有實例對象,能夠讀寫同一項內部數據。這個時候,只要把這個內部數據,封裝在類對象的里面、createNew()方法的外面即可。
var Cat = {
sound : "沙沙沙",
createNew: function(){
var cat = {};
cat.makeSound = function(){ alert(Cat.sound); };
cat.changeSound = function(x){ Cat.sound = x; };
return cat;
}
};
然后,生成兩個實例對象:
var cat1 = Cat.createNew();
var cat2 = Cat.createNew();
cat1.makeSound(); // 沙沙沙
這時,如果有一個實例對象,修改了共享的數據,另一個實例對象也會受到影響。
cat2.changeSound("啾啾啾");
cat1.makeSound(); //啾啾啾
*請認真填寫需求信息,我們會在24小時內與您取得聯系。