面向对象
一般来说,对象的创建有两种,字面量和构造函数
let obj = {} //字面量
obj = new Object() //构造函数
两种方法的本质是一样的,都是新建了一个对象赋值给了 obj,
如果我们每使用一次都会创建一个对象。那么就会生成大量重复的对象。并且每次生成的时候包括方法在内都会重复。
可以利用 JavaScript 的原型的特点,来构造出一个包含共同方法和私有属性的对象。这个特性就是 JS 对象的原型。
原型
new
构造函数生成实例化对象的方式是 new 关键字。
内部用 this 指代需要生成的实例化对象,添加属性与方法。
构造函数函数名通常首字母大写。
function Person(name) {
this.name = name
this.say = function() {
console.log(this.name)
}
}
let p1 = new Person("Gintama")
p1 //{name: "Gintama",say:function(){console.log(this.name)}}
p1.say() //"Gintama"
原本 JavaScript 是没有类的,只有基于原型,在构造函数中通过 new 关键字可以生成对象,和基于类的语言非常类似,所以我们称构造函数也是一个类。
prototype
只有(构造)函数 原型 prototype 对象,可以直接访问修改原型的属性,
原型对象的方法可以让实例化对象使用
function Person(name) {
this.name = name
}
Person.prototype.say = function() {
//注意,一定要写在实例化之前
console.log(this.name)
}
let p1 = new Person("Gintama")
let p2 = new Person("狗蛋")
p1.say() //"Gintama"
p2.say() //"狗蛋"
Person.prototype.say = function() {
//注意,一定要写在实例化之前
console.log("shut up!")
}
p1.say() //"shut up!"
__proto__
任何(实例化)对象拥有一个属性 constructor,构造器。它指向当前对象的构造函数。
function Person() {}
let p = new Person()
p.constructor === Person //true
let o1 = {}
o1.constructor === Object //true
Object.getPrototypeOf()
实例化对象拥有隐式原型[[prototype]]
,可以通过 __proto__
访问构造函数的原型
(实际上有的浏览器不支持这个属性)。推荐使用Object.getPrototypeOf()
访问。
p.__proto__ //{say: ƒ, constructor: ƒ}
Object.getPrototypeOf(p) //{say: ƒ, constructor: ƒ}
p.__proto__ === Object.getPrototypeOf(p) //true
new 关键字做了什么
function Foo() {}
//let o = new Foo();等价于做了以下
o = new Object() //新建一个对象
o.__proto__ = Foo.prototype //修改对象原型指向
Foo.call(o) //将this指向o执行
自实现 new
function myNew(fn) {
const obj = Object(fn.prototype)
result = fn.apply(obj, [...arguments].slice(1))
return typeof result === "object" ? result : obj
}
instanceof
判断是否是实例或子类
p1 instanceof Person //true
Object 是对象,同时也是构造函数。(可以使用 new Object)
Object instanceof Object //true
Object instanceof Function //true
因此,任何的对象都是可以看作 Object 的实例。
寄生组合式继承
继承我们主要要干几件事:
- 把父类的属性和方法绑定到子类上
- 把父类添加到子类的原型链上
子类与父类之间的继承关系,和实例与类下关系差不多。寄生组合式继承是原型式继承的终极写法
function inheritPrototype(subType, superType) {
//1.创建了超类(父类)原型的(副本)浅复制
var prototype = Object(superType.prototype)
//2.修正子类原型的构造函数属性
prototype.constructor = subType
//3.将子类的原型替换为超类(父类)原型的(副本)浅复制
subType.prototype = prototype
}
Object(.create)不同于 new 它只是生成一个拷贝了原型的对象
ES6 class
es6 新语法糖,构造函数和继承方法细节隐藏,但是很直白
声明使用 class 关键字 也可以使用匿名 class 表达式
class Rectangle {
// constructor
constructor(height, width) {
this.height = height
this.width = width
}
// Getter
get area() {
return this.calcArea()
}
// Method
calcArea() {
return this.height * this.width
}
}
const square = new Rectangle(10, 10)
console.log(square.area)
// 100
上式结构分明,一目了然。要注意没有逗号
extends
继承。class 子类 extends 父类
super指代超类,顺着__proto__
查找超类。
class Animal {
constructor(name) {
this.name = name
}
speak() {
console.log(`${this.name} makes a noise.`)
}
}
class Dog extends Animal {
constructor(name) {
super(name) // 调用超类构造函数并传入name参数
}
speak() {
console.log(`${this.name} barks.`)
}
}
var d = new Dog("Mitzie")
d.speak() // 'Mitzie barks.'
static
定义一个类的一个静态方法,调用静态方法不需要实例化。方法中也不使用 this
class Point {
constructor(x, y) {
this.x = x
this.y = y
}
static displayName = "Point"
static distance(a, b) {
const dx = a.x - b.x
const dy = a.y - b.y
return Math.hypot(dx, dy)
}
}
Symbol.species
覆盖默认的构造函数
class MyArray extends Array {
// Overwrite species to the parent Array constructor
static get[Symbol.species]() {
return Array
}
}
var a = new MyArray(1, 2, 3)
var mapped = a.map((x) => x * x)
console.log(mapped instanceof MyArray)
// false
console.log(mapped instanceof Array)
// true
private
私有。相当于类中的局部变量。(ts 用 private,js 用#属性名)
class Rectangle {
#height = 0
#width
constructor(height, width) {
this.#height = height
this.#width = width
}
}
包装对象
字面量:js 中的基础类型都是字面量声明的值类型,而不是 new 出来的,却又可以使用对象的方法。
;
(2).toString() //"2"
值类型之所以能够像对象一样进行属性设置操作,这是因为这行代码执行时临时生成了一个包装对象
Number(2)并对它进行操作返回值,然后销毁。
"123" * 1 //123
//本质上是
Number("123").valueOf() * 1 //123
let str = "asd"
str.length //3
//本质上是
String(str).length //3
str.length = 4
str.length //3
这就是为什么这里只能访问 str 的值却无法修改的原因了,因为每次都会生成一个新的包装对象
Java 和 JavaScript 不同,Java 的原始类型需要装箱,否则不是Object 的子类。
而 JavaScript 和 Kotlin(另一个 JVM 上运行的语言,取代 Java 作为了安卓 APP 构建语言)在设计上避免了装箱拆箱。