传统的js中没有对象,没有类的概念。传统方法(es5)是通过构造函数,定义并生成新对象,并且将自身的属性共享给新对象。
对于类中的方法是通过prototype属性进行添加的。注意:函数名与实例化构造名相同。(函数名首字母大写,方便区分普通函数)通过构造函数创建对象时必须使用new运算符。
function Phone (brand, price) {
this.brand = brand;
this.price = price;
}
// 添加方法
Phone.prototype.call = function () {
console.log('正在呼叫***')
}
// 实例化对象
let Huawei = new Phone('华为', 3999)
Huawei.call()
console.log(Huawei)
es6中引入Class,通过class关键字定义类。es6的class可以看作只是一个语法糖,它的绝大部分功能,es5都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。es6中的类中会有一个默认构造函数constructor,用来接收参数和初始化,实例化时会自动调用,且该构造函数的命名不可更改。在类中定义类的方法,定义方法时不能加上function,且方法之间不可有逗号(,)分隔。
class Phone {
// 构造方法 (名字不可更改, 实例化时会自动调用)
constructor(brand, price) {
this.brand = brand
this.price = price
}
// 方法必须使用该语法,不能使用es5对象完整形式 不可有function
call () {
console.log(this.brand+' 正在呼叫***')
}
}
let onePlus = new Phone('1+', 2999)
onePlus.call()
console.log(onePlus)
类的数据类型就是函数,类本身就指向构造函数。
console.log(typeof onePlus) // object
console.log(typeof Phone) // function
console.log(Phone === Phone.prototype.constructor) // true
es6中静态属性指的是Class本身的属性,即Class.propname,而不是定义在实例对象(this)上的属性。静态属性不能被实例对象调用。
静态属性定义的方法:
① 方法一(目前只有这种写法可行,ES6明确规定,Class内部只有静态方法,没有静态属性,其他方法eslint-js编译不通过)(对于新写法,本人写的时候eslint-js编译没有通过)
对于老写法,其静态属性定义在类的外部,整个类生成以后,再生成静态属性。新写法是显示声明(declarative),而不是赋值处理,语义更好。
// 老写法
class Foo {}
Foo.prop = 1;
console.log(Foo.prop) // 1
// 新写法
class Foo {
static prop = 1
}
console.log(Foo.prop) // 1
// 以下几种定义方式失效
class Test {
prop: 2 // 写法一 (eslint-js 编译不通过)
static prop: 2 // 写法二 (eslint-js 编译不通过)
}
Test.prop // undefined
② 方法二 (ES7 有一个静态属性的提案,目前Babel转码器支持。类的静态属性可以用等式写入类的定义中)
class Test {
prop = 'myProp'
}
类相当于实例的原型,在类中定义的方法会被实例化继承。如果在方法之前加上static关键字,实例化对象就不会继承到该方法,此方法为静态方法,直接通过类来调用。
class Phone {
static change () {
console.log('change')
}
}
let nokie = new Phone()
Phone.change() // change
nokie.change() // 报错:Uncaught TypeError: nokie.change is not a function
对于ES5的静态成员的处理:
// es5
function Phone () {}
Phone.name = 'phone' // 静态属性
Phone.change = function () { // 静态方法
console.log('change')
}
Phone.prototype.size = '5.5inch' // (不是静态属性)此种定义方式与this处理类同,可被继承
// 实例化
let nokie = new Phone()
console.log(nokie.name) // undefined
console.log(nokie.size) // 5.5inch
nokie.change() // 报错
通过父级类名.call(this, 属性名, ...)进行寄生继承属性。其中this指向的是子级类。推荐一篇es5实现继承的几种方式
// es5 构造函数实现继承 ----------------------------------------------------------
// 父级构造函数
function Phone (brand, price) {
this.brand = brand;
this.price = price;
}
// 添加方法
Phone.prototype.call = function () {
console.log('正在呼叫***')
}
// ------------------------------------------------------------------------------
// 子级构造函数
function SmartPhone (brand, price, color, size) {
Phone.call(this, brand, price) // this指向SmartPhone 继承父级属性
this.color = color
this.size = size
}
// 设置子级构造函数的原型
SmartPhone.prototype = new Phone;
SmartPhone.prototype.constructor = SmartPhone; // 做矫正 加不加不影响
// 声明子类的方法
SmartPhone.prototype.photo = function () {
console.log('拍照')
}
SmartPhone.prototype.PlayGames = function () {
console.log('玩游戏')
}
// ------------------------------------------------------------------------------
const XXX = new SmartPhone('锤子', 2499, '黑色', '5.5inch')
console.log(XXX)
ES6,子级类定义时通过关键字extends来时间类的继承,通过super关键字实现继承父级的属性,类似于父级.call()的功能。
class Phone {
constructor(brand, price) {
this.brand = brand
this.price = price
}
// 父类的成员属性
call () {
console.log('正在呼叫***')
}
}
// 子级继承 使用关键子extends
class SmartPhone extends Phone {
constructor(brand, price, color, size) {
super (brand, price) // 类似于Phone.call(this, brand, price)
this.color = color
this.size = size
}
photo () {
console.log('拍照')
}
playGames () {
console.log('玩游戏')
}
}
const xiaomi = new SmartPhone('小米', 1799, 'white', '4.7inch')
console.log(xiaomi)
xiaomi.call() // 正在呼叫***
xiaomi.photo() // 拍照
xiaomi.playGames() // 玩游戏
super关键字既可以当作函数使用,也可以当作对象使用。这两种情况下,用法完全不同。
① super作为函数调用时,代表父类的构造函数。ES6要求,子类的构造函数必须执行一次super函数。super虽然代表了父类的构造函数,但返回的是子类的实例,即super内部的this指向子类,因此在这里相当于父级.prototype.constructor.call(this)。作为函数时,super()只能在子类的构造函数中,用在其他地方会报错。
② super作为对象时,指向的是父类的原型对象(父类.prototype)。通过super.XXX的方式调用父类的属性和方法。定义在父类实例对象的方法和属性(父类构造函数中的属性属于父类实例对象的)不能通过super调用。ES6规定,通过super调用父类方法时,super会绑定子类的this。
注意:使用super时,必须显式指定作为函数还是作为对象使用,否则会报错。如console.log(super)会报错。
class SmartPhone extends Phone {
constructor(brand, price, color, size) {
super (brand, price) // 类似于Phone.call(this, brand, price)
this.color = color
this.size = size
}
...
call () { // 对call方法进行重写
console.log('视频通话')
}
}
xiaomi.call() // 视频通话
ES6中,可以通过get方法获取属性值,通过set方法对属性值进行更改值。不自定义时,初始化时会自动调用set方法,读取时调用get方法,get方法只能读。
自定义get和set方法的注意事项和实例:
class Phone {
constructor(price) {
this.price = price
}
get price () {
console.log('--- getter')
return this._price
}
set price (newPrice) {
console.log('--- setter')
this._price = newPrice
}
sayPrice () {
console.log(this.price)
}
}
let xxx = new Phone (500) // --- setter
// ---------------------------------
xxx.sayPrice() // 读取price输出:1. --- getter 2. 500
自定义get和set时,调用set方法内是this.price = newPrice,调用get方法内是return this.price,在初始化实例对象时会在constructor构造函数中执行this.price=price,会进行无限递归,最后导致栈溢出现象。即
但通过在set和get中使用别名_price会解决这个问题。直接调用_price,就直接输出_price的值,不走price(),因为get price()是读取price属性值。直接调用price时,会调用price的get()。
console.log(xxx._price) // 500
console.log(xxx.price) // 1. --- getter 2. 500
console.log(xxx)
当在类中定义_price的get和set方法时,在直接调用_price时,也会自动调用_price的get()方法,即会走自己的get()和set()。即同样的操作,只是类中多定义了_price的get()和set(),输出结果如下:
get price()与set price(newPrice)是成对出现的。如果只有get,则会报如下错误:
下一篇.......