[JavaScript 对象入门]
https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Objects
目录
创建的对象实例是如何与它的构造函数中的 `prototype` 关联的?
面向对象(object-oriented, OO)
面向对象编程 - OOP
对象基础:
首先我们需要先明白什么是对象,对象是一个包含相关数据和方法的集合(通常由一些变量和函数组成,我们称之为对象里面的属性和方法)
/**
*1. 直接量
*/
const obj = {
name: '张三',
age: 18,
address: {},
study() {},
}
//当一个对象初始化创建后,还可以继续向该对象中添加保存新的属性特征,如:
stu.clazz = '一班'
stu.eat = function() {}
/**
*2. 利用封装自定义工厂函数的方式创建对象
*/
let ID = 1
function createStudent(name, age, address) {
const stu = {
id: ID++,
name,
age,
address,
study() {
console.log(this.name + '开始学习')
}
}
return stu
}
/**
*3. 利用构造函数,创建对象
*/
//构造函数函数名的命名规范: 帕斯卡命名规范(即每个单词的首字母都大写,其它字母小写)
let ID = 1
function Student(name, age, address) {
this.id = ID++
this.name = name
this.age = age
this.address = address
this.study = function() {
console.log(this.name + '开始学习')
}
}
const stu = new Student('张三', 18, '四川成都')
/**
*4. `Object.create()` 静态方法以一个现有对象作为原型,创建一个新对象。
*/
const obj = Object.create(proto)
/**
*5. Class语法糖
* 这是 ES6 中新增的创建对象的方式,其只是一个语法糖,本质上还是原型链体系内容
*/
class 类名 {
// 构造函数
constructor() {}
// 成员方法
methodName() {}
}
对象属性方法的调用
1. 使用了点表示法(dot notation)来访问对象的属性和方法,如: `obj.name`
2. 另外一种访问对象属性的方式是使用括号表示法(bracket notation),如: `obj['name']`,这也叫通过索引访问对象属性。使用方括号索引的方式访问对象属性比打点调用更灵活,因为索引是可以通过变量或运算符进行运算最终得到
[继承与原型链]
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Inheritance_and_the_prototype_chain
原型
原型,能够实现对象中的属性复用,是JS中主要实现继承的方式,使用函数中的一个特殊属性 `prototype` 表示
设置原型
/**
* 1. Object.create() 方法创建一个新的对象,并允许你指定一个将被用作新对象原型的对象。
*这里我们创建了一个 personPrototype 对象,它有一个 greet() 方法。然后我们使用 Object.create() 来*创建一个以 personPrototype 为原型的新对象。现在我们可以在新对象上调用 greet(),而原型提供了它的
*实现。
*/
const personPrototype = {
greet() {
console.log("hello!");
},
};
const carl = Object.create(personPrototype);
carl.greet(); // hello!
/**
* 1. 使用构造函数被
*在 JavaScript 中,所有的函数都有一个名为 prototype 的属性。当你调用一个函数作为构造函数时,这
*个属性被设置为新构造对象的原型(按照惯例,在名为 __proto__ 的属性中)。因此,如果我们设置一个构*造函数的 prototype,我们可以确保所有用该构造函数创建的对象都被赋予该原型:
*/
const personPrototype = {
greet() {
console.log(`你好,我的名字是 ${this.name}!`);
},
};
function Person(name) {
this.name = name;
}
Object.assign(Person.prototype, personPrototype);
// 使用 Object.assign 将 personPrototype 中定义的方法绑定到 Person 函数的 prototype 属性上。
// 或Person.prototype.greet = personPrototype.greet;
//在这段代码之后,使用 Person() 创建的对象将获得 Person.prototype 作为其原型,其中自动包含 greet 方法。
const reuben = new Person("Reuben");
reuben.greet(); // 你好,我的名字是 Reuben!
原型链与继承
创建的对象实例是如何与它的构造函数中的 `prototype` 关联的?
`prototype` 是构造函数中的一个显式的属性,它是一个普通对象,通常我们将这个属性称为`原型对象`。使用 `new` 调用构造函数创建出来一个对象实例,该对象实例中有一个隐式属性 `__proto__`,通常我们将该隐式属性称为 `原型属性`。对象实例中的 `__proto__` 指向的就是其构造函数的 `prototype` 属性。
> - 每个对象都有 `__proto__` 的隐式属性
> - 每个函数都有 `prototype` 的显式属性
> - 对象实例中的 `__proto__` 指向的就是其构造函数的 `prototype` 属性。
原型链: 由对象的 `__proto__` 及其构造函数的 `prototype` 所串联的链式结构就是原型链。 `Object.prototype.__proto__` 固定设计为 null,也就意味着到此原型链结束。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
function Student(name) {
this.name = name
}
// 共享共用 study 方法
// 将对象需要共享的属性保存在构造函数的 prototype 属性中
Student.prototype.study = function() {
console.log(this.name + '开始学习')
}
const stu = new Student('小明')
console.log(stu.__proto__)
console.log(stu.__proto__ === Student.prototype) // true
console.log(Student.prototype.__proto__)
// 默认情况下,自定义创建的函数中,prototype 对象的 __proto__
// 指向的是 Object.prototype
console.log(Student.prototype.__proto__ === Object.prototype) // true
// 在 JS 中,固定设计 Object.prototype.__proto__ 为 null
// null 是没有原型的
console.log(Object.prototype.__proto__) // null
console.log('姓名:', stu.name)
stu.study()
console.log(stu.toString())
console.log('年龄:', stu.age) // 原型链中查找不到,返回 undefined
// stu.eat() // Uncaught TypeError: stu.eat is not a function
</script>
</body>
</html>
对象属性查找(继承):当试图访问一个对象的属性时:如果在对象本身中找不到该属性,就会在原型中搜索该属性。如果仍然找不到该属性,那么就搜索原型的原型,以此类推,直到找到该属性,或者到达链的末端,在这种情况下,返回 undefined
。
简单地说:每个对象(object)都有一个私有属性指向另一个名为原型(prototype)的对象。原型对象也有一个自己的原型,层层向上直到一个对象的原型为 null。根据定义,null 没有原型,并作为这个原型链(prototype chain)中的最后一个环节。
注意: 如果需要操作原型链,不要操作隐式属性 `__proto__`,通过操作 `prototype` 实现功能。
最后:继承的方法非常多变灵活,以下是部分实例
//1. 构造函数继承
// 父
function Person(name, age) {
this.name = name
this.age = age
}
// 子
function Teacher(name, age, course) {
Person.call(this, name, age)
this.course = course
}
//2. 原型链继承
function Person() {}
function Teacher() {}
Teacher.prototype = new Person()
//3. 组合继承
// 父
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.eat = function(){}
// 子
function Teacher(name, age, course) {
Person.call(this, name, age)
this.course = course
}
// Teacher.prototype = new Person()
Teacher.prototype = Object.create(Person.prototype)
//4. class 继承
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
eat() {
console.log('父类中吃饭')
}
sleep() {
console.log('父类中睡觉')
}
}
class Teacher extends Person {
constructor(name, age, course) {
// 调用父类构造函数
super(name, age)
this.course = course
}
// 私有的成员方法
teach() {
console.log('授课...')
}
// 重写继承到的sleep() 方法,
// 如果重写该方法时,仍然要调用到父类中的 sleep() 方法。
// 可以使用 super.sleep() 调用到该方法
sleep() {
console.log('自我催眠了...')
super.sleep()
}
}