引言
你是否见过这么一张让人绝望的原型关系图
说到JavaScript的原型,这一直是一个非常重要,但又让人头疼的问题。别急,本文将带你一步步理解构造函数和原型对象之间讲不清,捋不清的微妙关系。
创建对象的方式
我们先来了解了解JS中的创建对象的各种方法
-
对象字面量
JS中最基础的创建对象的方法,对于一些简单的,少量的对象创建,不用想太多,直接用,好用!但看上去好像又没那么灵活,不适合创建复杂的对象。
// 对象字面量 let zhang = { name: '张三', } let li = { name: '李四', age: 17 } -
Class
在es6中新出现的关键字,可以定义类,让JS更好的与传统的面向对象语言(C++,Java)相结合。它的封装性更好,代码可读性和可维护性更强。
在这里引用《阮一峰 ECMAScript 6》中对class关键字的看法
基本上,ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
// class es6 // 构造函数+原型 class Person { // 每个类只能有一个 constructor 构造函数 // 也就是把下面要提到的构造函数放在了class之中 // 比原型对象更加直观,但会受class的结构限制 constructor(name, age) { this.name = name; this.age = age; } // 原型prototype部分 eat() { console.log('吃饭'); } } // 要使用new let li = new Person('李四', 18) let wang = new Person('王五', 18) -
构造函数
在es5还没有class的时候,我们使用构造函数来创建对象。使用new来实例化。是否是构造函数只需要看是否使用了new。为了区分构造函数与普通函数,我们一般约定把构造函数的首字母大写,但归根结底还是要看new。
// 普通函数 // function add(x, y) { // console.log(this) //全局global // return x + y // } // console.log(add(1,2)) // 和普通函数的区别在哪里? // 构造对象的过程 构造函数 constructor function Person(name, age) { console.log(this) //Person{} this.name = name; this.age = age; } const zhang = new Person('张三', 18); console.log(zhang.name, zhang.age); const li = new Person('李四', 18); console.log(li.name, li.age);
构造函数和普通函数的区别:
- 语法和调用:构造函数需要使用new实例化,并且首字母大写。普通函数可以直接调用。
- this指向:构造函数中,this指向新创建的对象。普通函数在非严格模式下,this指向全局变量。严格模式下,this为undefined。
- 返回对象:构造函数中,如果构造函数没有返回值或返回一个非对象值(如字符串、数字、布尔值等),则新创建的实例对象会被隐式返回。普通函数可以返回任何类型的值。
原型(Prototype)
但凡是JS中的对象都拥有一个原型prototype属性。JS面向对象是原型式的面向对象(设计哲学)。
-
共享方法:使用同一个构造函数创建的对象可以共享原型对象上的方法。所以,我们通常把那些不变的方法,直接定义在
prototype对象上,这样所有对象的实例就可以共享这些方法。 -
节约内存,动态管理:通过将方法定义在原型上,所有实例可以共享同一个方法,从而节省内存。可以在运行时动态地添加或修改方法,所有实例都会受到影响。
//原型对象prototype, 对象可以共享原型对象上的方法
// 构造函数 constructor
function Person(name, age) {
console.log(this);
//将传入的name,age参数分别赋值给this对象的name,age属性
//这样通过Person构造函数创建的对象将具有name属性
this.name = name;
this.age = age;
}
// 每个函数都有一个原型对象 在原型对象上添加方法
Person.prototype = {
eat: function () {
console.log(`${this.name}爱吃饭`);
}
}
// 实例化
const zhang = new Person('张三', 18)
const li = new Person('李四', 18)
// 所有实例都能够调用这种方法
zhang.eat(); //张三爱吃饭
li.eat(); //李四爱吃饭
当访问对象的属性或方法时,先在当前实例对象是查找,然后再去原型对象查找,并且原型对象被所有实例共享。
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.name = '孔子'
Person.prototype.hometown = '山东'
let person1 = new Person('张', 18)
let person2 = new Person('李', 18)
// 创建的两个对象并不相同
console.log(person1 === person2); //false
//先查找实例对象,找不到再去找原型对象
console.log(person1.name, person1.hometown, person2.name);
// 张 山东 李
JavaScript的面向对象设计哲学
JS的原型式面向对象不基于类的继承,而是基于原型的委托。相比较class而言,不会受到class的结构限制,更加灵活,允许动态修改行为。JS的面向对象更加符合前端开发的需求。
const lisi = {
name: '李四',
playBasketball: function () {
console.log('科比来了');
},
}
function Person(name, age) {
console.log(this);
this.name = name;
this.age = age;
}
Person.prototype = lisi;
const san = new Person('张三', 19)
san.playBasketball() //科比来了
构造函数、原型对象与实例的关系
构造函数通过new来创建实例,在构造函数中总有一个原型对象,原型对象可以用来定义所有实例共享的属性和方法。实例通过原型链可以访问原型对象上的属性和方法。
用一张图来说就是
总结
在JavaScript中,构造函数、原型对象和实例是面向对象编程的核心概念。理解它们之间的关系对于编写高效、可维护的代码至关重要。希望这篇文章能帮助您更好地理解这些概念。如果您有任何其他问题或需要进一步的解释,请随时告诉我!