ES5面向对象汇总

理解对象

ECMAScript中没有“类”的概念。

ECMA-262将对象定义为:无序属性的集合,其属性可包含基本值,对象或者函数。

使用创建Object实例的方法创建一个对象:

let person = new Object()
person.name = 'Faker'
person.age = 25
person.job = 'T1 MID'
person.sayName = function () {
    console.log(person.name)
}

当然也可以使用对象字面量语法:

let person = {
    name: 'Faker',
    age: 25,
    job: 'T1 MID',
    sayName() {
        console.log(this.name)
    }
}

对象的属性

1.数据属性

数据属性有四个描述其行为的特性:

Configurable(可配置的,默认true):

表示能否通过delete属性删除属性,从而重新定义属性;

能否修改属性的特性;

能否将属性修改为访问器属性。

Enumerable(可数,默认true)

表示能否通过for-in循环返回该值。

Writable(可写的,默认true)

表示能否修改该属性的值。

Value(值,默认undefined)

包含这个属性的数据值。读取这个数据的时候,从这个位置读,写入属性位置的时候,把新值保存在这个位置。

想修改默认特性,必须使用Object.defineProperty()方法。

Object.defineProperty(属性所在的对象,属性的名字,描述符对象)

一个例子,修改Writable属性后,属性值将变为只读,无法改变。

let person = {}
Object.defineProperty(person, 'name', {
    writable: false,
    value: 'Faker'
})
person.name = 'Bang'
console.log(person.name) // Faker

※可以重复使用Object.defineProperty()更改属性,但是把configurable属性改为false后就会有有限制了。

2.访问器属性

访问器用于访问一个对象的属性。

访问器属性包含一对getter和setter函数。

读取访问器属性时,会调用getter函数,负责返回有效的值;

写入访问器属性时,会调用setter函数并传入新值。这个函数负责决定如何处理数据。

访问器属性有四个特性:

Configurable:

表示能否通过delete删除属性从热重新定义属性;

能否修改属性的特性,能否把属性修改为数据属性。

Enumerable:

表示能否通过For-in循环返回属性。

Get:

在读取属性时调用的函数。默认值undefined

Set:

在写入属性时调用的函数。默认值undefined

一个例子: 通过修改访问器属性,用year同时修改_year和edition的值。

let book = {
    _year: 2004,
    edition: 1
}
Object.defineProperty(book, 'year', {
    get: function () {
        return this._year
    },
    set: function (newData) {
        if (newData > 2004) {
            this._year = newData
            this.edition += newData - 2004
        }
    }
})
book.year = 2005
console.log(book.edition) // 2

※可以只定义Set或者Get。结果是只能读或者只能写。

3.定义多个属性

可以使用defineProperties()方法同时定义多个属性。

具体用法看下例:

let person = {}
Object.defineProperties(person, {
    _name: {
        writable: true,
        value:'Faker'
    },
    _age: {
        writable: true,
        value:25
    },
    age: {
        get: function () {
            return this._age
        },
        set: function (newAge) {
            if (newAge > 0) {
                this._age = newAge
            }
        }
    }
})
person.age = 26
console.log(person.age) //26

4.读取属性的特性

定义完了属性,我们还需要读取她。

使用Object.getOwnPropertyDescriptor()方法。

该方法有两个参数:属性所在的对象、要去读其描述符的属性名称

let person = {}
Object.defineProperties(person, {
    _name: {
        writable: true,
        value:'Faker'
    },
    _age: {
        writable: true,
        value:25
    },
    age: {
        get: function () {
            return this._age
        },
        set: function (newAge) {
            if (newAge > 0) {
                this._age = newAge
            }
        }
    }
}) 

let descriptor = Object.getOwnPropertyDescriptor(person, '_name')
console.log(descriptor.value) // Faker

如果是数据属性,name这个对象的属性有Configurable、Enumerable、Writable、Value

let descriptor = Object.getOwnPropertyDescriptor(person, '_name')
console.log(descriptor)
// {value: "Faker", writable: true, enumerable: false, configurable: false}

如果是访问器属性,age这个对象的属性和访问器属性一致(同上)。

let descriptor = Object.getOwnPropertyDescriptor(person, 'age')
console.log(descriptor)
// {enumerable: false, configurable: false, get: ƒ, set: ƒ}

创建对象

工厂模式

通过抽象创建具体对象的过程,来实现快速创建对象。

function createPerson(name, age, job) {
    let person = new Object
    person.name = name
    person.age = age
    person.job = job
    person.sayName = function () {
        console.log(this.name)
    }
    return person
}
let Faker = createPerson('Faker', 25, 'MID')
let Bang = createPerson('Bang', 24, 'BOT')

构造函数模式

创建自定义的构造函数,从而定义自定义对象类型的属性和方法

function Person(name, age, job) {
    this.name = name
    this.age = age
    this.job = job
    this.sayName = function () {
        console.log(this.name)
    }
}
let Faker =new Person('Faker', 25, 'MID')
let Bang =new Person('Bang', 24, 'BOT')
console.log(Faker)
console.log(Bang)

①构造函数模式和工厂模式的不同:

构造函数没有显示的创建对象;

构造函数直接将属性和方法赋给了this对象;

没有return语句

 ※构造函数始终都应该以大写字母开头,非构造函数则使用驼峰命名法小写字母开头。

②使用构造函数时,内部实现的过程:

创建一个新对象 => 将构造函数作用域分配给该对象 => 执行构造函数 => 返回新对象

③使用构造函数创建的对象,其constructor属性指向构造函数

console.log(Faker.constructor == Person) // true

④使用构造函数创建的对象,既是Object的实例,也是构造函数的实例。

所有对象均继承与Object

console.log(Faker instanceof Object) // true
console.log(Faker instanceof Person) // true

※也就是说,构造函数可以为对象创建一个特定的实例,这也是他优于工厂模式的地方。

构造函数

①构造函数-作为函数

构造函数与普通函数的唯一区别:调用方式不同

任何函数,只要以new操作符调用,她就可以作为构造函数;

任何函数,只要不通过new操作符调用,她就是个普通函数;

②构造函数的问题

构造函数的问题在于,内部的方法在每一个实例上都要重新创建一遍

我们可以通过将函数定义在构造函数外来解决这个问题。

function Person(name, age, job) {
    this.name = name
    this.age = age
    this.job = job
    this.sayName = sayName
}
function sayName() {
    console.log(this.name)
}
let a = 1
console.log(window.a)
let Faker = new Person('Faker', 24, 'T1 MID')
Faker.sayName() // Faker

但显而易见,这种方法带来了麻烦:

在全局作用域中定义类函数,浪费了全局作用域的存在

如果有很多类函数,大量的全局作用域函数让自定义的引用类型难以封装

在此,我们引入原型链!

原型模式

什么是prototype?

prototype是一个指针,指向一个对象,这个对象包含可以由所有实例共享的属性和方法。

什么是构造函数?

prototype指向原型对象,原型对象会自动获得一个constructor(构造函数)属性,这个属性是一个指向prototype所在函数的指针。

例如新建一个Person构造函数,Person.prototype.constructor指向Person。

什么是__proto__?

在每个实例中,可以通过__proto__访问构造函数的原型对象。

__proto__连接的是实例和构造函数和原型对象,而不是构造函数。

isPrototypeOf()

用于检验一个原型是否是一个实例的原型。

hasOwnProperty()

用于检验属性是否来自原型。如果该属性被改写,则返回true,来自原型则返回false。

in

当in单独使用时,用于判断一个对象能否访问目标属性。无论存在于原型还是实例中。

我们创建的每个函数都有一个原型属性 prototype ,她是一个指针,指向一个对象。这个对象可以包含由特定类型的所有实例可以共享属性和方法

通俗点来说,prototype是通过调用构造函数从而创建对象的原型对象

原型对象可以让所有对象实例共享它所包含的属性和方法。

通俗点来说,我们只需将对象实例的信息定义在prototype中即可

function Person() { }
Person.prototype.name = 'Faker'
Person.prototype.age = 24
Person.prototype.job = 'SKT MID'
Person.prototype.sayName = function () {
    console.log(this.name)
}
let Faker = new Person()
let Bang = new Person()
Bang.name = 'Bang'
Bang.job = 'SKT BOT'
Faker.sayName() // Faker
Bang.sayName() // Bang

原型模式的弊端:

原型中如果有引用类型作为属性,如果在实例中更改了该属性,那么原型中的属性会被更改,所有实例中的属性都会受到影响。

原型链

原理:

构造函数、原型和实例的关系如下:

当原型的对象等于另一个类型的实例时

 

B继承了A内部的属性和方法。

代码示例如下。

function A() {
	this.a = 'A'
}
A.prototype.showA = function () {
	console.log(this.a)
}

let newA = new A()
newA.showA() // "A"

function B() {
	this.b = 'B'
}

B.prototype = new A()
let newB = new B()
newB.showA() // "A"

※使用原型链继承时,禁止使用对象字面量方式设置属性。对象字面量会重写原型链,导致失效。

确定原型和实例的关系:

使用instanceof 和 isPrototype函数。

console.log(newB instanceof Object) // true
console.log(newB instanceof A) // true
console.log(newB instanceof B) // true

console.log(Object.prototype.isPrototypeOf(newB)) // true
console.log(A.prototype.isPrototypeOf(newB)) // true
console.log(B.prototype.isPrototypeOf(newB)) // true

谨慎地定义方法:

子类如果需要覆盖超类中的某个方法,或添加某个超类不存在的方法时,函数定义都要写在继承语句之后。

原型链的问题

1.原型中的引用变量会被子类共享继承,在子类中修改该类属性时,会影响所有子类的该属性

function SKT() {
    this.team = ['Faker', 'Bang', 'Bengi']
}
function T1() {

}

T1.prototype = new SKT()

var a = new T1()
a.team.push('Effort')
console.log(a.team) // ["Faker", "Bang", "Bengi", "Effort"]

var b = new T1()
console.log(b.team) // ["Faker", "Bang", "Bengi", "Effort"]

2.不能在不影响不影响所有对象实例的情况下,在创建子类的实例时,给超类的构造函数传递参数。

相应的解决办法

借用构造函数

在子类构造函数的内部调用超累构造函数。

实际上就是把超类的构造函数借用了一下,没有使用原型链。

function SKT() {
    this.team = ['Faker', 'Bengi']
}
function T1() {
    SKT.call(this)
    a = 1
    b = 2
}
var aaa = new T1()
console.log(aaa.team) // ["Faker", "Bengi"]

解决了在子类中向超类构造函数传递参数的问题,但定义在超类原型中的函数无法传递。

组合继承

使用借用构造函数继承属性,使用原型链继承方法。

function SuperType(name) {
    this.name = name
    this.getName = function () {
        console.log(this.name)
    }
}
function SubType(name, age) {
    // 继承属性
    SuperType.call(this, name)
    this.age = age
}
// 继承方法
SubType.prototype = new SuperType()
SubType.prototype.constructor = SubType
SubType.prototype.getAge = function () {
    console.log(this.age)
}

var instance1 = new SubType('Faker', 24)
instance1.getAge() // 24
instance1.getName() // Faker

console.log(SubType.prototype)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值