JS学习笔记整理五 面向对象的程序设计

栏目: JavaScript · 发布时间: 4年前

内容简介:对象的属性类型包含:数据属性、访问器属性

对象的属性类型包含:数据属性、访问器属性

  1. 数据属性

    • 可配置:Configurable,是否能用delete删除。能否修改属性特性。此值一旦设为false,就不可逆。
    • 可修改:Writable,能否修改value值。
    • 可枚举:Enumerable,能否通过for...in枚举。
    • 值:Value
  2. 访问器属性

    没有value和writable,多了一对get和set。

    不管writable真假,只要configurable为真,通过Object.defineProperty就能把特性改为访问器属性。

Object.getOwnPropertyDescriptor(o,”prop”); :读取属性特性。

Object.getOwnPropertyDescriptors(o); :读取所有属性特性。

犀牛书里把访问器accessor称为存取器,我觉得这样更形象。可以使用直接量语法的扩展语法来定义属性,比如下面两种方法:

var o={
    name:'tiedan',
    get ga(){return this.name},
    set ga(value){this.name=value}
}
var p={name:'tiedan'};
Object.defineProperty(p,'ga',{
    set:function(value){this.name},
    get:function (){return this.name}});
console.log(Object.getOwnPropertyDescriptors(o));
console.log(Object.getOwnPropertyDescriptors(p));
/*{ name:{ value: 'tiedan',writable: true,enumerable: true,configurable: true },
    ga:{ get: [Function: get ga],set: [Function: set ga],enumerable: true,configurable: true } }
  { name:{ value: 'tiedan',writable: true,enumerable: true,configurable: true },
    ga:{ get: [Function: get],set: [Function: set],enumerable: false,configurable: false } }*/
//方括号里有细微区别。    
复制代码

使用 Object.defineProperty 或者 Ojbect.defineProperties ,不显示定义configurable、writable、enumerable,则这三个特性默认值都为false

vue的核心实现就是利用 Object.definProperty 劫持属性的get和set特性,来实现双向绑定的。

Object.preventExtensions 对象不能添加新属性(可删除原有属性)。可通过Object.isExtensible来检测对象是否能扩展。

Ojbect.seal 可以密封对象,密封后的对象不能删除和添加属性。所有属性特性的[[configurable]]都为false。可通过Object.isSeal来检测对象是否为密封对象。

Object.freeze 可以冻结对象,比Object.seal更进一步,对象不能有任何变化。所有特性均为false。可通过Object.isFrozen来检测对象是否为冻结对象。

通过 Object.getOwnPropertyNames 看到的Object.prototype的属性,这部分属性都是可继承的。

[ 'constructor',             //给所有对象的构造器属性,创建函数时,就会自动创建函数原型,和其constructor属性,如果重写原型,则继承这个
 '__defineGetter__',      //一些浏览器的非标准方法,在Object.defineProperty不支持的时候,可以尝试用这个定义属性的特性。
 '__defineSetter__',      //同上类似
 'hasOwnProperty',     //检测对象是否有自有的属性
 '__lookupGetter__',    //非标准方法,用来返回命名属性的Getter方法
 '__lookupSetter__',    //同上类似
 'isPrototypeOf',         //是否为检测对象的原型。其原理同instanceof一样都是查找原型链,只是instanceof后面是构造函数
 'propertyIsEnumerable',//属性是否是可枚举的,这个和Object.getOwnPropertyDescriptor()里得到的enumberable属性是一样的
 'toString',                  //
 'valueOf',                  //
 '__proto__',               //一般是内部属性,一个原型指针
 'toLocaleString' ]     //
复制代码

也可以通过 Object.getOwnPropertyNames 看看Object的属性和方法。

创建对象

犀牛书例子,最先由道格拉斯.克罗克福德提出,这个人在两本书里出现N多次。下面的是犀牛书p122的例子,红皮书类似的例子在p169。 Object.create() 创建一个新对象,参数是新对象的原型。create方法有两个参数,第二个参数与defineProperties的第二个参数一样。

function inherit(p){
    if(p==null) throw TypeError();
    //首先排除null,本身typeof就可能有null。Object.create是可以传null的
    if(Object.create){
        return Object.create(p);
    }
    var t=typeof p;
    if(t!="object"&&t!="function")throw TypeError;//不能是基本类型值
    function a(){}
    a.prototype=p;
    return new a();
  }
复制代码

new的实现大致如下:

o = {};
o.__proto__=f.prototype;
f.apply(o,arguments);
复制代码

所以 new Object(); 不如直接用 {} 执行快就是因为这个原因吗?

自定义的new如下:

function New(f) {
  //返回一个func
  return function () {
    o = {};
    o.__proto__=f.prototype;
    f.apply(o, arguments);//继承父类的属性
    return o; //返回一个Object
  }
}
复制代码

通过 new fn(); 或者 Object.create(p); 其实都是返回新对象的方法。

工厂模式

“生产对象”,所以一般直接调用。不使用new(使用也能返回正常对象)。

function Person(name,age){
    var o=new Object();
    o.name=name;
    o.age=age;
    return o;
}
复制代码

弊端 :解决了对象创建问题,但没有解决对象识别问题

构造函数模式

function Person(name,age){
    this.name=name;
    this.age=age;
}
复制代码

没有显示创建对象,直接将属性和方法赋给this,(可以)没有return。通过new来调用。

实际上步骤:

var o={};//创建新对象
o.__proto__=fn.prototype;//对象的原型指针赋值
fn.apply(o,arguments);//this指向新对象,执行构造函数代码
return o;//返回新对象
复制代码

new fn() 就是类似上面的过程。构造函数fn的执行实际上就是给o对象赋值操作的过程。

弊端 :方法重复定义,即便放到全局也只适合对象调用,还容易污染全局作用域。

原型模式

function Person(){
}
Person.prototype.name='tiedan';
Person.prototype.age='1';
复制代码

对于原型有两种写法:

fn.prototype.p1=value1;
fn.protptype.p2=value2;
复制代码
fn.prototype={
  p1:value1,
  p2:value2
}
复制代码

第二种看起来更清楚,但是存在问题,因为这相当于重写原型对象。之前定义好的一些属性方法可能会丢失,constructor属性需要显示重新指向构造函数。要不就没了。在第二种重写原型之前不能先new fn,这会导致重写原型之后切断了构造函数fn和最初的原型之间的联系。因为prototype属性本身存储的就是一个指针。而对象和原型之间的联系仅靠__proto__存储的一个指针。相当于原型链出了问题,无法通过这个属性找到新的原型对象,获得新对象的属性和方法。说白了就是fn和obj指向了不同的原型对象。

function Person(name,age){
    this.name=name;
    this.age=age;
}
Person.prototype.height=100;
var p1=new Person("tiedan",36);
Person.prototype={
    constructor:Person,
    height:180
}
var p2=new Person("someone",29);
console.log(p1 instanceof Person);//false,因为其__proto__指向的已经不是最新的Person.prototype。经管如此,p1.constructor.prototype还是对的。
console.log(p1.height);//100
console.log(p2 instanceof Person);//false
console.log(p2.height);//180
复制代码

弊端 :除了上面所说的,还有就是因为原型对象的属性和方法都是实例共享的。那么对于属性是引用类型,比如数组,大家也是共享的同一个引用对象。修改这类属性值,实际上会影响到所有实例。

建议属性值是引用类型的,不要用原型模式,除非你就需要这种共享引用类型的方式。

in 运算符有两种用法,一种用于for...in。另一种在单独使用时, in 操作符会在通过对象能够访问给定属性时返回true,无论该属性存在于实例中还是原型中!

function hasPrototypeProperty(object,name){
  return !object.hasOwnProperty(name)&&(name in object);
}
复制代码

动态原型模式

function Person(name,age){
    this.name=name;
    this.age=age;
    if(typeof this.sayName!='function'){
        Person.prototype.sayName=function(){console.log(this.name);}
    }
}
复制代码

相对比较完美的一种模式。

使用这种模式,同样不能用对象字面量重写原型。思考一下new的实现步骤,就能明白,构造函数里重写原型,则必然会在o对象创建之后切断与原有原型的联系。

寄生构造函数模式

function Person(name,age){
    var o=new Object();
    o.name=name;
    o.age=age;
    return o;
}
var tiedan=new Person('铁蛋',1);
console.log(tiedan instanceof Person);//false
复制代码

这个模式和工厂模式的函数完全一样,只是这个直接通过new来调用。测试了一下,当做构造函数使用,末尾return会代替构造函数正常的return值。

弊端 :返回的对象和构造函数没什么关系。不能依赖instanceof来检测对象。(返回的实际上是里面的o类型)

稳妥构造函数模式

durable object持久对象 克罗克福德发明

function Person(name,age){
    var o=new Object();
    o.sayName=function(){
        alert(name);
    };
    return o;
}
var tiedan=Person('铁蛋');
复制代码

类似于寄生构造函数模式。没有公共属性,不引用this对象。适合在安全环境或防止数据被其他应用程序改动时使用。1.不引this,2.不用new

其实是利用了闭包原理。即便对象被添加属性和方法,也无法篡改原始值。

继承

继承分为接口继承和实现继承

红皮书p162说es中无法实现接口继承,其实es4版本是实现了接口继承的(as3.0)。说不定哪天interface就从保留字变关键字了。

原型链

  1. 别忘记默认原型: Object.prototype

  2. 确定原型和实例的关系, instanceofisPropertyOf

  3. 谨慎的定义方法,方法覆盖,红皮书p166必须用SuperType实例替换SubType的原型后再定义原型方法,否则原型对象一重写就没了。

  4. 原型链的问题:

    类似之前原型模式引用类型的问题。 SubType.prototype=new SuperType(); 实际上就是重写原型。原型属性包含引用类型就容易出现意料之外的情况。

借用构造函数

constructor stealing 明明是偷非说是借~

function SubType(age){
 SuperType.call(this,"tiedan");
 this.age=age;
}
复制代码
  1. 可向父类构造函数传参
  2. 问题:父类原型对子类不可见。

组合继承

将借用构造函数和原型链技术结合

  1. 借用构造函数得到实例属性

  2. 原型链技术继承原型属性和方法,同时还可以扩充自己的原型方法

SubType.prototype=new SuperType();
SubType.prototype.constructor=SubType;
SubType.prototype.prop=…
复制代码

原型式继承

又是道格拉斯.克罗克福德... 提到了object(o)函数,犀牛书里名为inherit(p)

var person={};
var anotherPerson=Object.create(person);
anotherPerson.prop=...
//继承的引用类型会被实例共享
复制代码

Object.create在使用一个参数的时候,行为和object(o);相同,使用两个参数的时候,第二个参数和Object.defineProperties的第二个参数格式相同。以这种方式指定的属性会覆盖原型对象的同名属性。

其实inherit也可以加第二个参数,也就是做一个浅复制,这可以参看犀牛书的浅复制。extend方法。

var p2=Object.create({},{"hehe":{value:30 }});
console.log(p2);//{}
//什么都看不见,因为和Object.defineProperties一样,enumerable不显示定义默认为false,所以看不到。
复制代码

寄生式继承

克罗克福德推广的

function createAnother(o){
    var anotherPerson=Object.create(o);
    anotherPerson.sayHello=function(){console.log('hello');}
    return anotherPerson;
}
var anotherPerson=createAnother({});
anotherPerson.sayHello();
复制代码

主要考虑对象而不是自定义类型和构造函数的情况下使用

寄生式继承的思路与寄生构造函数和工厂模式类似。创建一个用来封装继承过程的函数。在函数内部以某种方式增强对象,最后像自己做了所有工作一样返回对象。

寄生组合式继承

之前的组合式继承最大的问题是要调用两次父类构造函数,一次是借用构造函数,一次是重写子类原型。其实没必要调用两次构造函数。可以只调用一次借用构造函数,另一次寄生式new一个空构造函数。只为了复制父类原型属性和方法。

function SuperType(name){
    this.name=name;
    this.colors=['red','green','blue'];
}
SuperType.prototype.sayName=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.sayAge=function(){console.log(this.age);}

复制代码

改为:

function inheritPrototype(SubType,SuperType){
    var prototype=Object.create(SuperType.prototype);//创建对象
    prototype.constructor=SubType;//增强对象
    SubType.prototype=prototype;//指定对象,依然重写原型啊...
}

function SuperType(name){
    this.name=name;
    this.colors=['red','green','blue'];
}
SuperType.prototype.sayName=function(){console.log(this.name);}

function SubType(name,age){
    SuperType.call(this,name);//第二次调用
    this.age=age;
}
inheritPrototype(SubType,SuperType);//第一次调用
SubType.prototype.sayAge=function(){console.log(this.age);}

复制代码

上面例子依然重写了原型啊。所以,顺序很重要。否则,最后两行换一下位置,sayAge就被盖掉了。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

智能革命

智能革命

李彦宏 等 / 中信出版集团 / 2017-5-1 / 68.00元

人类历史上的历次技术革命,都带来了人类感知和认知能力的不断提升,从而使人类知道更多,做到更多,体验更多。以此为标准,李彦宏在本书中将人工智能定义为堪比任何一次技术革命的伟大变革,并且明确提出,在技术与人的关系上,智能革命不同于前几次技术革命,不是人去适应机器,而是机器主动来学习和适应人类,并同人类一起学习和创新这个世界。“人工智能”正式写入2017年政府工作报告,折射出未来人工智能产业在我国经济发......一起来看看 《智能革命》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具