面向对象的语言有一个标志,那就是它们都有类的概念,而通过类可以创建任意多个具有相同属性和方法的对象。但是JavaScript中并没有类的概念,所以它的对象也与基于类的语言中的对象有所不同。
JavaScript中把对象定义为:无序属性的集合,其属性可以包含值,对象或者函数。
虽然Objeck构造函数或对象字面量都可以创建对象,但这些方法有个很明显的特点:使用一个接口创建很多对象,会产生大量的重复代码。
JavaScript语言的传统方法是通过构造函数定义并生成新对象。
function Point(x, y){
this.x = x;
this.y = y;
}
Point.prototype.toString = function(){
return `${this.x} + ${this.y}`
}
var p = new Point(1,2).toString();
console.log(p);
prototype 这个属性是一个指针,指向一个对象的原型。
new在里面做了四个操作:
- 创建一个新对象
- 将构造函数的作用域赋给这个新对象(所以this指向这个新对象)
- 执行构造函数中的代码(为了这个新对象添加属性)
- 返回一个新对象
ES6中提供了更接近传统语言的写法,引入了Class(类)这个概念作为对象的模板。通过class关键字可以定义类。
class Point{
constructor(x, y){
this.x = x;
this.y = y;
}
toString(){
return `${this.x + this.y}`
}
}
var s = new Point(1,2);
console.log(s);
上面的代码定义了一个类,可以看到里面有个constructor方法,这就是构造函数,而this关键字则代表实例对象。
Point 类除了构造函数,还定义了一个toString方法。
定义类的方法时,前面不需要加上function这个保留字,直接把函数定义放进去就可以了。另外,方法之间不需要逗号分隔,加了会报错。
class Point{};
console.log(typeof Point); //"function"
console.log(Point === Point.prototype.constructor); //true
类的数据类型就是函数,类本身就指向构造函数。
class Point{
constructor(){
}
toValue(){
}
}
//等同于
Point.prototype = {
constructor(){},
toValue(){}
}
在类里面调用方法,其实就是调用原型上的方法。
class B {}
let b = new B();
B.constructor === b.prototype.constructor // true
上面的代码中,b是B类的实例,它的constructor 方法就是B类原型的constructor 方法。
constructor始终指向创建当前对象的构造函数。
prototype 就是通过调用构造函数而创建的那个对象实例的原型对象。
prototype 对象的constructor 属性直接指向类本身。
类的内部定义的所有方法都是不可枚举的
class Point {
constructor(){
}
toString(){
}
}
Object.keys(Point.prototype); // []
Object.getOwnPropertyNames(Point.prototype); //["constructor", ["toString"]
类的属性名可以使用表达式
let methodName = 'getArea';
class Point {
constructor (){
}
[methodName] (){
}
}
constructor 方法
constructor 方法是类的默认方法,通过new生成对象实例时自动调用该方法。一个类必须有constructor 方法,如果没有显式定义,一个空的constructor 方法会被默认添加。
function Point(x, y){
this.x = x;
this.y = y;
}
Point.prototype.toString = function(){
return `${this.x} + ${this.y}`
}
var p = new Point(1,2);
console.log(p.hasOwnProperty('x')); //true
console.log(p.hasOwnProperty('y')); //true
console.log(p.hasOwnProperty('toString')) //false
console.log(p.__proto__.hasOwnProperty('toString')) //true
x和y都是实例对象 p 自身的属性(因为定义在this变量上),所以hasOwnProperty方法返回为true,而toString方法是原型对象的属性(因为定义在Point类上),所以hasOwnProperty方法返回为false。
与ES5一样,类的所以实例共享一个原型对象。
var p1 = new Point(1,2);
var p2 = new Point(2.3);
p1.__proto__ === p2.__proto__; //true
proto并不是语言本身的特性,而是各大厂商具体实现时添加的私有属性,虽然目前很多现代浏览器的js引擎中都提供了这个私有属性,但依旧不建议再生产中使用该属性,避免对环境产生依赖。生产环境中,可以使用Objeck.getPrototypeOf 方法来获取实例对象的原型,然后在来为原型添加方法&属性。
私有方法
一种做法是在命名上加以区别
class Widget {
//公有方法
foo(baz){
this._bar(baz);
}
//私有方法
_bar(baz){
return this.snaf = baz;
}
}
_bar方法前面的下划线表示这是一个只限于内部使用的私有方法。但是,这种命名是不保险的,在类的外部依然可以调用这个方法。
另一种方法是索性将私有方法移出模块,因为模板内部的所有方法都是对外可见的。
class Widget {
//公有方法
foo(baz){
bar.call(this, baz);
}
}
function bar(baz){
return this.snaf = baz;
}
foo是公有方法,内部调用了bar.call(this, baz)。这使得bar实际上成为了当前模块的私有方法。
还有一种方法,利用Symbol值的唯一性将私有方法的名字命名为一个symbol值。
const bar = Symbol('bar');
const snaf = Symbol('snaf');
class Widget {
//公有方法
foo(baz){
this[bar](baz);
}
[bar](baz){
return this[snaf] = baz;
}
}
利用symbol值的唯一性,导致第三方无法获取到它,因此达到了私有方法和私有属性的效果。
私有属性
为class加了私有属性。方法是在属性名之前,使用#来表示。
class Point{
#x;
constructor(x = 0){
#x = +x;
}
get x() {
return #x;
}
set x(value){
#x = +value;
}
}
.#x表示私有属性x,在Point类之外是读取不到这个属性的,私有属性与实例的属性是可以同名的
this的指向
类的方法内部如果含有 this,它将默认指向类的实例。但是,一旦单独使用该方法,很可能或报错。
网友评论