原始生成实例对象的方法是通过构造函数:
function Person(name, age) { this.name = name; this.age = age } Person.prototype.sayName = function () { console.log(this.name); } var person = new Person(‘wang‘, 18); person.sayName();
ES6引入了类的概念,通过class关键字用来定义类。
// es6 class Person { constructor(name, age) { this.name = name; this.age = age; } say() { console.log(this.name) } }
说明:constructor() {} 为类的构造方法。而this代表实例对象。 constructor之外用来定义方法。注意:方法之间不需要用逗号或是分号分隔。
实际上,Person就是就是构造函数的语法糖:
console.log(typeof Person); // function console.log(Person === Person.prototype.constructor); // true
注:所有类的自身属性,都定义在constructor里,在constructor之外定义的方法,都是在类的prototype上;
和es5的构造函数类的区别:
1. es5的prototype中的属性和方法是可枚举(enumerable)的:
console.log(Object.keys(Person.prototype)); // ["sayName", "name"]
es6的prototype中的属性和方法是不可枚举的(non-enumerable)的:
console.log(Object.keys(Person.prototype)); // []
2.es6的类的内部,默认使用严格模式,es5不是
3.es6必须使用new调用,否则会报错;es5可以直接用
4.es6不存在变量提升(let变量声明也不会提升),es5因为是普通的函数,所以存在变量提升
(hasOwnProperty可以用来判断类的属性是否在类自身还是类的原型上)
(由于__proto__并不是js本身拥有,而是浏览器单方面实现的私有属性,因此不建议在开发中使用,替代方法为:Object.getPrototypeOf(person),参数为实例对象,可以得到实例对象的类的原型:
console.log(Object.getPrototypeOf(person) === Person.prototype) // true
)
注:es6有提案,用来实现私有属性和方法(现有能用的方法是加下划线表示私有属性和方法,或者使用Symbol生成独特的仅供内部使用的属性和方法),即在属性或方法之前加#;
this指向问题:
es6类的方法内部如果含有this,则默认指向类的实例。但是,如果将类的方法提取出来用,this将会指向其运行的环境,而不是类的实例。解决方法就是绑定this(.bind(this))或是使用箭头函数;
例如:
class Person { constructor(name, age) { this.name = name; this.age = age; } say() { console.log(this.name) } } var person = new Person(‘wang‘, 12); person.say(); var { say } = person; say(); // Uncaught TypeError: Cannot read property ‘name‘ of undefined
Class的取值(getter)和存值(setter):
与es5一样,在类的内部可以使用get,set关键字,对某个属性设置存值函数和取值函数,在相应操作中会执行get或set函数:
class Person { constructor(name, age) { this.name = name; this.age = age; } say() { // console.log(this.name) } get name() { console.log(‘get name‘); } set name(value) { console.log(‘set name = ‘ + value) } } var person = new Person(‘wang‘, 12); person.name = ‘wangpei‘; // set name = wangpei person.name; // get name
Class可以在内部使用Generator方法
在某个方法之前加上*,就表示该方法是一个Generator函数
Class的静态方法
说明:即只能通过类本身来调用,不能在实例化对象中调用。但是可以被子类继承,子类同样不能在实例中调用
注意:和私有方法(即只能在类内部调用,不能通过类本身调用,例如在类的内部定义var a = 1;这个变量a就只能在类内部调用,外部无法使用)的概念区别
注:在es5中,静态方法是直接定义在构造函数自身的方法:
Person.write = function(){}
es6中:需要通过static关键字定义,表示该方法不会被实例继承,但会被子类继承;
class Person { constructor(name, age) { this.name = name; this.age = age; } say() { // console.log(this.name) } static write() { console.log(this.name); } get name() { console.log(‘get name‘); } set name(value) { console.log(‘set name = ‘ + value) } } var person = new Person(‘wang‘, 12); Person.write(); // Person person.write(); // Uncaught TypeError: person.write is not a function
注意:静态方法内部无法访问到类的实例对象的属性和方法。只能访问到类本身的属性和方法
注意:es6规定,Class的内部只有静态方法,没有静态属性,即类的内部不能通过static为属性定义值(static name: ‘wang‘)
Class的实例属性:
Class的实例属性也可以写在constructor方法外部,相当于在constructor内部写入this.xxx = xxx;
例:
class Person { sex=33; constructor(name, age) { this.name = name; this.age = age; } } var person = new Person(‘wang‘, 12); console.log(person.sex); // 33
注:这个特性是es7的新提案。目前需babel转码器支持,浏览器暂不支持
构造函数(非Class)独有的命令:new.target
new.target 命令一般用于构造函数中,用于判断构造函数是怎么调用的;如果构造函数不是通过new调用的,会返回undefined,否则会返回构造函数:如果有子类继承父类,它总是返回子类构造函数;利用这个特性,可以写出不能独立使用,必须继承后才能使用的类(构造函数)
注:只能在构造函数内部使用,不能在构造函数原型上使用(undefined)
例如:
function Person(name, age) { console.log(new.target); // Person函数本身 this.name = name; this.age = age } Person.prototype.sayName = function () { console.log(new.target); } var person = new Person(‘wang‘, 18); person.sayName(); // undefined
Class的继承
语法:
// es6 class Person { constructor(name, age) { this.name = name; this.age = age; } say() { console.log(this.name); // console.log(this.name) } static write() { console.log(this.write); } } var person = new Person(‘wang‘, 12); person.say(); class Student extends Person { } var student = new Student(‘yao‘, 24); student.say(); // yao Student.write(); // write函数本身
注:子类的内部如无constructor,则默认会添加constructor()和super()函数;
如子类定义了constructor,则必须同时定义super()方法(否则报错),因为子类没有自己的this对象,而是靠父类继承的this对象,再对其进行加工。如果不调用super方法,子类就得不到this对象;
由此可以理解,super是用来返回父类的实例对象的,然后才能在父类的实例上进行加工,从而构建子类的实例。
因此,子类使用this时,必须在super()之后,此时子类才会拥有父类实例的this对象
例如:
class Person { constructor(name, age) { this.name = name; this.age = age; } say() { console.log(this.name); // console.log(this.name) } static write() { console.log(this.write); } } var person = new Person(‘wang‘, 12); person.say(); class Student extends Person { constructor() { } } var student = new Student(‘yao‘, 24); // Uncaught ReferenceError: this is not defined
值得注意的是:
super()虽然返回的是父类的实例对象,但是子类中的this指向的仍是子类,而不是父类。super()相当于Person.prototype.constructor.call(this)
super也可以作为对象来使用:(作为函数使用时,只能在constructor()方法中使用)
在普通方法中,super指向父类的原型对象,在静态方法中,super指向父类
例如:
class Person { constructor(name, age) { this.name = name; this.age = age; } say() { console.log(this.name); // console.log(this.name) } static write() { console.log(this.write); } } class Student extends Person { constructor() { super(); this.class = 1 console.log(super.say) } run() { console.log(super.say) } } var student = new Student(‘yao‘, 24); student.run(); // function say(){...}
注:作为对象时,不能直接使用super对象,只能使用super上的属性或方法(否则报错:Uncaught SyntaxError: ‘super‘ keyword unexpected here);因为super指向的是父类的原型对象,因此可以取得父类的原型上的属性或方法,但不能取到父类实例的属性或是方法;
class Person { constructor(name, age) { this.name = name; this.age = age; } say() { console.log(this.name); // console.log(this.name) } static write() { console.log(this.write); } } class Student extends Person { constructor() { super(); this.class = 1 } run() { console.log(super.say); // function say(){...} console.log(super); // 报错 console.log(super.name); // undefined } static eat() { console.log(super.name); // Person } } var student = new Student(‘yao‘, 24); student.run(); Student.eat();
**注意:es6规定,super()虽然指向的是父类的原型或实例,但是this指向的却是子类,如下:
class Person { constructor(name, age) { this.name = name; this.age = age; } say() { console.log(this.name); } } class Student extends Person { constructor() { super(); this.class = 1 this.name = ‘dong‘ } run() { console.log(super.say()); // dong } } var student = new Student(‘yao‘, 24); student.run();
子类获取父类的方法:
Object.getPrototypeOf(subClass); // 父类
类的prototype属性和__proto__属性
__proto__是指向父类的指针
(1)子类的__proto__,表示构造函数的继承,指向父类
(2)子类prototype属性的 __proto__属性,表示方法的继承,总是指向父类的prototype属性
(3)子类原型的原型,是父类的原型,即子类的__proto__属性的__proto__属性,指向父类实例的__proto__属性。
class Person { constructor(name, age) { this.name = name; this.age = age; } say() { } } class Student extends Person { constructor() { super(); } } console.log(Student.__proto__ === Person); // true console.log(Student.prototype === Person); // false console.log(Student.prototype.__proto__ === Person.prototype); // true