JavaScript深入面向对象


本人学习过程中的理解,如有误请在评论区指正


面向对象是现实的抽象方式

new一个对象 new GridFriend();
new一堆金矿(doge) new 金矿()

  • 对象是 JavaScript 中一个非常重要的概念,这是因为对象可以 将多个相关联的数据封装到一起,更好的描述一个事物:
    • 描述一个人:Person,姓名(name)、年龄(age)、身高(height)、行为(eat,run) 等
  • 对象 的方式来描绘事务,更有利于我们将 现实的事物,抽离成代码中 某个数据结构;
var personName = 'xuan';
var personAge = 18;
var personHeight = '180cm';
var gender = '男';
function personEat() {}

当我们描述一个东西,这样描述并不能看成是一个整体,所以我们会创建一个对象

var person = {
    name: 'xuan',
    age: 18,
    height: '180cm',
    gender: '男',
    eat: function () {},
    run: function () {},
};
// 这样我们就会更好的描绘一个事物

像这样把这些放入到一个对象中,形成一个整体的,描述这种对显示生活的抽象,这种编程的方式就叫做 面向对象 ;

034BDNN0(~SLBPUVA`9`5{1.png


JavaScript 面向对象

  • JavaScript其实是支持多种编程范式的,包括 函数式编程面向对象编程
    • JavaScript中的对象被设计成一组 属性的无序集合 ,像是一个 哈希表,有 key 和 value 组成;
    • key 是一个标识符名称,value可以是任意类型,也可以是其他对象或者函数类型;
    • 如果值是一个函数,那么我们可以称之为是对象的方法;(函数和方法本质上是一个东西)

如何创建一个对象

  1. 通过 new Object() 来创建 (以前)

因为早期很多JavaScript开发者都是从Java过来的,他们也更习惯于Java中通过new的方式创建一个对象;

var person = new Object();
person.name = 'xuan';
person.age = 18;
person.height = 175;
person.running = function () {
    console.log(this.name + '在跑步');
};
  1. 通过字面量形式创建(流行)

这样创建会使对象和属性看起来是一个整体,也更加简洁,可读性更强;

var info = {
    name: 'qiang',
    age: 18,
    height: 172,
    eating: function () {
        console.log(this.name + '在吃东西');
    },
};

对对象属性的操作

  • 获取属性:console.log(person.name);
  • 给属性赋值:person.name = 'yu';
  • 删除属性:delete person.name;
  • 遍历属性:for(var k in obj) {};

对属性的操作控制

  • 如果我们需要对这个对象内部的属性做一些限制;比如这个属性是否可以通过delete删除,这个属性是否在for-in的时候被遍历出来
  • 如果我们要对 一个属性进行比较精准的控制,那么我们就可以使用 属性描述符
    • 通过属性描述符 可以精准的添加或修改对象的属性
    • 属性描述符需要使用 Object.defineProperty 来对属性进行添加或修改;

Object.defineProperty

Object.defineProperty(person, "name", 属性描述符)
  • 它可以接收三个参数:
    1. 要修改的对象名;
    2. 要定义或修改的属性的名称或 symbol;
    3. 要定义或修改的属性描述符;
var obj = {
    name: 'xuan',
    age: 18,
};

// 属性描述符是一个对象
Object.defineProperty(obj, 'height', {
    // 配置
    value: 180,
});

console.log(obj);
console.log(obj.height);

属性描述符的分类

  • 属性描述符的类型有两种:
    • 数据属性 (Data Properties)描述符(Descriptor);
    • 存取属性 (Accessor访问器 Properties)描述符(Descriptor)
configurable enumerable value writable get set
数据描述符 可以 可以 可以 可以 不可以 不可以
存取描述符 可以 可以 不可以 不可以 可以 可以
数据属性描述符
  • configurable: 表示属性是否可以通过delete删除属性,或者是否可以修改该配置为true或false;
    • 当我们直接在一个对象上定义某个属性时,这个属性缺省为true;
    • 当我们通过属性描述符定义一个属性时,这个属性缺省为false;
  • enumerable: 是否可以通过遍历或者log返回该数据,是否是可枚举的;
    • true

    • false

  • writable: 是否可以赋值
    • true
    • false
  • value:
    • 缺省是undefined
var obj = {
    name: 'xuna',
    age: 18,
};

// 数据属性描述符
Object.defineProperty(obj, 'address', {
    // 配置
    value: '北京市',
    // 该属性不可删除/不可重新定义属性描述符
    configurable: false, // 缺省false
    // 是否是可以枚举的,是否是可以被遍历出数据的,能否拿到这个数据
    enumerable: true, // 缺省false
    // 是否可以赋值
    writable: false, // 缺省false
})
存取属性描述符
  • 应用场景:
    1. 隐藏某一个私有属性
    2. 如果我们希望截获某个属性它访问和设置值的过程时,也会使用存储属性描述符

configurable 和 enumerable 和上面两个一致

  • get: 获取属性时会执行的函数,如果我们打印这个属性,则会调用get

    • 缺省是undefined
  • set: 获取属性时会执行的函数,如果我们赋值这个属性,则会调用set,并且把值放到set里面的value里

    • 缺省是undefined
var obj = {
    name: 'xuna',
    age: 18,
    _address: '北京市',
};

Object.defineProperty(obj, 'address', {
    configurable: true,
    enumerable: true,
    // 如果我们打印这个属性,则会调用get
    get: function () {
        return this._address;
    },
    // 如果我们赋值这个属性,则会调用set,并且把值放到set里面的value里
    set: function (value) {
        this._address = value;
    },
});

console.log(obj.address);
obj.address = '上海市';
console.log(obj._address);

数据属性描述符不能和存取属性描述符共存

同时定义多个属性

  • Object.defineProperties() 方法直接在一个对象上定义 多个 新的属性或修改现有属性,并且返回该对象
var obj = {
    // 下划线开头的是开发者们约定俗成的私有属性
    // js中没有严格意义上的私有属性
    _age: 18,
    eating: function () {
        console.log(this.name + '在吃东西');
    },
};

Object.defineProperties(obj, {
    name: {
        configurable: true,
        enumerable: true,
        writable: true,
        value: 'xuan',
    },
    age: {
        configurable: false,
        enumerable: false,
        get: function () {
            return this._age;
        },
        set: function (value) {
            this._age = value;
        },
    },
});

获取属性描述符

console.log(Object.getOwnPropertyDescriptor(obj, 'name'));

获取对象所有的属性描述符

console.log(Object.getOwnPropertyDescriptors(obj));

Object的方法对对象的一些限制

禁止对象添加新的属性

给一个对象添加新的属性会失败(严格模式下会报错)

Object.preventExtensions(obj);

禁止对象配置\删除里面的属性

实际是调用preventExtensions

并且将现有属性的configurable: false

Object.seal(obj);

让属性不可修改(冻结对象)

实际是调用seal

并且将现有属性的writable: false

Object.freeze(obj);

创建对象方案-工厂模式

工厂模式其实是一种常见的 设计模式

通常我们会有一个工厂方法,通过该工厂方法我们可以产生想要的对象;

function createPerson(name, age, height, address) {
    var p = {};
    p.name = name;
    p.age = age;
    p.height = height;
    p.address = address;

    p.eating = function () {
        console.log(this.name + '在吃东西');
    };
    p.running = function () {
        console.log(this.name + '在跑步');
    };
    return p;
}
var p1 = createPerson('张三', 18, 188, '北京市');
var p2 = createPerson('李四', 20, 178, '上海市');
var p3 = createPerson('王五', 22, 190, '广州市');

工厂模式的缺点

获取不到对象的真实类型

ps:上面通过工厂模式创建的雷同对象,真实属性时“Person”,工厂模式并不能获取到

  从某种角度来说,对象应该有一个共同的类型

认识构造函数

  • 构造函数也称之为构造器(==constructor==),通常是我们在创建对象时会调用的函数;

JavaScript中的构造函数

  • 构造函数也是一个普通的函数,从表现形式来说,和千千万万个普通的函数没有任何区别;
  • 那么如果这么一个普通的函数被使用new操作符来调用了,那么这个函数就称之为是一个构造函数

如果被用new操作符调用了

  1. 在内存中创建一个新的对象(空对象)
  2. 这个对象内部的[[prototype]]属性会被赋值为该构造函数的prototype属性;
  3. 构造函数内部的this,会指向创建出来的新对象;
  4. 执行函数的内部代码(函数体代码);
  5. 如果构造函数没有返回非空对象,则返回创建出来的新对象;

==通过构造函数批量创建对象==

function Person(name, age, height) {
    this.name = name;
    this.age = age;
    this.height = height;
}
var p1 = new Person('xuan', 18, 188);
var p2 = new Person('yu', 19, 180);

原型

对象的原型 __ proto _ _

我们每个对象中都有一个[[prototype]], 这个属性可以称之为对象的原型==(隐式原型)==:看不到,不会改,不会用

原型的概念和看一下原型

// 早期的ECMA是没有规范如何去去查看  [[prototype]]

// 给对象中提供了一个属性,可以让我们查看一下这个原型对象(浏览器提供)
// __proto__
// console.log(obj.__proto__);
// console.log(info.__proto__);

// ES之后提供
// console.log(Object.getPrototypeOf(obj));

原型有什么用?

// 当我们从对象获取一个属性时,会触发[[get]]操作
// 1. 在当前对象中查找对应的属性,如果找到就直接使用
// 2. 如果没有找到,那么就会沿着他的原型去查找 [[prototype]]
obj.age = 18;
// obj.__proto__.age = 18;
console.log(obj.age);
console.log(obj);

  • 如果我们想给一个对象的没有的属性赋值,打印对象时就会发现对象中出现了这个属性

    var obj = { name: 'why' };
    obj.age = 18;
    console.log(obj.age);
    console.log(obj)
    
      [图片上传失败...(image-96c63b-1645793151658)]
    
  • 如果我们给prototype赋值,打印这个对象的时候会发现这里面并没有这个属性

  • obj.__proto__.age = 18;
    console.log(obj.age);
    console.log(obj)
    

函数的原型 prototype

  • 当我们创建一个构造函数(Person)时,会同时创建一个Person函数的原型对象
    • 创建的Person构造函数内有一个prototype对象,指向了Person函数的原型对象内有一个constructor方法

创建对象的内存表现

image-20220225101620355.png
image-20220225101632268.png
image-20220225101648480.png

原型有什么作用?

function Person(name, age, height, address) {
    this.name = name;
    this.age = age;
    this.height = height;
    this.address = address;
}
Object.defineProperty(Person.prototype, 'constructor', {
    enumerable: false,
    configurable: true,
    writable: true,
    value: Person,
});
Person.prototype = {
    eating: function () {
        console.log(this.name + '在吃东西');
    },
    running: function () {
        console.log(this.name + '在跑步');
    },
};

var p1 = new Person('xuan', 17, 1.75, '北京市');
var p2 = new Person('yu', 17, 1.75, '上海市');
console.log(p1.eating());
  • 当我们需要批量创建对象时,内部的姓名等雷同信息是不同的,而有些方法内实现的代码是相同的,这样可以把对象的方法放入原型内

constructor属性

  • 默认情况下原型上都会添加一个属性叫做constructor,这个会指向当前的函数对象;

面向对象的特性-继承

  • 面向对象有三大特性:封装、继承、多态

    • 封装:我们前面讲属性和方法封装到一个类(构造函数)中,可以称之为封装的过程;

      • function Person(name, age, height, address) {};
        Person.Protoytpe.running = function() {};
        
        // 编写类的过程称之为是一个封装的过程;
        
    • 继承:继承是面向对象中非常重要的,不仅仅可以减少重发代码的数量,也是多态前提(纯面向对象中);

      1. 重复利用一些代码(对代码的复用);
      2. 继承是多态的前提
    • 多态:不同的对象在执行时表现出不同的形态;

原型链

我们知道,从一个对象上获取属性,如果在当前对象中没有获取到就会去它的原型上获取;

因为原型对象也是一个对象,内也有原型(proto),所以就可以一直查找一直查找,对象和对象之间形成了一个链条就叫做原型链

image-20220225171039613.png

原型链的尽头-Object原型

console.log(obj.__proto__.__proto__);
  • 我们会发现它打印的是 [Object: null prototype] {}
    • 该对象有原型属性,但是它的原型属性已经指向的是null,也就是已经是顶层原型了;
    • 该对象上有很多默认的属性和方法;
var obj = {
    name: 'xuan',
    age: 18,
};
// Object.prototype
console.log(obj.__proto__);
console.log(obj.__proto__ === Object.prototype);

console.log(Object.getOwnPropertyDescriptors(Object.prototype));
image-20220225204226313.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
禁止转载,如需转载请通过简信或评论联系作者。