一.自定义构造函数

创建对象的方法,有两种
    字面量  const obj = {} 
    构造函数  const obj = new Object()
    这个构造函数,就是JavaScript程序定义好的构造函数,我们直接使用就可以了
所谓的构造函数,实际也是一种函数

构造函数专门用于生成,定义对象的

通过构造函数,生成的对象,称为实例化对象

强调: 构造函数,就是一种函数,是专门用于生成对象的函数
        实例化对象,就是通过构造函数,生成的对象,称为实例化对象

构造函数分为两种,一种是JavaScript程序定义好的构造函数,称为 内置构造函数
                一种是程序员自己定义的构造函数,称为 自定义构造函数

构造函数和普通函数的区别
    1,构造函数一定要和 关键词 new 一起使用
        new 关键词具有特殊的功能,
        会自动的给 构造函数中 定义一个对象,并且返回这个对象
        我们只要对这个对象设定属性,设定方法就可以了
    2,构造函数为了和其他函数区别
        语法规范规定,构造函数,函数名称,第一个字母必须大写,使用大驼峰命名法
    3,构造函数,给对象定义属性和方法的语法,与一般函数不同

实例化对象和普通的对象,没有任何的区别,只是建立方式不同而已

自定义构造函数
注意:1,函数名称要使用大驼峰命名法
        2,自定义构造函数中,不要定义对象,也不要定义return
        new 关键词会执行,如果定义了,会产生冲突
    

function CrtObj(name,age,sex,addr){
    // 在构造函数中,使用this,来指代对象
    // 这个对象,就是我们使用构造函数,生成的实例化对象
    
    // 定义属性
    // 给实例化对象,添加name属性,属性值是输入的name参数
    this.name = name;
    this.age = age;
    this.sex = sex;
    this.addr = addr;

    // 定义方法
    this.funAll = function(){
        console.log(this.name,this.age,this.sex,this.addr )
    }
    this.funNameAge = function(){
        console.log(this.name,this.age)
    }
    this.funSexAddr = function(){
        console.log(this.sex,this.addr )
    }

}

// 通过自定义构造函数来生成对象,实例化对象
// 调用执行构造函数时,都必须要和new 关键词一起使用
const obj1 = new CrtObj('张三',18,'男','北京');
console.log(obj1);
// 调用 对象/实例化对象 中的方法
obj1.funAll();
obj1.funNameAge();
obj1.funSexAddr();

总结
new 的作用
1,在构造函数中,自行(内部自动就会)创建一个对象,并且返回这个对象
2,因为new 关键词,创建了对象,此时,构造函数中的this,才会指向这个对象
    也就是将来生成的实例化对象
3,所有的构造函数中,this的指向,都是将来通过这个构造函数生成的实例化对象

二.解决构造函数中的一些问题

// 自定义构造函数
function CrtObj(name, age, sex, addr) {
    // 定义属性
    this.name = name;
    this.age = age;
    this.sex = sex;
    this.addr = addr;
    // 定义方法
    this.funAll = function () {
        console.log(this.name, this.age, this.sex, this.addr);
    }
}

// 定义了构造函数,可以生成实例化对象
const obj1 = new CrtObj('张三',18,'男','北京');
const obj2 = new CrtObj('李四',19,'女','上海');
const obj3 = new CrtObj('王五',20,'不详','火星');

// 每个实例化对象中,都有属性和方法

// console.log(obj1);
// console.log(obj2);
// console.log(obj3);

通过同一个构造函数,生成的实例化对象
属性相同,属性值可能不同
定义的方法的程序,是相同的
但是,如果做一个比较判断,结果是 false 

表示,不同的实例化对象中,定义的是不同的方法/函数
不同的方法和函数,会占用过多的内存空间
要想办法,让使用同一个构造函数,生成的实例化对象,都是相同的方法

原因:每次创建对象,都会在对象上定义一个新的方法,也就是新的函数
        函数存储时会生成一个独立的存储空间,不同函数有不同的存储空间
console.log( obj1.funAll == obj2.funAll );   此时结果为  →   false

解决的方式
将构造函数需要定义给实例化对象的方法,定义在函数的 prototype 属性中

function CrtObj2(name, age, sex, addr) {
    // 定义属性
    this.name = name;
    this.age = age;
    this.sex = sex;
    this.addr = addr;
}
// 在构造函数的 prototype 属性中,来定义实例化对象的方法
CrtObj2.prototype.fun = function(){
    console.log(this.name, this.age, this.sex, this.addr);
}

const obj4 = new CrtObj2('张三',18,'男','北京');
const obj5 = new CrtObj2('李四',19,'女','上海');

// console.log(obj4);
// console.log(obj5);

// obj4.fun();

// console.log( obj4.fun === obj5.fun );    此时结果为  →  true


详细解释一下 prototype
到底 prototype 是什么?
prototype 是每个函数本身就具有的一个特殊的属性
可以在这个特殊的属性中,存储 数据和函数

function fun(){}

// 向 fun 函数 中的 prototype 中,定义属性 name 属性值 张三
fun.prototype.name = '张三';

fun.prototype.f = function(){console.log(123)};

console.dir( fun );// 查看fun

console.log( fun.prototype.name );  //  →  张三
fun.prototype.f();  //  →  123

// 在构造函数中, prototype 的所用和使用方法

// 在构造函数内部,定义实例化对象的属性
// 通过 this关键词,指向实例化对象
function CreFun(name,age){
    this.name = name;
    this.age = age;
}
// 如果是构造函数,创建实例化对象
// 给实例化对象,添加属性,是通过this方法来添加定义的
// 定义在 prototype 中的属性,就是写在 prototype 中的,不会写在实例化对象中
CreFun.prototype.name2 = '李四';
CreFun.prototype.age2 = 180;

// 在 prototype 中定义的方法/函数,也不会定义在实例化对象上
// 只会写在 prototype 中
CreFun.prototype.ff = function(){
    console.log('1111');
}



JavaScript中,每一个对象,都有一个 __proto__
实例化对象的 __proto__  就是指向的 构造函数中, prototype 这个属性
通过构造函数,生成的实例化对象,这个实例化对象, __proto__ 存储的地址
就是生成这个实例化对象的构造函数的 prototype 的地址
实例化对象的 __proto__ 实际上 就是指向 构造函数的 prototype

在对象中,调用数据,先在对象本身上找,有没有这个属性
obj6.name 企图调用 obj6中,name属性
先在 obj6 对象本身的属性上找 , 如果有就调用本身属性上,对应的数据

如果调用的属性,对象本身没有这个属性,会自动去 __proto__ 中寻找
如果有,就使用 __proto__ 当中的数据
obj6.name2 企图调用 obj6中,name2属性
实际上,obj6中,本身没有 name2属性
去 __proto__ 中 寻找,没有 name2 
如果有就会使用 name2 的数据

const obj6 = new CreFun('张三',18);
console.dir( CreFun )
console.dir(obj6);
console.log(obj6.name);
console.log(obj6.name2);

总结
    函数有一个 属性 叫 prototype 
        其中可以定义,存储, 属性属性值 函数名称函数等等数据
        定义在 prototype 中的内容 就是 函数自己本身的数据

    对象有一个 属性 叫 __proto__
    构造函数,在生成实例化对象时,会将自己 prototype 这个空间的地址
    赋值给 实例化对象 的 __proto__ 来存储
    实际上 构造函数的 prototype 和 生成的实例化对象的 __proto__ 指向的是同一个 存储空间
    可以相互调用数据


    构造函数 
    function Fun(){}
    只定义在函数 Fun 中的数据
    Fun.prototype.name = 'abc';
    Fun.prototype.f = function(){};

    // 通过 构造函数,生成实例化对象
    const obj = new Fun();
    构造函数 Fun 的 prototype 的 空间地址 就赋值给了
    实例化对象 obj 的 __proto__ 
    obj 的 __proto__  和 Fun 的 prototype 指向的是同一个空间地址

    
    通过构造函数,定义给实例化对象的属性,必须使用this,在构造函数中操作
    function Fun(){
        通过this方法才是定义给实例化对象的属性和方法
        this.name = '张三'
        this.age = 18
    }
    只是定义在 构造函数 Fun 中的数据,跟实例化对象没有半毛钱关系
    Fun.prototype.name = 'abc';
    Fun.prototype.f = function(){};

    当调用 实例化对象 中 f函数时 obj.f()
    实例化对象本身中,是没有这个函数的
    会继续在 __proto__ 中寻找,是否有这个方法函数 f
    __proto__ 实际上指向的 就是 构造函数的 prototype
    构造函数的 prototype 中,是有这个方法的,那么就可以正常调用

    所有通过这个构造函数生成的实例化对象,实际上,本身都没有定义方法和函数
    使用时,都是调用生成实例化对象的构造函数,本身prototype中定义的方法
    就不会重复生成函数,占用内存空间