重学《JavaScript 高级程序设计》笔记 第6章对象

第6章 面向对象的程序设计

ECMAScript中没有类的概念;

1.创建对象-历史

1.1 创建实例,添加方法和属性 → 对象字面量

缺点: 使用同一接口创建很多对象,产生大量重复代码

var person = new Object()
person.name = "Y"
person.age = 18
person.job = "police"

person.sayName = function() {
    alert(this.name)
}

var person = {
    name = "Y",
    age = 18,
    job = "police",

    sayName = function() {
        alert(this.name)
    }    
}

1.2 工厂模式 (返回对象)

缺点: 没有解决对象识别的问题:怎样知道一个对象的类型;
特点: 内部创建对象,并返回

function createPerson(name, age, job) {
    var o = new Object()
    o.name = name
    o.age = age
    o.job = job
    o.sayName = function () { 
        alert(this.name)
    }
    return o
}

1.3 构造函数模式

缺点: 每个方法都要在每个实例上重新创建一遍!不同实例上的同名函数是不相等的,然而,创建2个完成同样任务的Function实例的确没有必要。
特点
没有显示创建对象;
没有return;
将方法属性赋值给this对象;
首字母大写;
构造函数创建的实例标识为一种特定的类型

1.3.1 理解面试题

调用构造函数创建对象,实际会经历以下4个步骤:

  1. 创建一个新对象;
  2. 将构造函数的作用域赋值给新对象(this指向这个新对象)
  3. 执行构造函数代码(为这个新对象添加属性方法)
  4. 返回新对象
function Person(name, age, job) {
    this.name = name
    this.age = age
    this.job = job
    this.sayName = function () { // 每定义一个函数,就是实例化一个对象
        alert(this.name)
    }
}
var p1 = new Person("Y",18,"police")
var p2 = new Person("H",8,"teacher")
// p1.constructor == Person
// p2.constructor == Person

// ( p1 instanceOf Object  )  true
//  ( p1 instanceOf Person  )  true
// ( p2 instanceOf Object  )  true
//  ( p2 instanceOf Person  )  true
  • 在全局作用域调用函数,this总是指向GLOBAL对象
function person(name, age, job) {
    this.name = name
    this.age = age
    this.job = job
    this.sayName = function () { 
        alert(this.name)
    }
}
person('Leo', 16, 'doctor') 
window.sayName() //leo
1.3.2 将构造函数当做函数

任何函数,只要通过new操作符来调用,便可以作为构造函数;若不用,即与普通函数无异。this会指向Global对象(在浏览器中就是window)

1.3.3 将方法定义到构造函数外部

缺点: 在全局作用域上定义的函数,若只能被某个对象调用,不合理;并且,如果对象需要定义很多方法,则需要定义很多个全局函数,对于这个自定义的引用类型就丝毫没有封装性可言。

function Person(name, age, job) {
    this.name = name
    this.age = age
    this.job = job
}
function sayName() {
    alert(this.name)
}

1.4 原型模式

1.4.1 理解原型模式
function Person() {}
Person.prototype.name = "Y" 
Person.prototype.age = 18
Person.prototype.job = "police" 

Person.prototype.sayName = function () {
    alert(this.name)
}
  1. 使用原型对象的好处:让所有的对象实例共享它所包含的属性和方法;
  2. 只要创建了一个新函数,就会为该函数创建一个prototype属性,指向函数的原型对象
  3. 原型对象会自动获得constructor属性,该属性包含一个指向prototype属性所在函数的指针(constructor属性指向构造函数);
  4. 对象的constructor属性最初是用来表示对象类型的;
  5. 构造函数创建的实例,实例内部包含一个指针,指向构造函数的原型对象
  6. Person.prototype.isPrototypeOf(p1) // true
  7. Object.getPrototypeOf(p1) == Person.prototype // true
  8. 代码读取某个对象某个属性时,先搜索对象实例,若无再搜索原型对象;
  9. 若实例中添加的属性和原型属性同名,会屏蔽原型中的属性(因为实例只能访问原型中的值,而不能重写);
  10. 若将实例中的同名属性设为null,并不能恢复与原型的连接,需要使用delete操作符完全删除实例属性
  11. p1.hasOwnProperty("name")// true hasOwnProperty()方法可以检测一个属性是存在于实例中还是存在于原型,若来自实例则返回true
    在这里插入图片描述
1.4.2 in操作符

in操作符会在通过对象能访问给定属性时返回true,无论属性存在于实例还是原型中

"name" in p1 // true
1.4.3 确认属性存在于原型
function hasPrototypeProperty(object,name) {
    return !object.hasOwnProperty(name) && (name in object)
}
1.4.4 更简单的原型语法

缺点: constructor属性不再指向Person,切断了实例和原型对象的联系;对包含引用类型值的属性,被实例共享会造成问题。
特点: 减少不必要的输入(每添加一个属性/方法就要多敲一遍Person.prototype)

将Person.prototype设置为一个以对象字面量形式创建的新对象,本质上重写了prototype对象(创建函数时自动创建的原型对象),导致constructor属性指向Object构造函数,尽管instanceof还能返回正确的结果。

function Person() {}
Person.prototype = {
    name : "Y",
    age : 18,
    job : "police",
    sayName: function () {
        alert(this.name)
    }
}
p1 instanceof Object // true
p1 instanceof Person // true
p1.constructor == Person // false
p1.constructor == Object // true

增加constructor属性,确保该属性能访问到适当的值。

function Person() {}
Person.prototype = {
    constructor : Person,
    name : "Y",
    age : 18,
    job : "police",
    sayName: function () {
        alert(this.name)
    }
}
1.4.5 原生对象的问题
function Person() {}
Person.prototype = {
    constructor : Person,
    friends:["Yoona","Jessica"], // 数组,引用类型
    name : "Y",
    age : 18,
    job : "police",
    sayName: function () {
        alert(this.name)
    }
}
p1.friends.push("Krystal")
p1.friends == p2.friends
1.4.6 组合使用构造函数模式和原型模式

构造函数模式用于定义实例属性,原型模式用于定义方法和共享的属性。(实例属性中的引用类型互不干扰)

function Person(name, age, job) {
    this.name = name
    this.age = age
    this.job = job
    this.friends = ["Yoona","Jessica"]
}

Person.prototype = {
    constructor : Person,
    sayName: function () {
        alert(this.name)
    }
}
1.4.7 原生对象的原型

通过原生对象的原型,不仅可以取得所有默认方法的引用,也可以定义新方法。但不推荐在产品化的程序中修改原生对象的原型:
如果因为某个实现中缺少某个方法,就在原始对象的原型中添加,那么当在另一个支持该方法的实现中运行代码时,就可能会导致命名冲突,而且这样做也可能会意外地重写原生方法。

1.5 梳理

在这里插入图片描述

1.6 修改原生对象的原型

String.prototype.startsWith = function(){}

在当前环境中,所有字符串都可以调用startsWith,但不推荐,可能会产生命名冲突,也可能会意外地重写原生方法。

String.prototype.
toString = function () {
    console.log('修改原生对象默认方法')
}

'str'.toString() // 修改原生对象默认方法

将原生对象原型指向空,没效果

String.prototype = null
console.log('str'.toString()) // str

2. 属性类型 P139

2.1 数据属性

  1. [[Configurable]]:能否通过delete删除属性,默认为true
  2. [[Enumerable]]:能否通过for-in循环返回属性,默认为true
  3. [[Writeable]]:能否修改属性的值,默认为true
  4. [[Value]]:属性值,默认为undefined
  • 一旦把属性定义为不可配置的,就不能再把它变回可配置了;
  • 要修改属性默认的特性,必须使用Object.defineProperty(),3个参数:对象、属性名、描述符对象(属性必须是数据属性),若不指定数据属性,则默认为false;

2.2 访问器属性

  1. 包含一对getter和setter函数(都不是必须的),有4个特性:
    1) [[Configurable]]
    2) [[Enum而able]]
    3) [[Get]]:读取属性时默认调用的函数,默认值undefined
    4) [[Set]]:写入属性时默认调用的函数,默认值undefined
  • 属性 _year 前面的下划线用于表示只能通过对象方法访问的属性;
  • 使用访问器属性的常见方式:设置一个属性的值会导致其他属性发生变化;

2.3 读取属性

Object.getOwnPropertyDescriptor(),2个参数:对象、属性

var descriptor = Object.getOwnPropertyDescriptor(person,"age")
alert(age.value)
alert(age.enumerable)
alert(age.configurable)
alert(age.get)

方法集合

Object.defineProperty() // 修改属性默认值
Object.getOwnPropertyDescriptor() //读取属性的描述符
谁谁的原型对象.isPrototypeOf(实例) // 参数是实例,判断实例内部是否有指向构造函数原型对象的指针
hasOwnProperty() // 检测属性来自实例(返回true)还是原型

3. 继承(子类继承父类的特征和行为)

3.1 原型链

原型链的问题

  1. P166 原型链虽然很强大,可以用它来实现继承,但它也存在一些问题,其中最主要的问题来自包含引用类型值的原型。包含引用类型值的原型属性会被所有实例共享,因此要在构造函数,而不是原型对象中定义属性。
  2. 在创建子类型的实例时,不能向超类型的构造函数中传递参数。(没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数)

让原型对象等于另一个类型的实例。

SubType.prototype = new SuperType()

SubType的实例指向SubType的原型,进而又指向SuperType的原型
在这里插入图片描述

3.2 默认的原型

所有引用类型默认都继承了Object,所有函数的默认原型都是Object的实例,因此默认原型都会包含一个内部指针,指向Object.prototype,这也正是所有自定义类型都会继承toString()、valueOf()等默认方法的根本原因。完整的原型链如下:
在这里插入图片描述

instance instanceof Object // true
instance instanceof SuperType // true
instance instanceof SubType // true

Object.prototype.isPrototypeOf(instance) // true
SuperType.prototype.isPrototypeOf(instance) // true
SubType.prototype.isPrototypeOf(instance) // true
function Fun() {}

console.log(Fun.prototype.__proto__==Object.prototype) // true

3.3 组合继承

  1. 父构造函数内有引用类型属性值
  2. 子构造函数内使用call(保证了实例不会都指向同一个引用类型)
  3. 子构造函数原型指向父构造函数原型(Object.create
  4. 子构造函数原型里的构造器指向自己(知晓实例由谁创建)
    在这里插入图片描述

4. 方法整理

  • A.isPrototypeOf(a)
    // 实例a的__proto__指向A的原型
    // 判断原型对象A是否是实例a的原型,是则返回true
  • a.hasOwnProperty(‘name’) 判断属性是否存在于实例中(实例属性),是则返回true
    Object.keys() 获得对象上所有可枚举的属性
  • ‘name’ in a 无论原型/实例,只要是能访问得到的属性,in操作符返回true(包括constructor)'name’字符串
    for-in循环,返回所有能通过对象访问的、可枚举的属性 ,(遍历一个实例对象,原型上的属性也会打印、要只打印实例属性,需要配合hasOwnProperty
  • Object.getOwnPropertyNames(),得到所有实例属性,包括constructor
function Test(name) {
    this.name = name
}
Test.prototype.name = 'hhh'
let tObj = new Test('yyy')
console.log('name' in tObj) // true
console.log('constructor' in tObj) // true
for(var prop in tObj){
    console.log('for-in', prop) // name
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值