ES6前没有引入类的概念,使用构造函数与原型模拟类的实现。

创建对象的三种方式

  1. 使用new Object()创建对象

    let obj = new Object();
  2. 利用对象字面量创建对象

    let obj = {};
  3. 利用构造函数创建对象

    function People(name, age) {
        this.name = name;
        this.age = age;
        this.say = function() {
            console.log('永远滴神');
        }
    }
    
    let uzi = new People('乌兹', 18);
    uzi.say();

构造函数

构造函数用来初始化对象,即为对象成员变量赋初始值,总是与new一起使用

new在执行时做的四件事

  1. 在内存中创建一个新的空对象
  2. 让this指向这个新的对象
  3. 执行构造函数中的代码,给这个新的对象添加属性和方法
  4. 返回这个新对象(所有构造函数中不需要return)

静态成员和实例成员

  • 静态成员:在构造函数本身上添加的成员
  • 实例成员:构造函数内部通过this添加的成员,只能通过实例化的对象来访问
function People(name, age) {
    this.name = name;
    this.age = age;
    this.say = function() {
        console.log('永远滴神');
    }
}
// name,age,say为实例成员
let uzi = new People('乌兹', 18);
uzi.say();
// 静态成员,只能通过构造函数访问
People.sex = "男" 
console.log(People.sex);	// 男
console.log(uzi.sex);	// undefined

原型

构造函数原型对象prototype

构造函数存在浪费内存的问题,每new一个对象就会开辟新的内存空间存放参数和方法

使用原型来解决这个问题

JavaScript规定,每个构造函数都有一个prototype属性,指向另一个对象,这个prototype就是一个对象,这个对象的所有属性和方法都会被构造函数所拥有,构造函数通过原型分配的函数是所有对象所共享的

可以把那些不变的方法,直接定义在prototype对象上,这样所有对象的实例就可以共享这些方法

// 一般情况下,公共属性定义到构造函数里,公共方法放到原型对象上
function People(name, age) {
    this.name = name;
    this.age = age;
}
People.prototype.say = function() {
    console.log('永远滴神')
    // 这里的this也指向实例对象
}
let uzi = new People("wuzi", 18);
let theShy = new People("shy", 12);
uzi.say === theShy.say;	// true, 内存位置相同

对象原型__proto__

对象都会有一个属性__proto__指向构造函数的prototype原型对象,之所以对象可以使用构造函数prototype原型对象的属性和方法,就是因为对象有__proto__原型的存在

uzi.__proto__ = People.prototype

原型constructor

对象原型和原型对象中都有一个constructor属性,用于记录对象引用哪个构造函数

很多情况下,手动的利用constructor属性指回原来的构造函数

function People(name, age) {
    this.name = name;
    this.age = age;
}
People.prototype = {
    // 如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数
    constructor: People,
    say: function() {
        console.log('111');
    }
    run: function() {
        console.log('222');
    }
}

原型链

proto.drawio

只要是对象就有__proto__原型,Object原型对象中的__proto__指向null,为最上层

方法查找规则:首先看对象本身是否有say方法,如果有就执行该方法,如果没有,因为存在__proto__,就去构造函数原型对象prototype中查找该方法,通过原型链一层层向上查找。

拓展内置对象方法

Array.prototype.sum = function() {
    let sum = 0
    for(let i = 0; i < this.length; i++) {
        sum+=this[i];
    }
    return sum;
}
let arr = [1,2,3];
console.log(arr.sum());	// 6

继承

ES6前没有提供extends属性提供继承功能,通过构造函数+原型对象模拟继承

// 借用父构造函数继承属性
function Father(name, age) {
    // this 指向父构造函数的对象实例
    this.name = name;
    this.age = age;
}
Father.prototype.money = function() {
    console.log(10000);
}
function Son(name, age, sex) {
    // this 指向子构造函数的对象实例
    Father.call(this, name, age);
    this.sex = sex;
}
// Son.prototype = Father.prototype; 这样指向同一个地址,修改子原型对象后,父原型对象也会被修改
Son.prototype = new Father();
// 利用对象覆盖原有的原型对象后,要将constructor指回原来的构造函数
Son.prototype.constructor = Son;
Son.prototype.exam = new function() {
    console.log("exam");
}
let son = new Son('wuzi', 18, '男');

实现New方法

function myNew() {
    // 创建一个空对象
    const obj = {};
    // 获取第一个参数,即构造方法
    Constructor = Array.prototype.shift.call(arguments);
    // 将空对象的对象原型指向构造方法的原型对象
    obj.__proto__ = Constructor.prototype;
    // 使用apply改变构造函数的指向,并传入参数
    Constructor.apply(obj, arguments);
    // 返回该对象
    return obj;
}

const Tom = myNew(People, 'Tom', 18);

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!

函数与call,apply与bind的使用 上一篇
JS面向对象编程 下一篇