熟悉JavaScript:掌握原型与构造函数

470 阅读7分钟

构造函数

构造函数的定义

构造函数与普通函数其实没什么区别,只不过看有没有被new关键字调用,我们想用new关键字调用这个函数来创建和初始化对象,那么该函数就叫构造函数。下面是一个例子

function Person(name, age) {
    this.name = name;
    this.age = age;
    }
    let person1 = new Person("彭于晏", 42);

js中自带的构造函数

let str = new String()
let s = ''
console.log(str);

v8引擎里面,第一行代码和第二行代码是没有区别的,然后我们看看str的输出结果

27.png

我们可以看到,输出结果用花括号括起来了,说明这是一个对象,而这个对象是用new关键字String里面调用的,然后根据我们之前所说的构造函数的定义可以得知,String是一个js中自带的构造函数,在js中自带的构造函数还有下面这些

//字符串
let str = new String()
let s = ''
console.log(str);


//数字
let num = new Number()
let n = 0

//布尔
let boo = new Boolean()
let b = false

//对象
let obj = new Object()
let o = {}

//数组
let arr = new Array()
let a = []

这些就是官方内置的构造函数,它们的首字母都是大写的,可见构造函数命名格式首字母要大写, 那有人会疑问了,既然这两种方法都可以达到目的为什么js还要都给它们创建好构造函数呢,这就不得不提到js中的另一个概念——原型

原型(protetype)

28.png

展开str对象,我们可以看到str中有一个lenght属性,是的,我们平时也可以通过lenght直接查看字符串的长度,那么问题来了,这个lenght属性是哪里来的,我们并没有创建它呀,然后我们再展开一下Prototype

29.png

发现下面还有一堆方法,这些方法就是字符串可以使用的方法,那我们为什么就可以用这些方法呢,这就不得不先提一下函数所具有的属性

30.png

在这里我们创建了一个函数fn,在函数里面都会具备一个属性叫prototype,翻译一下就叫原型,这是每个函数天生定义出来就具备的属性也叫对象

Person.prototype.say='Hello'
function Person(){
    this.name = '彭于晏'
    this.age = 42
}
let p = new Person()
console.log(p.say);

person中并不具备say这个属性,say这个属性是由我们手动添加到原型(protetype)对象当中的,那我们从由person构造函数创建出来的p对象中调用这个say属性,能成功调用吗

31.png

可以看到我们成功调用出来了,从效果上来说,我们只要往构造函数自带的原型(protetype)上面增加属性值,实例对象都可以访问

所以看之前字符串为什么可以使用那么多方法,我们现在来分析

27.png

字符串对象是由构造函数String创建出来的,然后函数上一定会有原型(protetype)这个属性,函数原型上的属性,实例对象都可以访问到,所以字符串对象上才会有那么多方法可以使用,那就可以明白为什么函数里面都要具备属性原型(protetype)

因为实例对象能访问到函数原型上的属性,所有原型存在的意义就是为了让某一种数据结构拥有更多的方向可用

那我们可以在原型上增加属性的意义是什么呢,官方都定义好了呀,还开放给我干嘛

Car.prototype.name = 'su7'
Car.prototype.height = 1400
Car.prototype.lang = 5000

function Car(color,owner){
    //this.Car = 'su7'
    //this.height = 1400
    //this.lang = 5000 
    this.color = color
    this.owner = owner
}
//柯尼塞格,一款可以定制名字的跑车

let car1 =new Car('red','彭于晏')
let car2 =new Car('green','金城武')

在购买车的时候车是具有一些固定属性的,如果是买一辆就要创建一个对象,那么那些固定的属性也在不断的重复执行,那么为了优化代码,我们可以将固定属性添加到函数的原型(protetype)上去,这样固定属性只需要调用一次就可以一直使用了,这就是开放给我们的意义;可以将一些固定的属性提取到原型上,减少重复的代码执行

隐式原型(__ proto __ )

隐式原型是指每个实例对象都具有的一个内部属性,记为__proto__

然后让我们看一下下面的代码

32.png 我们发现对象的隐式原型(__ proto __ )和构造函数Object原型(protetype)它们里面的内容是一样的,这是为什么呢,让我们看看下面的代码演示

const obj = {
    name:彭于晏
}
obj.name
obj.toString() 

在这段代码中我们创建了一个实例对象并在对象中添加一个名为name的属性,然后我们可以用实例对象去查找这个属性,那我们如果去查找toString的属性呢,在对象中我们并没有去定义这个属性,那么对象就会去隐式原型中查找,那么我们之前说的是实例对象一定可以去访问构造函数上面的原型

然后通过构造函数上的原型(protetype)和对象上的隐式原型(__ proto __ )中的内容是一样的,那我们将它们联系起来,其实就是为了让实例对象可以访问到构造函数上面的原型(protetype),所以给对象也打造了一个内容相同的隐式原型(__ proto __ )隐式原型(__ proto __ )属性是一个指针,指向创建该对象的构造函数的原型(prototype)

那对象上的隐式原型(__ proto __ )是什么时候指向原型(prototype)的呢,其实就是被创建出来的时候,而在v8引擎当中,对象都是被new出来的,那我们来讲讲new对象的过程

new的原理

  1. 创建一个新的空对象:首先,JavaScript 会创建一个新的空对象 {}
  2. 设置原型:这个新对象的内部 [[Prototype]] 属性(在 ES6 之前通常通过 __proto__ 访问,但不建议直接使用)会被设置为构造函数的 prototype 属性。这意味着新对象将能够继承构造函数原型上的属性和方法。
  3. 绑定 this:构造函数内部的 this 会被绑定到新创建的对象上。
  4. 执行构造函数中的代码:构造函数中的代码会执行,可能会为新对象添加属性或方法。
  5. 返回新对象:除非构造函数显式地返回一个对象(使用 return 语句),否则默认情况下,new 表达式会返回新创建的对象。

这里是一个简单的例子来说明这个过程:

function Person(name, age) {
    this.name = name;
    this.age = age;
    // 默认情况下,构造函数没有返回值,或者可以显式地返回 `this` 来返回新对象 
    // return this; // 可选,因为默认就是这样做的 
}
// 使用 new 关键字调用构造函数 
const alice = new Person('彭于晏', 42);
// alice 是一个新对象,它继承了 Person.prototype 上的属性和方法(如果有的话)
console.log(alice.name); // 输出: 彭于晏
console.log(alice.age); // 输出: 42
// 检查 alice 的原型 
console.log(Object.getPrototypeOf(alice) === Person.prototype);
// true

在这个例子中,new Person('彭于晏', 42) 创建了一个新的 Person 对象,并且这个对象有 name 和 age 属性,它们分别被设置为 '彭于晏' 和 42。此外,这个新对象还继承了 Person.prototype 上的任何属性和方法

总结

  • 构造函数用于创建和初始化对象,通过new关键字调用。
  • 原型对象允许对象的实例共享方法和属性,促进了代码复用和内存节省。
  • 隐式原型__proto__使得实例对象能够访问其构造函数的原型对象上的属性和方法。
  • new操作符按照一系列步骤创建新对象、设置原型、绑定this并执行构造函数代码。

最后来分析一张图片,如果你能自己分析出来,恭喜你已经掌握了原型(prototype)这个概念

33.png