JavaScript进阶讲解八—>面向对象二(原型)

74 阅读3分钟

一、使用new操作符时,是怎么执行的?

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

二、对象的原型

JavaScript当中每个对象都有一个特殊的内置属性 [[prototype]],这个特殊的对象可以指向另外一个对象。

var obj = {}
console.log(obj);
console.log(obj.__proto__);
console.log(Object.getPrototypeOf(obj));

上面这段代码,大家可以在浏览器与node环境分别执行下,他们打印出的效果是不一样的,这是因为浏览器,帮我们做了其他处理。但我们不管如何确实是可以通过__proto__与Object.getPrototypeOf这两种方式获取到的。而对象的原型,我们称为隐式原型,即通过obj.__proto__获取到的原型,就是隐式原型。

这个特殊的对象,可以做什么呢?

  1. 当我们通过引用对象的属性key来获取一个value时,它会触发 [[Get]]的操作。
  2. 这个操作会首先检查该属性是否有对应的属性,如果有的话就使用它。
  3. 如果对象中没有改属性,那么会访问对象[[prototype]]内置属性指向的对象上的属性。
var obj = {}
obj.__proto__.name = 'xt'
console.log(obj.name); // 'xt'

二、函数的原型

所有的函数都有一个prototype的属性,大家注意看我这里的prototype,可没加[[prototype]]这个符号,对象中可没有prototype这个属性,大家千万别搞混了,上面已经说过,对象的原型我们是通过__proto__与Object.getPrototypeOf这两种方式获取,可没有obj.prototype去获取哟。

var obj = {}
console.log(obj.prototype); // undefined  对象没有prototype这个属性

function foo() {
    
}
console.log(foo.prototype) // node {} 浏览器{constructor: ƒ},这是因为node中他的可枚举属性是false ,自行通过Object.getOwnPropertyDescriptors(Foo.prototype)方法查看

对于函数的prototype属性,我们称为显示原型,这是函数特有的。

function Foo() {
    
}

var f1 = new Foo()
var f2 = new Foo()


console.log(f1.__proto__ === f2.__proto__) // true
console.log(f1.__proto__ === Foo.prototype) // true

现在再看我们最上面的new执行的第2步,现在大家应该就能明白其中的含义了。

function Foo() {
    
}

var f1 = new Foo()
var f2 = new Foo()


f1.__proto__.name = 'xt'
// Foo.prototype.name = 'xt'

console.log(f1.name) // xt
console.log(f2.name) // xt

我们在看这个,为什么我们在f1.__proto__上面添加了name 属性,而f2.name也是可以打印的呢? 这就证明了f1.proto 与 Foo.prototype与f2.__proto__他们的内存地址是相等的。他们找的规则就是现在自己里面找,找不到在沿着原型链去找。

function Foo() {
    
}

Object.defineProperty(Foo.prototype, "constructor", {
    enumerable: true,
    configurable: true
})


// prototype.constructor 指向构造函数本身
console.log(Foo.prototype.constructor); // [Function: Foo]

函数的prototype都用是有constructor属性的,用node执行时看不见的原因是enumerable默认为fasle,向这里我们改成了true,就可以看见了。

function Foo() {
    
}

Object.defineProperty(Foo.prototype, "constructor", {
    enumerable: true,
    configurable: true
})


// prototype.constructor 指向构造函数本身
console.log(Foo.prototype.constructor); // Foo
console.log(Foo.prototype.constructor.prototype.constructor); // Foo

在这里插入图片描述 上面这个的意思就是说函数中有prototype属性,也就是函数的原型对象,而这个原型中是有个constructor属性的,并且他的constructor属性的值是函数本身。

三、优化通过构造函数创建对象

上一节中,我们提过几种创建对象的方式,其中有一种是通过构造函数创建

function CreateObj(name, age) {
    this.name = name
    this.age = age

    this.getName = function() {
        console.log(this.name);
    }
}

var obj1 = new CreateObj('xt', 18)
var obj2 = new CreateObj('tx', 20)
console.log(obj1);
console.log(obj2);

这一种是有弊端的,比如getName 这个函数,在每次new CreateObj时他都会被创建,我们如果不想他每次被创建,就可以通过我们函数的原型来解决,如下:

function CreateObj(name, age) {
    this.name = name
    this.age = age
}

CreateObj.prototype.getName = function() {
    console.log(this.name);
}

var obj1 = new CreateObj('xt', 18)
obj1.getName() // xt


var obj2 = new CreateObj('tx', 20)
obj2.getName() // tx