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

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

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

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

  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就被盖掉了。


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

查看所有标签

猜你喜欢:

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

TensorFlow实战

TensorFlow实战

黄文坚、唐源 / 电子工业出版社 / 2017-2-1 / 79

Google近日发布了TensorFlow 1.0候选版,这个稳定版将是深度学习框架发展中的里程碑的一步。自TensorFlow于2015年底正式开源,距今已有一年多,这期间TensorFlow不断给人以惊喜,推出了分布式版本,服务框架TensorFlow Serving,可视化工具TensorFlow,上层封装TF.Learn,其他语言(Go、Java、Rust、Haskell)的绑定、Wind......一起来看看 《TensorFlow实战》 这本书的介绍吧!

SHA 加密
SHA 加密

SHA 加密工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具