目录
一、对象的结构
对象中存储属性的区域实际有两个:
1. 对象自身
- 直接通过对象所添加的属性,位于对象自身中
- 在类中通过 x = y 的形式添加的属性,位于对象自身中
2. 原型对象(prototype)
- 对象中还有一些内容,会存储到其他的对象里(原型对象)
- 在对象中会有一个属性来存储原型对象,这个属性叫做_ _proto_ _
- 原型对象也负责为对象存储属性,
当我们访问对象中的属性时,会优先访问对象自身的属性,
对象自身不包含该属性时,才会去原型对象中寻找
(也就是当对象自身和原型对象中含有同一个属性名时,会访问对象自身中的该属性)
- 会添加到原型对象中的情况:
1. 在类中通过xxx(){ }方式添加的方法,位于原型中
2. 主动向原型中添加的属性或方法
<script>
class Person{
//在类中通过x = y 的形式添加的属性,位于对象自身中
name = "孙悟空"
age = 18
//直接通过对象所添加的属性,位于对象自身中
constructor(){
this.gender = "男"//与p.address = '花果山'效果相同,均是为其添加属性
}
sayHello(){
console.log("Hello,我是",this.name);
}
}
const p = new Person()
p.address = '花果山'
p.sayHello = "嘻嘻"
console.log(p.sayHello);//输出“嘻嘻”,若没有上一行添加的sayHello属性,
//则输出ƒ sayHello(){console.log("Hello,我是",this.name);}
</script>
二、原型
访问一个对象的原型对象: 对象._ _proto_ _(开头是下划线或者其他特殊的表示不希望被访问的)
或 Object.getPrototypeOf(对象) (更安全)
原型对象中的数据:
1. 对象中的数据(属性、方法等)
2. constructor (对象的构造函数,也就是对象所在的类)
注意:原型对象也有原型,这样就构成了一条原型链,根据对象的复杂程度不同,原型链的长度也不同,如在下面代码中的p对象和obj对象的原型链长度就不同:
p对象的原型链:p对象 --> 原型 --> 原型 --> null
obj对象的原型链:obj对象 --> 原型 --> null
<script>
class Person{
name = '孙悟空'
age = 18
constructor(){
this.gender = '男'
}
sayHello(){
console.log("hello,我是" + this.name)
}
}
const p = new Person()
const obj = {}
p.address = "花果山"
console.log(p.__proto__)
console.log(p.__proto__.__proto__)
console.log(p.__proto__.__proto__.__proto__)
console.log(obj.__proto__);
console.log(obj.__proto__.__proto__);
</script>
原型链:读取对象属性时,会优先读取对象自身属性
如果对象中有,则使用,没有则去对象的原型中寻找
如果原型中有,则使用,没有则去原型的原型中寻找
直到找到Object对象的原型(Object的原型没有原型(为null))
如果依然没有找到,则返回undefined
注意:
作用域链,是找变量的链,找不到会报错;
原型链,是找属性的链,找不到会返回 undefined 。
三、原型的作用
所有的同类型对象它们的原型对象都是同一个
也就意味着,同类型对象的原型链是一样的
原型的作用:
原型就相当于是一个公共的区域,可以被所有该类实例访问,可以将一个该类实例中所有的公共属性(方法)统一存储到原型中,这样我们只需要创建一个属性,即可被所有实例访问。
JS的继承就是通过原型来实现的
当继承时,子类的原型就是一个父类的实例
在对象中有些属性值是对象独有的,像属性(name, age , gender)每个对象都应该有自己的值
但是有些值对于每个对象来说都是一样的,像各种方法,对于一样的值没必要重复的创建
<script>
class Person{
//在类中通过x = y 的形式添加的属性,位于对象自身中
name = "孙悟空"
age = 18
sayHello(){
console.log("Hello,我是",this.name);
}
}
class Dog{
}
const p = new Person()
const p2 = new Person()
const d = new Dog()
console.log(p)
console.log(p2)
console.log(p === p2);//false
console.log(p.__proto__ === p2.__proto__);//true
console.log(p.__proto__ === d.__proto__);//false
</script>
<script>
class Animal{
}
class Cat extends Animal{
}
class TomCat extends Cat{
}
// TomCat --> cat --> Animal实例 --> object --> object原型 -->null
// cat --> Animal实例 --> object --> object原型 --null
const cat = new Cat()
console.log(cat);//Cat {}
console.log(cat.__proto__);//Animal {constructor: ƒ}
console.log(cat.__proto__.__proto__);//{constructor: ƒ}
console.log(cat.__proto__.__proto__.__proto__);
//{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
console.log(cat.__proto__.__proto__.__proto__.__proto__);//null
</script>
尝试:
函数的原型链是什么样的?
Object的原型链是什么样的?
四、修改原型
大部分情况下不需要修改原型对象
注意:千万不要通过类的实例去修改原型
原因:1.通过一个对象影响所有同类对象,这么做不合适
2.修改原型先得创建实例,这么做麻烦
3.危险
除了通过__proto__能访问对象的原型外,
还可以通过类的prototype属性,来访问实例的原型
修改原型时,最好通过类去修改
好处:
1. 一修改就是修改所有实例的原型
2. 无需创建实例即可完成对类的修改
原则:
1. 原型尽量不要手动改
2. 要改也不要通过实例对象去改
3. 通过 类. prototype 属性去修改
4. 最好不要直接给 prototype 去赋值
<script>
class Person{
name = '孙悟空'
age = 18
sayHello(){
console.log('hello,我是' + this.name);
}
}
class Dog {
}
const p = new Person()
const p2 = new Person()
// 通过对象修改原型,像原型中添加方法,修改后所有同类实例都能访问该方法
// p.__proto__.run = ()=>{
// console.log('我再跑~');
// }
console.log(Person.prototype);//访问Person实例的原型对象,相当于p.__proto__
Person.prototype.fly = ()=>{
console.log('我在飞~');
}
p.fly()
p2.fly()
</script>
五、instanceof 和 hasOwn
Instanceof 用来检查一个对象是否是一个类的实例
- instanceof 检查的是对象的原型链上是否有该类实例
只要原型链上有该类实例,就会返回true
- Object 是所有对象的原型,所以任何对象和Object 进行 instanceof 运算都会返回 true
<script>
class Animal{
}
class Dog extends Animal{
}
const dog = new Dog()
console.log(dog instanceof Dog);//true
// dog -> animal的实例 -> Object实例 -> Object原型
console.log(dog instanceof Animal);//true
console.log(dog instanceof Object);//true
const obj = Object()
console.log(obj.__proto__);
console.log(Object.prototype);//读取object的原型,两种方式
</script>
in
- 使用 in 运算符检查属性时,无论属性在对象自身还是在原型中,都会返回true
对象.hasOwnProperty(属性名)(不推荐使用)
- 用来检查一个对象的自身是否含有某个属性,现在不太用这种方法了
Object.hasOwn(对象,属性名)(推荐使用)
- 用来检查一个对象的自身是否含有某个属性
<script>
class Person{
name = '孙悟空'
age = 18
sayHello(){
console.log('hello,我是' + this.name);
}
}
const p = new Person()
console.log('name' in p);//true,用 in 检查对象中是否有该属性
console.log('sayHello' in p);//true
console.log(p.hasOwnProperty('name'));//true
console.log(p.hasOwnProperty('sayHello'));//false
console.log(p.hasOwnProperty('hasOwnProperty'));//false
console.log(p.__proto__.hasOwnProperty('hasOwnProperty'));//false
console.log(p.__proto__.__proto__.hasOwnProperty('hasOwnProperty'));//true
console.log(Object.hasOwn(p,'sayHello'));//false
</script>
六、旧类
早期的JS中,直接通过函数来定义类
- 一个函数如果直接调用 xxx( ), 那么这个函数就是一个普通函数
- 一个函数如果通过new 调用 new xxx( ) 那么这个函数就是一个构造函数
等价于:class Person{ }
静态属性 Person.staticProperty = 'xxx'
静态方法 Person.staticMethod = function(){}
<script>
// function Person(name,age){
// //在构造函数中,this表示新建的对象
// this.name =name
// this.age = age
// // this.sayHello = ()=>{
// // console.log(this.name);
// // }
// }
// //向原型中添加属性(方法)
// Person.prototype.sayHello = function(){
// console.log(this.name);
// }
// // 静态属性
// Person.staticProperty = 'xxx'
// // 静态方法
// Person.staticMethod = function(){}
// 但是目前这样的话,对于Person的操作分段了,可能会在那一块就创建了实例对象
// 故应采用闭包将其放在一起,如下所示
var Person = (function(){
function Person(name,age){
//在构造函数中,this表示新建的对象
this.name =name
this.age = age
// this.sayHello = ()=>{
// console.log(this.name);
// }
}
//向原型中添加属性(方法)
Person.prototype.sayHello = function(){
console.log(this.name);
}
// 静态属性
Person.staticProperty = 'xxx'
// 静态方法
Person.staticMethod = function(){}
return Person
})()
const p = new Person('孙悟空', 18)
console.log(p);
var Animal = (function(){
function Animal(){
}
return Animal
})()
var Cat = (function(){
function Cat(){
}
// 继承Animal
Cat.prototype = new Animal()
return Cat
})()
</script>
七、new运算符
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new
- 当使用 new 去调用一个函数时,这个函数将会作为构造函数调用,
使用new调用函数时,将会发生这些事:
1.创建一个普通的JS对象(Object对象{ }),为方便,我们称其为新对象
2. 将构造函数的prototype属性设置为新对象的原型
3. 使用实参来执行构造函数,并且将新对象设置为函数中的 this
4. 如果构造函数返回的是一个非原始值,则该值会作为new 运算的返回值返回(千万不要这么做),
如果构造函数的返回值是一个原始值或者没有指定返回值,则新的对象将会作为返回值
通常不会为构造函数指定返回值
<script>
function MyClass(){
// var newInstance = {}
// newInstance.__proto__ = MyClass.prototype
// return {name:'孙悟空'}
return 1
}
var mc =new MyClass()
console.log(mc);
</script>
八、对象总结
面向对象本质就是,编写代码时所有的操作都是通过对象来进行的。
面向对象的编程的步骤:
1.找对象
2.搞对象(对对象进行操作)
学习对象:
1. 明确这个对象代表什么,有什么用
2.如何获取到这个对象
3. 如何使用这个对象(对象中的属性和方法)
对象的分类:
内建对象
- 由ES标准所定义的对象,比如 Object Function String Number......
宿主对象
- 由浏览器提供的对象,如document
- BOM、DOM
自定义对象
- 由开发人员自己创建的对象
-