JavaScript专精系列(2)——对象、构造、原型、继承

1、引言

最近,研究了一下JavaScript 关于对象的部分。有些东西刷新了我对 Js 的认知,就比如下面的几个问题:

1、如何删除一个对象的属性?所有对象的属性都是可以删除的吗?

2、for in 在遍历对象的过程中,会存在什么问题?

3、var person1 = new Person() 这个过程发生了什么?

4、什么是构造函数?它在使用的时候有什么样的坑?

5、构造和继承的本质区别是什么?

6、JavaScript常见的设计模式有哪些?是针对什么问题提出的?这些设计模式的缺点又是什么?

2、关于对象的属性类型

(1)、描述

对象拥有属性,一般而言,给一个对象添加一个属性的语法是这样的:

var o = new Object();
o.name = 'mapbar_front';

那么,在添加这样的一条属性,系统为我们做了什么?

对象的每一条属性,都不仅仅代表着一个 value ,还代表着能够对这个 value 进行各种操作的 type!

在给一个对象添加一条属性的时候,系统默认这个对象的属性是可删除的、可for in 遍历的、可读写的。分别对应着这么几条属性 Configurable、Enumberable、Writable。

configurable——默认true,表示能够被delete语句删除这个属性。

enumberable——默认true,表示能够被for in遍历这个属性。

writable——默认true,表示能够改变属性的value值。

value——默认为Undefined。存储这个属性的具体value。

(2)、Object.defineProperty()

JavaScript提供了原生的方法,让我们可以定义一个可配置的属性。

平时我们定义一个属性的方式如下:

var person = {};
person.name = 'mapbar_front';

他是和下面这个是等价的:

var person = {};
Object.defineProperty(person,'name',{
    value: 'mapbar_front',
    configurable: true,//可delete
    enumberable: true,//可for in
    writable: true//可读写
});

通过修改configurable、enumberable、writable的值为true/false,我们就能对这个属性的操作权限进行控制。

(3)、其他相关函数

Object.defineProperties()——定义多个属性的函数

Object.defineProperties(book,{
    name: { 
        enumberable: true,
        value: 'JavaScript高级程序设计'
    },
    year: { 
        configurable: true,
        value: 2012
    }
})

3、设计模式

在创建对象的过程中,由于会在创建多个对象的时候,产生大量的冗余代码,所以有了工厂模式!

(1)、工厂模式
function createObj(name,age){
    var o = new Object();
    o.name = name;
    o.age = age;
    return o;//这个是重点
}
var p1 = createObj('cat',3);
var p2 = createObj('dog',5);

所以,工厂模式的本质就是,返回一个创建好的Object实例

构造函数的由来:
然而,工厂模式如何来解决对象识别的问题?现在我们只能描述一个事物属于Object类,如何描述一个事物是Cat类,或者是Dog类?所以构造函数的模式就应用而生!

(2)、构造函数模式
//描述dog类
function Dog(){
    this.type = 'dog';
    this.age = 5;
}
var dog1 = new Dog();
console.log(dog1);//{ type:'dog', age: 5 }

上面的方式,也适合描述一个Cat类,这样我们分清了dog属于Dog类,cat属于Cat类。

(3)、构造函数模式和工厂模式的显著区别
  • 没有return。
  • 使用this进行指向。
  • 使用new 进行实例化。
(4)、构造函数和普通函数区别?

如果说,构造函数和普通函数有什么区别?那么请记住这么一句话:构造函数和普通函数在语法方面无任何区别,但是在功能需求上有很大区别

构造函数的由来,是为了解决对象创建的问题而存在的!

普通函数的由来,是为了复用代码块而存在的!

//普通函数也可以new
function add(){
    console.log(1);
}
var obj = new add();//obj是一个空对象而已
//构造函数能new出属性的唯一原因就是使用了this
(5)、构造函数自身的缺陷

那它有什么问题呢?

function Person(){
    this.name = 'mapbar_front';
    this.getName = function(){ 
        alert(this.name); 
    }
}

每次实例化一个对象,getName方法同样也会被实例化,但是getName方法本质上是一个代码块相同的函数。这样极大的损耗了内存空间。(函数要比变量耗费大的多的内存)

(6)、原型模式
function Person(){}
Person.prototype.name = 'mapbar_front';
var p1 = new Person();
var p2 = new Person();
//p1.name 就是'mapbar_front'
//p2.name 也是'mapbar_front'
//他们共享一块内存空间
(7)、原型模式的缺陷

正是因为原型的共享问题,虽然解决了上面描述的占用一块内存空间,但是对象并不是所有的属性都应该被共享。

p1和p2都是Person的实例,他们可以有相同的方法。但是他们的name可能需求就是各自不同。原型的占用一块内存空间的方式是无法解决这个问题的。

比如两个人,可以有相同的工作,甚至姓名都可以相同,但是内在的“思维和意识”,绝对不可能完全相同的。

(8)、原型模式和构造函数模式的组合模式

构造函数解决对象创建的私有问题!
原型模式解决对象创建的共享问题!
两者的结合应该很完美:

function Person(name){
    this.name = name;
}
Person.prototype.getName = function(){
    alert(this.name);
}
var p = new Person('map');
var p2 = new Person('front');
//实现了属性私有,方法公有,挺好!

那么有没有什么更精简的表达方式??

(9)、动态原型模式
function Person(name){
    this.name = name;
    if(typeof this.getName !== 'function'){ 
        Person.prototype.getName = function(){
            alert(this.name);
        }
    }
}

这样,我们不必提前把原型方法定义好,在实例调用的时候,就可以动态的添加和调用!!!

还有什么模式?

在JavaScript的标准中,还有两种模式,寄生构造函数模式和稳妥构造函数模式。

只不过都有其很大的局限性,不常用,所以不做其他提示工作。

4、关于原型我们该知道什么?

我们需要知道这样的一些基本东西。

函数都拥有原型,函数的原型使用prototype来指向。在JavaScript中,一旦定义了一个函数,系统就自动了生成了一个函数的原型。

函数的原型是一个对象,函数的原型的构造就是指向自身。

Person.prototype.constructor == Person;//true
(1)、定义了一个构造函数,它的原型对象如何生成
  • 定义一个函数的同时,系统会new一个Object的实例。
  • 然后把函数的prototype指向这个实例。
  • 最后把这个实例的构造再指向函数本身。

所以,原型对象类似下面这样的表示:

Person.prototype = {
    construcor: Person,
    .....//其他new Object()得到的属性。
}
(2)、new之后发生了什么?

一定注意
一个构造函数,使用了new操作符,经历了下面几个阶段。

  1. var o = new Object();
  2. 把this指向o;
  3. 把构造函数的作用域也给了这个对象
  4. 返回这个对象

5、继承

继承的方式有两种,一种是原型链继承,另一种是借用构造函数。

借用构造函数的方式的思想就是,在子类型构造函数内部调用超类型构造函数。

(1)、call、apply
function Super(name){
    this.name = name;
}
function Sub(name){
    Super.call(this,name);
}
var sub1 = new Sub('mapbar_front');
//继承了Super的name属性
(2)、prototype继承

原型继承,就是把子类型构造函数的原型,指向超类型构造函数的一个实例。

Sub.prototype = new Sup();

一般而言,我们还会把原型的constructor再次指向Sub。

Sub.prototype.constructor = Sub;

不进行上面的指向会有问题吗?

不会有使用上的大障碍,单是会有语法上的紊乱。
var sub = new Sub();
这时候sub.constructor应该按理等于Sub,
但是不指定上面的指向就会是Sup。
(3)、最后得到一个封装的原型继承
function extend(Sub,Sup){
    var o = new Object();
    o.constructor = Sub;
    Sub.prototype = o;
}

6、其他问题

(1)、读取属性的特性

Object.getOwnPropertyDescriptor();

var book = {};
Object.defineProperty(book,'name',{
    value:'JavaScript高级程序设计'
});
var descripter = Object.getOwnerPertyDescripter(book,'name');

console.log(descripter.configurable);//true
(2)、判定是否是实例的实例属性

hasOwnerProperty();

var object = { name: 'mapbar_front' };
object.hasOwnerProperty('name');//true

7、我的总结

1、一个对象,拥有属性,这个属性就会有相应的value,除了这个value之外,这个属性还有标记它自身功能权限的东西——configurable、 enumberable、 writable。

2、定义一个具有操作权限的函数的属性:Object.defineProperty()

3、工厂模式:是为创建大量对象而提出,但无法判断对象类型,也就是无法描述class的概念。

4、构造函数模式:解决了类型判定的问题,但是无法解决方法工农共享的问题。

5、原型模式:解决了共享的问题,但是它也正因为共享,成就了它的不完美特性。

6、构造函数+原型模式的组合模式:是目前较为成熟的面向对象编程方案。构造函数解决私有属性问题,原型模式解决共有方法问题。

7、继承有两种方式——原型继承和借用构造函数的继承。

8、原型继承的封装:

function extends(Parent,Child){
    var o = new Parent();
    o.constructor = Child;
    Child.prototype = o;
}

9、借用构造函数的方式主要是靠call和apply来实现。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值