创建对象
- 基本方式
let tom = {};
tom.name = 'Tom';
tom.sayHi = function() {
console.log(this.name, 'say hi.');
}
tom.sayHi();
- 使用构造函数
function Human(name) {
this.name = name;
this.sayHi = function() {
console.log(this.name, 'say hi.');
}
}
let tom = new Human("Tom");
tom.sayHi();
上面代码中,Human
就是一个构造函数。构造函数一般首字母要大写,用来和普通函数作区分。其实构造函数和普通函数没有任何区别。构造函数通过new
关键字来创建对象:
let tom = new Human("Tom");
new
关键字做的事情,是创建一个空的对象{}
,然后将该对象传入到Human函数中进行初始化,然后返回该对象。
- 使用class
ES6引入了class关键字,如果你了解像java、C++这样的语言,一定对class很熟悉。
class Human {
constructor(name) {
this.name = name;
}
sayHi() {
console.log(this.name, 'say hi.');
}
}
let tom = new Human('Tom');
tom.sayHi();
添加方法
当我们用构造函数的方式添加方法时,有两种写法:
写法1:
function Shape {
this.copy = function() {
console.log('copy...');
}
}
写法2:
function Shape() {}
Shape.prototype.copy = function() {
console.log('copy...');
}
两种都可以,但是写法1每次创建一个新的对象,都要创建一个新的方法,而写法2是所有对象共享同一个方法。如果是需要创建大量对象的场景,写法2有更好的性能。
静态属性和静态方法
JavaScript使用static
关键字声明静态属性和静态方法:
class Human {
static level = 0;
static upgrade() {
this.level += 1;
}
}
Human.upgrade();
console.log(Human.level);
私有属性和私有方法
方法1:通过Symbol实现私有属性和私有方法
const _name = Symbol();
const _sayHi = Symbol();
class Human {
constructor(name) {
this[_name] = name;
this[_sayHi] = function() {
console.log(this[_name], 'say hi.');
}
}
}
let tom = new Human('Tom');
ES6新增了Symbol作为原始数据类型(像number、boolean、null、undefined、string这些都是原始数据类型)。Symbol() 函数每次调用会创建一个新的唯一值。
方法2:通过WeakMap
实现私有属性和私有方法
const nameMap = new WeakMap();
const sayHiMap = new WeakMap();
class Human {
constructor(name) {
nameMap.set(this, name);
sayHiMap.set(this, () => {
console.log(nameMap.get(this), 'say hi.');
})
}
}
let tom = new Human('Tom');
WeakMap
将属性或者方法保存在对象之外,所以对外部是真正的不可见。使用WeakMap
不会影响对象的垃圾回收,因为WeakMap
不会持有该对象。
getter和setter
const ageMap = new WeakMap();
class Human {
constructor(age) {
ageMap.set(this, age);
}
set age(age) {
if (typeof age !== 'number' || age < 0 || age > 150) throw '您输入的age不对劲!';
ageMap.set(this, age);
}
get age() {
return ageMap.get(this);
}
}
let tom = new Human(18);
console.log(tom.age);
继承
在JavaScript中,所有的对象,都有一些共有的方法。
let obj = {};
console.log(obj.toString());
上面代码中的toString
方法就是所有JavaScript共有的方法,它是怎么来的呢?在JavaScript中,每个对象都有一个隐藏的属性,这个属性是一个对象,我们称之为prototype(原型)。由于原型也是一个对象,所以原型也有它自己的原型!依次类推,形成一个原型的链条。一个对象中有什么属性和方法,是由它的原型链条决定的,因为当你访问一个对象的方法或者属性时,首先会在当前对象上找,如果找不到,就会顺着他的原型链条找。
获取一个对象的原型的方式:
function Shape(idx) {
this.idx = idx;
}
let s = new Shape(1);
// 获取对象s的原型有三种方式:
// 方式1:
let p1 = s.__proto__;
// 方式2
let p2 = Shape.prototype;
// 方式3
let p3 = Object.getPrototypeOf(s);
当然这个原型链条是有终点的,这个终点我们可以称之为objectBase
,objectBase
是JavaScript中所有的对象最终的原型。objectBase
中包含JavaScript对象共有的方法,例如toString
等。
let foo = {};
// 获取objectBase
let objectBase = foo.__proto__;
if (foo.toString === objectBase.toString) console.log('foo的toString方法来自于objectBase');
所以,要实现JavaScript中的继承功能,就是要设计一个原型链条。先看一下最简单的情况:
let obj = {};
原型链条是:
objectBase
|
obj
使用构造方式创建对象:
function Shape() {
}
let s = new Shape();
原型链条是:
objectBase
|
shapeBase
|
s
那么,如果要设计一个继承于Shape的Circle类型,要怎么设计呢?需要通过手动修改Circle的原型的原型!
function Shape(idx) {
this.idx = idx;
}
function Circle(idx, radius) {
Shape.call(this, idx);
this.radius = radius;
}
Circle.prototype.__proto__ = Shape.prototype;
let c = new Circle(1,1);
原型链条是:
objectBase
|
shapeBase
|
circleBase
|
c
说实话,很麻烦!不过事实上没有人这么实现JavaScript的继承的,因为ES6新增了class
关键字,现在实现继承是很容易的:
class A {}
class B extends A {}
class C extends B {}
let c = new C();
但是,这只不过是语法糖,JavaScript中并没有class的概念,最终还是靠原型链实现继承功能的。通过使用class
关键字,可以让编译器帮我们完成原型链设计。
方法覆写(Method overriding)
JavaScript并不需要像其他面向对象的语言一样使用overiding
之类的关键字:
class Shape {
copy() {
console.log('Shape copy...');
}
}
class Circle extends Shape {
copy() {
super.copy();
console.log("Circle copy...");
}
}
let c = new Circle();
c.copy();
多态
有了上面的基础后,使用多态就很简单了:
class Shape {
copy() {
console.log('Shape copy...');
}
}
class Circle extends Shape {
copy() {
console.log("Circle copy...");
}
}
class Triangle extends Shape {
copy() {
console.log('Triangle copy...');
}
}
let shapes = [
new Circle(),
new Triangle()
];
for (const shape of shapes) {
shape.copy();
}
网友评论