本人学习过程中的理解,如有误请在评论区指正
面向对象是现实的抽象方式
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 () {},
};
// 这样我们就会更好的描绘一个事物
像这样把这些放入到一个对象中,形成一个整体的,描述这种对显示生活的抽象,这种编程的方式就叫做 面向对象 ;

JavaScript 面向对象
- JavaScript其实是支持多种编程范式的,包括 函数式编程 和 面向对象编程:
- JavaScript中的对象被设计成一组 属性的无序集合 ,像是一个 哈希表,有 key 和 value 组成;
- key 是一个标识符名称,value可以是任意类型,也可以是其他对象或者函数类型;
- 如果值是一个函数,那么我们可以称之为是对象的方法;(函数和方法本质上是一个东西)
 
如何创建一个对象
- 通过 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 + '在跑步');
};
- 通过字面量形式创建(流行)
这样创建会使对象和属性看起来是一个整体,也更加简洁,可读性更强;
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", 属性描述符)
- 它可以接收三个参数:
- 要修改的对象名;
- 要定义或修改的属性的名称或 symbol;
- 要定义或修改的属性描述符;
 
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
})
存取属性描述符
- 应用场景:
- 隐藏某一个私有属性
- 如果我们希望截获某个属性它访问和设置值的过程时,也会使用存储属性描述符
 
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操作符调用了
- 在内存中创建一个新的对象(空对象)
- 这个对象内部的[[prototype]]属性会被赋值为该构造函数的prototype属性;
- 构造函数内部的this,会指向创建出来的新对象;
- 执行函数的内部代码(函数体代码);
- 如果构造函数没有返回非空对象,则返回创建出来的新对象;
==通过构造函数批量创建对象==
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方法
 
创建对象的内存表现



原型有什么作用?
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() {}; // 编写类的过程称之为是一个封装的过程;
 
- 
继承:继承是面向对象中非常重要的,不仅仅可以减少重发代码的数量,也是多态前提(纯面向对象中); - 重复利用一些代码(对代码的复用);
- 继承是多态的前提
 
- 多态:不同的对象在执行时表现出不同的形态; 
 
- 
原型链
我们知道,从一个对象上获取属性,如果在当前对象中没有获取到就会去它的原型上获取;
因为原型对象也是一个对象,内也有原型(proto),所以就可以一直查找一直查找,对象和对象之间形成了一个链条就叫做原型链

原型链的尽头-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));
