javaScript面向对象的程序设计

  • 面向对象

面向对象可以理解成具有类的概念。 可以通过类创建任意多个具有相同属性和方法的对象。由于javascript中没有类的概念。故和基于类的语言有所不同。 ECMA-262将其定义为无序属性的集合。 七属性可以包含基本值。对象和方法。 换种说法可以认为对象就是一组没有特定顺序的值。对象的每一个属性和方法都有属于自己的名字。 而每一个名字有有对应一个值。即常说的键值对。

  • 理解对象

        1. 通过Object()实例一个对象。 并为其添加属性和方法

let  person = new Object();
person.name = "小王";
person.age = 24;
person.tall = 178;
person.getName = function () {
    console.log(this.name)
};

console.log(person)


{ name: '小王', age: 24, tall: 178, getName: [Function] }

       2. 通过字面量创建

let person = {
    name : "小王",
    age: 24,
    tall:178,
    getName(){
        console.log(this.name)
    }
}

console.log(person)

{ name: '小王', age: 24, tall: 178, getName: [Function: getName] }

两种方法都创建了相同属性的和方法的对象。 而在创建创建属性的时候其属性也携带一些特征。 javaSript通过属性类型来描述这些特征

       3. 属性特征

ECMA-262第5版规定只有内部才具有特征时。描述了属性的各个特征。这些特征是为实现javaScript引擎用的。 因此在js中不能直接访问他们。 为了表示是内部属性。 规定将其放在两对方括号中。 ESCAscript具有两种属性。数据属性和访问属性

  • 数据属性

数据属性包含一个数据值的位置。在这个位置可以读取和写入值。数据属性有4个描述其行为的特性 

  1. [[Configurable]]:能否被delete删除属性重新定义  默认为true
  2. [[Enumerable]]:能否被for-in枚举 默认为true
  3. [[Writable]]:能否修改属性值 默认为true
  4. [[Value]]:数据的数据 默认为undefined

要修改默认属性就要必须通过Object.defineProperty()方法。 该方法有三个参数。 参数一表示属性所在的对象。参数二表示属性的名字。 参数三表示一个描述符对象。  

let person = {
    name : "小王",
    age: 24,
    tall:178,
    getName(){
        console.log(this.name)
    }
}

console.log(person);
console.log("------")
Object.defineProperty(person,"tall",{
    writable:false,
    value:2000
});
console.log(person.tall);
console.log('--------');
person.tall = 165;
console.log(person.tall);


{ name: '小王', age: 24, tall: 178, getName: [Function: getName] }
------
2000
--------
2000

将对象的tall属性设置为可读后。 我们尝试去修改的时候是无法修改的。 这种操作在非严格模式下会被忽略。在严格模式下就会报错。同样的操作还适用于configurable。

let person = {
    name : "小王",
    age: 24,
    tall:178,
    getName(){
        console.log(this.name)
    }
}

console.log(person);
console.log("------")
Object.defineProperty(person,"tall",{
    configurable:false,
    value:2000
});
console.log(person.tall);
console.log('--------');
delete person.tall;
console.log(person.tall);

{ name: '小王', age: 24, tall: 178, getName: [Function: getName] }
------
2000
--------
2000

将tall属性设置为configurable等于false时调用delete时在这种操作在非严格模式下会被忽略。在严格模式下就会报错。如果在将其修改为true则会报错。在调用Object.defineProperty方法将configurable修改为true的同时修改writable为false

let person = {
    name : "小王",
    age: 24,
    tall:178,
    getName(){
        console.log(this.name)
    }
}

console.log(person);
console.log("------")
Object.defineProperty(person,"tall",{
    configurable:false,
    value:2000
});

Object.defineProperty(person,"tall",{
    configurable:true,
    writable:false
});

console.log(person);


{ name: '小王', age: 24, tall: 178, getName: [Function: getName] }
------
/Users/apple/Desktop/javaScript/object.js:17
Object.defineProperty(person,"tall",{
       ^

TypeError: Cannot redefine property: tall
    at Function.defineProperty (<anonymous>)
    at Object.<anonymous> (/Users/apple/Desktop/javaScript/object.js:17:8)
    at Module._compile (internal/modules/cjs/loader.js:1156:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1176:10)
    at Module.load (internal/modules/cjs/loader.js:1000:32)
    at Function.Module._load (internal/modules/cjs/loader.js:899:14)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:74:12)
    at internal/main/run_main_module.js:18:47

也就是说可以调用Object.defineProperty修改同一属性。但是将configurable设置成false后就受限制了

  • 访问属性

访问器属性不包含数据值,它们包含一对getter和setter函数(这两个函数都不是必须的)。在读取访问器属性时会调用getter函数,这个函数会负责返回有效的值,在写入访问器属性时,会调用setter函数并传入新值。这个函数负责决定如何处理数据。访问器属性有如下4个特性

  1. [[Configurable]]:表示能否通过delete删除属性从而重新定义属性,这个特性的默认值为true
  2. [[Enumerable]]:表示能否通过for-in循环返回属性,这个特性的默认值为true
  3. [[Get]]:在读取属性时调用的函数。默认值为undefined
  4. [[Set]]:在写入属性时调用的函数。默认值为undefined

访问器属性不能直接访问。 要通过Object.defineProperty()来定义

let person = {
    name : "小王",
    age: 24,
    tall:178,
    getName(){
        console.log(this.name)
    }
}


Object.defineProperty(person,'getData',{
    get(){
        return this.age
    },
    set(v) {
        if (v > 28){
            this.age = v;
            this.tall = '身高与体重不符合'
        }
    }
});


console.log(person.getData);

person.getData = 40;

console.log(person.tall)


24
身高与体重不符合

上述代码定义了访问器。 不难发现在直接使用访问器时。其实调用的事get方法并且返回对应的值。 当修改访问器的值后从而导致了对象另一个属性的变化 ,要说明的是get和set方法不一定要同时存在。 只指定get方法意味着不能写。 反之只能写不能读。

  • 定义多个属性

可以通过Object.defineProperties()给对象添加多个属相。 该方法需要两个参数。 参数一表示需要添加或者修改的对象。 第二个对象的属性与第一个对象中要添加和修改的属性药对应

let person = {};

Object.defineProperties(person,{
    name:{
        value:"Jack"
    },
    age:{
        value:24
    },
    getName:{
        get() {
            return this.name
        },
        set(v) {
            this.name = v
        }
    },
});


console.log(person.name)

console.log(person.getName)


Jack
Jack
  • 读取属性的特征

Object.getOwnPropertyDescriptor()可以返回给定属性的描述符。 接受两个参数。 参数一表示所在的对象。参数二表示表示要读去的属性描述符的名称。若果是访问器则返回get。set。enumerable  configurable。 如果是数据属性则返回value。writable   enumerable  configurable。

let person = {};

Object.defineProperties(person,{
    name:{
        value:"Jack"
    },
    age:{
        value:24
    },
    getName:{
        get() {
            return this.name
        },
        set(v) {
            this.name = v
        }
    },
});


let a = Object.getOwnPropertyDescriptor(person,"getName");
console.log(a)


let b = Object.getOwnPropertyDescriptor(person,'name');
console.log(b)


{
  get: [Function: get],
  set: [Function: set],
  enumerable: false,
  configurable: false
}
{
  value: 'Jack',
  writable: false,
  enumerable: false,
  configurable: false
}
  • 创建对象 

      1。 工厂模式

function createdProse(name,age,tall) {
    let obj = new Object();
    obj.name = name;
    obj.age = age;
    obj.tall = tall;
    obj.getName = function () {
        return age.name
    }
    return obj
}

let a = createdProse("jack", 23, 178);
console.log(a)

let b = createdProse('Alice',32,173);
console.log(b)

{ name: 'jack', age: 23, tall: 178, getName: [Function] }
{ name: 'Alice', age: 32, tall: 173, getName: [Function] }

工厂模式能接受参数创建多个所需要的对象,但问题在于无法确定对象类型

function createdProse(name,age,tall) {
    let obj = new Object();
    obj.name = name;
    obj.age = age;
    obj.tall = tall;
    obj.getName = function () {
        return age.name
    }
    return obj
}

let a = createdProse("jack", 23, 178);

console.log(typeof a === 'object')

true

   2 构造函数模式

function Person(name,age,city) {
    this.name = name;
    this.age = age;
    this.city = city;
    this.getName = function () {
        return this.name
    }
}

let a = new  Person("Jack",28,"成都");
console.log(a)
let b = new Person("Alice",24,"兰州");
console.log(b)

Person { name: 'Jack', age: 28, city: '成都', getName: [Function] }
Person { name: 'Alice', age: 24, city: '兰州', getName: [Function] }

与工厂模式相比有以下几点不同

  • Person()取代了createdProse(). 而且Person()函数的首字母使用了大写。这个主要是为了和普通函数区别开来。 说到底构造函数毕竟也是函数。只是用来创建对象的。
  • 没有显示的创建对象
  • 直接将属性和方法赋值给了this对象
  • 没有return语句

要创建Person的实例对象就必须使用new关键字。这个调用方式会经历一下几个步骤

  • 创建了一个新的对象
  • 将构造函数的作用域赋给新的对象所以this指向了这个对象
  • 执行构造函数的代码将属性和方法添加给新的对象
  • 返回新的对象

a,b两个对象分别保存着不同的实例对象。 这两个对象都有一个constructor属性。 而且该属性指向Person

function Person(name,age,city) {
    this.name = name;
    this.age = age;
    this.city = city;
    this.getName = function () {
        return this.name
    }
}

let a = new  Person("Jack",28,"成都");
let b = new Person("Alice",24,"兰州");
console.log(a.constructor === Person)
console.log(b.constructor === Person)


true
true
instanceof可以更可靠的检查类型。我们创建的对象不但是Objcet的实例还是Person的实例
function Person(name,age,city) {
    this.name = name;
    this.age = age;
    this.city = city;
    this.getName = function () {
        return this.name
    }
}

let a = new  Person("Jack",28,"成都");
let b = new Person("Alice",24,"兰州");
console.log(a instanceof Object)
console.log(a instanceof Person)

true
true
  • 将构造函数当作函数

任何函数如果能通过new关键字调用就可以将其当作构造函数。 如果不能通过new关键字调用就和普通函数没什么区别

function Person(name,age,city) {
    this.name = name;
    this.age = age;
    this.city = city;
    this.getName = function () {
        return this.name
    }
}

let a = new  Person("Jack",28,"成都");
a.getName()

Person("Jack",28,"成都");
window.getName()

let c = new Object()
Person.call(c,...["Jack",28,"成都"])
c.getName()

Jack
Jack
Jack

上述第一种调用为典型的构造函数调用。 第二种调用其实是将属性和方法添加在了window上。 此时this实际上指向的是window对象,第三章通过claa方法使其在某个特殊的作用域中调用Person().故此时c也就拥有了对象所有的属性和方法。

  • 构造函数的问题

使用构造函数的主要问题在每个方法都会在实例化的基础上重新创建一次。 上面的a,b对象都包含一个名为getName的方法。 但是这两个方法不在同一个实例中。 在js中函数也是对象。也就是说每定义一个方法就相当于创建了一个对象。

function Person(name,age,city) {
    this.name = name;
    this.age = age;
    this.city = city;
    this.getName = {
        name : this.name
    }
}

let a = new  Person("Jack",28,"成都");
console.log(a.getName.name)

Jack

所以不同实例上的同名函数是不想等的

function Person(name,age,city) {
    this.name = name;
    this.age = age;
    this.city = city;
    this.getName = function () {
        return this.name
    }
}

let a = new  Person("Jack",28,"成都");
let b = new Person("Alice",24,"兰州");
console.log(a.getName === b.getName)

false

为了解决这一问题出现了如下写法

function Person(name,age,city) {
    this.name = name;
    this.age = age;
    this.city = city;
    this.getName = getName
}

getName = function(){
    return this.name
}
let a = new  Person("Jack",28,"成都");
let b = new Person("Alice",24,"兰州");
console.log(a.getName === b.getName)

true


这样写解决了定义相同函数解决同一问题的的做法。 但是新的问题有随之产生了。就是这个全局方法只能通过某个对象来调用。这就显得又些奇怪了。 而且在存在很多方法的情况下显然不是最好的选择。

发布了26 篇原创文章 · 获赞 79 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_25502269/article/details/105506522