上篇我们说到,当我们需要自己做一些事情的时候,我们自己基于构造函数,创建的类就是自定义类;

这篇我们就来说说什么是构造函数。

思维导图

40.构造函数创建自定义类 - 图1

一、构造函数语法

构造函数,字面上的意思理解:

  • “构造”:就是通过某种手段或者方法,创造出来(重构出来);
  • “函数”:就是我们之前学过的函数

这么一想,构造函数 的意思就是通过某种手段或者方法,创造出来(重构出来)一个函数

那么怎么创建呢?

首先我们来看一个👇普通函数

  1. function Func(name, age) {
  2. this.name = name;
  3. this.age = age;
  4. }
  5. let f1 = Func('金色小芝麻', 18);//=> 把Func函数执行(当做普通函数执行)
  6. //=>方法中的THIS:window
  7. console.log(f1); //=>undefined 因为没有返回值
  8. console.log(window.name, window.age); //=>'金色小芝麻' 18
  9. 复制代码

这是我们的普通函数执行,在上面的代码的基础上我们加个new,一切就都变了

function Func(name, age) {
    this.name = name; 
    this.age = age;
}
let f1 = new Func('金色小芝麻', 18);
console.log(f1); //=> {name: "金色小芝麻", age: 18}
console.log(window.name, window.age); //=> window.name是空  window.age是undefined
复制代码

此时我们很明显发现不一样了,由window.name不在是‘金色小芝麻’, window.age不在是‘18’,我们可以断定,函数体中的this绝不在是window了,而且在没有return的情况下f1不在是undefined了;

这种在函数执行前加一个new的方式,就是我们的构造函数执行;

  • 在ES3语法中:

    • new 函数() => 这种方式就是基于构造函数的方式来执行
    • 约定的语法规范:类名的第一个字母一般都是大写的

就像是这样👇:

function Func(name, age) {
    this.name = name; 
    this.age = age;
}
let f1 = new Func('金色小芝麻', 18);
复制代码

此时:

    1. Func不在被誉为普通函数,而是叫做构造函数(也就是我们所谓的自定义类)
    2. 返回的结果也不再基于RETURN来判断返回值,返回的结果是当前类的一个实例

想创建自定义类和创建类的实例,只需要在执行的时候 不在 “函数()“ 普通函数执行; 而是 “new 函数()“ 执行,也就是构造函数执行,这样方法被称为类,返回结果被称为类的实例;

构造函数语法与普通函数语法的区别

function Fn() {
    this.x = 100;
    this.y = 200;
}
let f1 = new Fn();
console.log(f1);
let f2 = new Fn;
console.log(f2); 
复制代码
  • 普通函数

    • Fn 是函数本身(不是执行)
    • Fn() 函数执行
  • 构造函数

    • new Fn(); 构造函数执行 =>有参数new
    • new Fn; 构造函数执行(这种方式不能给函数传递实参了) =>无参数new

二、构造函数执行与普通函数执行的区别

还是以下👇题为例:

function Func(name, age) {
    this.name = name; 
    this.age = age;
}
let f = Func('小芝麻', 18);
let f1 = new Func('小芝麻', 18);
复制代码

为了方便理解,直接看图

40.构造函数创建自定义类 - 图2

1、普通函数执行时

还是以上体为例:这里我直接省略全局下的代码操作过程,直接看函数执行是的私有作用域中的操作过程;

  • 第一步:初始化作用域链:<EC(FUNC),EC(G)>

  • 第二步:初始化THIS:window

  • 第三步:形参赋值:name = '小芝麻'age = 18

  • ……省略不重要的步骤。

  • 第四步:变量提升:无

  • 第五步:代码执行:

    • this.name = name; //=> window.name = ‘小芝麻’
    • this.age = age;//=> window.age= 18

没有返回值,执行完成出栈销毁:f = undefined

2、构造函数执行时

构造函数拥有普通函数执行的特征,也有自己单独的一些操作

  • 第一步:初始化作用域链:<EC(FUNC),EC(G)>
  • 第二步:形参赋值:name = '小芝麻'age = 18
  • 第三步:变量提升:无

以上三步说明了构造函数具备普通函数的一面;

构造函数执行时浏览器多做的一些事情👇:

  • 第四步:在当前上下文中,创建一个对象(这个对象就是当前类的实例)=> AAAFFF111(我们假设的空间地址)

  • 第五步:让当前上下文中的THIS指向新创建的实例对象:this :AAAFFF111

  • 第六步:代码执行

    • this.name = name; //=> 把私有变量的name值,赋给新创建实例对象的私有属性
    • this.age = age;//=> 把私有变量的age值,赋给新创建实例对象的私有属性
  • 第七步:代码执行完,如果我们没有设置RETURN,浏览器默认会把新创建的实例对象返回

相信看到这里你已经明白其中的道理了,我们在简单总结下:

function Func(name, age) {
    /*
     * 代码执行之前,创建一个实例对象(堆)
     * 让THIS指向实例对象
     */
    this.name = name; //=>this.xxx=xxx 都是在给实例对象设置私有的属性和方法
    this.age = age;
    /*
     * 如果函数没有return,默认会把创建的实例对象返回 
     */
}
let f1 = new Func('小芝麻', 18);
let f2 = new Func('金色', 10);
console.log(f1);//=> {name: "小芝麻", age: 18}
console.log(f2);//=> {name: "金色", age: 10}
console.log(f1 === f2); //=>FALSE 每次都是创建一个新的实例,每一个实例和其他实例都是一个单独的对象(个体),互相之间不冲突
复制代码

总结如下:

3、构造函数执行时:实例的相关问题

  • 1、构造函数执行,由于具备普通函数特征,所以在私有上下文中可能会出现一些私有变量,但是这些私有变量和实例没有必然的联系,私有上下文中的THIS才是实例,所以只有写THIS.XXX=XXX的操作,才是给实例设置私有属性;

    • 实例的私有属性和上下文中的私有变量不是一个东西
  • 2、当前类的每一个实例都是单独的一个对象,实例和实例之间是独立的

  • 3、在构造函数的函数体中,基于 THIS.XXX=XXX 给实例设置的属性和方法都是自己私有的,和其它实例中的属性和方法不冲突

4、构造函数执行时:return的相关问题

function Fn() {
    this.x = 100;
    this.y = 200;
    return 1;
}
let f1 = new Fn;
console.log(f1); //=>{x: 100, y: 200} 仍然返回当前实例
function Fn() {
    this.x = 100;
    this.y = 200;
    return {
        name: 'xxx'
    };
}
let f1 = new Fn;
console.log(f1); //=>{name:'xxx'} 不再是Fn类的实例
复制代码

总结:

  • 1、没有return,默认返回当前类的实例对象(对象数据类型)
  • 2、有return,并且返回基本类型值,最后的返回结果还是类的实例,不会有影响
  • 3、如果返回的是一个引用数据类型,则会把默认返回的实例给覆盖掉,这样我们接收结果就不在是当前类的实例了,而是自己返回的值 => 真实项目中,如果想创建类的实例,则建议大家不要在手动写return了,防止实例被覆盖

三、一道例题

function Fn(x) {
    let y = 20;
    this.total = x + y;
    this.say = function () {
        console.log(`${x} + ${y} = ${this.total}`);
    };
}
let f1 = Fn(10); //=>f1=undefined
let f2 = new Fn(10); //=>f2实例对象
let f3 = new Fn(20); //=>f3实例对象
console.log(f2.total); //=>30
console.log(f2.x); //=>undefined
console.log(f3.y); //=>undefined
console.log(f1 === f2); //=>FALSE
console.log(f2 === f3); //=>FALSE
f2.say(); //=>"10+20=30"
console.log(f2.say === f3.say); //=>FALSE  都是当前实例的私有属性和方法(所有出现在构造函数的函数体中的 this.xxx=xxx 操作,都是设置私有的属性和方法)
console.log(f1.total); //=>Uncaught TypeError: Cannot read property 'total' of undefined  f1不是对象,只有对象才能操作键值对
console.log(window.total); //=>30  忽略上面的报错后输出结果
window.say(); //=>"10+20=30"  忽略上面的报错
复制代码

40.构造函数创建自定义类 - 图3

四、检测实例的几种方法

1、instanceof:检测当前实例是否属于这个类

instanceof: 检测当前实例是否属于这个类(或者检测当前值是否为某个类的实例)

  • 语法: 值 instanceof
  • 返回值: 是它的实例返回TRUE,反之返回FALSE
    (当前类的原型只要出现在了实例的原型链上就返回true,原型链概念我们下一篇讲解)
function Fn() {}
let f1 = new Fn;
console.log(f1 instanceof Fn); //=>TRUE
console.log(f1 instanceof Array); //=>FALSE
复制代码
  • 局限:instanceof不适用于基本数据类型检测,要求检测的实例必须是对象数据类型的
console.log(100 instanceof Number); //=>FALSE
复制代码
  • 应用场景:区分对象中的特殊数据格式,例如数组或者正则
let arr = [10, 20];
let reg = /^$/;
// console.log(typeof arr); //=>"object"  typeof不能具体检测对象类型的细分
// console.log(typeof reg); //=>"object"
console.log(arr instanceof Array); //=>TRUE
console.log(arr instanceof RegExp); //=>FALSE
复制代码

2、hasOwnProperty:检测当前属性是否为实例私有属性

hasOwnProperty:检测当前的某一个属性是否为实例(或者对象)的私有属性

  • 语法:对象.hasOwnProperty(属性)
  • 返回值:是私有的属性返回TRUE,如果不是对象的属性或者不是私有的属性都返回FALSE

3、in检测当前属性是否为对象的属性

in:检测当前属性是否为对象的属性

  • 语法:属性 in 对象
  • 返回值:只要是对象的属性(不管是公有还是私有的属性)结果都是TRUE
function Fn() {
    // 构造函数体中出现的 this.xxx = xxx 都是给当前实例设置的私有属性
    this.x = 100;
    this.y = 200;
    this.say = function () {
        console.log(x + y);
    };
}
let f1 = new Fn;
let f2 = new Fn;
console.log(f1.say === f2.say); //=>false
console.log(f1.hasOwnProperty('say')); //=>true
console.log(f1.hasOwnProperty('name')); //=>false 因为你连这个属性都没有
console.log(f1.toString()); //=>"[object Object]"  toString一定是f1对象的属性,否则f1也不可能调用这个方法
console.log(f1.hasOwnProperty('toString')); //=>false  toString不是它的私有属性,是他的公有属性
console.log('say' in f1); //=>true
console.log('toString' in f1); //=>true
console.log('name' in f1); //=>false
复制代码

利用上面两个方法我们可以自己写一个检测某一个属性是否为当前对象的公共属性的方法

需求:检测某一个属性是否为当前对象的公共属性(是他的属性,但是还不是私有的)

function Fn() {
        // 构造函数体中出现的 this.xxx = xxx 都是给当前实例设置的私有属性
        this.x = 100;
        this.y = 200;
        this.say = function () {
            console.log(x + y);
        };
    }
    let f1 = new Fn;
    let f2 = new Fn;
// 检测ATTR是否为OBJ的公有属性
function myHasPublicProperty(attr, obj) {
    // 1.需要是它的属性 =>IN检测为TRUE
    // 2.不是它的私有属性 =>HASOWNPROPERTY检测为FALSE
    // return ((attr in obj) === true) && (obj.hasOwnProperty(attr) === false);或者
    return (attr in obj) && !obj.hasOwnProperty(attr);
}
console.log(myHasPublicProperty('say', f1)); //=>false
console.log(myHasPublicProperty('name', f1)); //=>false
console.log(myHasPublicProperty('toString', f1)); //=>true