一.创建对象的方式
1.通过字面量的方式创建
js
//创建的对象的方式
var obj = { name: "mm", age:18}
console.log(obj); // {name: 'mm', age: 18}
2.工厂模式创建对象
js
//工厂方式创建
function createObj(name, age){
var obj = {}
obj.name = name
obj.age = age
return obj
}
const obj2 = createObj("kk", 19)
console.log(obj2);
3.通过构造函数创建
在MDN官网上有单独对
new关键字
解释和描述地址
其中在对new的描述中
有主要的几点
- 在内存中创建一个空对象
- 这个对象的内部的【prototype】属性会被赋值为该构造函数的prototype属性
- 构造函数内部的this,会指向创建出来的空对象
- 执行函数的内代码
- 如果构造函数没有返回非空对象,则返回创建出来的新对象 this
js
function Person(name,age,height){
this.name = name;
this.age = age;
this.height = height;
this.eating = function(){
console.log(this.name, '在吃东西');
}
}
var p2 = new Person('lee',20, 1.80);
console.log(p2);
二.原型
从构造函数创建对象中, 我们发现第2点的描述中涉及到
原型
了,其实每个对象都有原型
1.普通对象的原型
每个普通的对象都会有一个
原型属性
[[prototype]]
js
var obj = { name: 'mm'};
console.log(obj);
//打印的结果可以看到 [[Prototype]]属性,这个属性是一个对象
1.1 如何查看这个原型
通过下面的方式查看原型:
1.对象的
__proto__
属性获取(注意:这个属性是浏览器自己实现, 不一定所有的浏览器都有这个属性)2.通过
Object.getPrototypeOf(obj)
方法获取3.下面打印的原型, 出来的结果是一个对象, 我们称这个对象是原型对象
js
var obj = { name: 'mm'};
//浏览器中使用 __proto__ 查看原型
console.log(obj.__proto__);
//代码查看原型 object 类型
var proto = Object.getPrototypeOf(obj) ;
console.log("proto----",proto);
1.2原型的作用
1.我们知道创建的对象是
key-value
形式, 我们可以通过key
获取到对象的value
值
2.当我们用一个key
, 去对象obj中获取value
时,如果在obj中获取不到, 就会去obj的原型中查找,如果找不到就会返回undefined
js
// 当我们从一个对象中获取某一个属性的值时
// 1. 在当前对象中去查找对应的属性, 如果找到就直接使用
// 2. 如果没有找到, 那么会沿着它的原型去查找 [[prototype]]
console.log(obj.age); //undefined
//给原型对象上添加age
obj.__proto__.age = 18;
console.log(obj.age); //18
2.函数的原型
每一个函数都有一个
prototype
原型属性, 因为函数也是一个对象,所以还有一个__proto__
一般我们把
prototype称为函数的显式原型
__proto__我们称为函数作为对象的隐式原型
js
// 1.将函数看成是普通的对象的时候,他具备一个__proto__隐式原型
function foo(){}
//把函数是一个对象
console.log("对象原型__proto__:",foo.__proto__);
// 函数它因为是一个函数, 函数也会有一个prototype显示原型
// 2.每个函数默认有一个显示的原型prorotype
console.log("函数原型prototype:",foo.prototype);
从上面的打印中, 函数作为对象 和 函数是函数都有原型, 那么
对象原型__proto__
原型 和函数prototype
原型 有什么关系?
2.1 函数对象原型__proto__ 和 函数原型(prorotype)的关系
想要搞清楚这2者之间的关系, 就需要我们从
new关键字和构造函数说起
, 通过new创建对象,做了下面的步奏:
- 在内存中创建一个空对象
- 这个对象的的
__prpto__属性
会被赋值为该构造函数的prototype属性
- 构造函数内部的this,会指向创建出来的空对象
- 执行函数内的代码
- 如果构造函数没有返回非空对象,则返回创建出来的新对象 this
js
//自定义构造函数创建对象
function Person(){
//模拟 new关键字 (模拟代码)
var obj = {}
this = obj
this.__proto__ = Person.prototype
return this
}
从而我们可以知道通过 构造函数创建的
实例对象(instance)的原型__proto__
和构造函数的原型prototype
是相同的
js
//构造函数
function Foo(){}
//实例对象
var f = new Foo()
console.log(f.__proto__ === Foo.prototype) // true
2.2函数,函数原型,实例对象三者的关系
首先给出结论:
每个函数都有一个默认的属性叫做:prototype, prorotype属性保存一个对象, 这个对象我们称为原型对象
- 每个
原型对象
中都有一个默认的属性constructor
,这个constructor属性指向当前原型对象对应的 '构造函数'
- 通过构造函数创建出来的对象 我们称为:
实例对象
, 每个实例对象
中都有一个__proto__属性
, 这个__proto__属性
指向创建它的那个构造函数的原型对象prototype
js
function Foo() {}
//获取prototype原型对应的对象
console.log(Foo.prototype);
//从上面的打印结果 我们可以看到 prototype原型对象 有一个constructor属性,这个constructor属性 指向的是 Foo函数
console.log(Foo.prototype.constructor === Foo) //true
//实例对象
var f1 = new Foo();
//实例对象的__proto__属性 指向 构造函数Foo的原型对象
console.log(f1.__proto__ === Foo.prototype); //true
3者关系的展示图
2.3原型共享方法
其实我们通过构造函数创建多个实例对象的时候, 如果在构造函数中定义方法,其实是有一定的问题的,下面我们观察一下是什么问题
js
//构造函数
function Student(name){
this.name = name
this.study = function(){
console.log(`${this.name}好好学习`);
}
}
//创建实例对象
var s1 = new Student("MM");
console.log(s1.name); // MM
s1.study(); //
var s2 = new Student("TT");
console.log(s2.name); //TT
s2.study()
//说明2个实例的study方法不是同一个方法
console.log(s1.study === s2.study); // false
问题: 如果我们创建多个对象, 例如:创建100个或者更多的实例对象, 那么就会在内存中创建 100个study方法,这样就会浪费很多内存。 举个例子:学校有100个学生, 不能给100个学生配置100个教室用来学习, 共享一个教室就可以用来学习了
那么我们就会用到原型对象
,由于实例对象的原型__proro__ 和 函数原型prototype的关系
,所以在函数原型protorype的对象上 设置study方式,是可以共用共享的, 因为所有创建出来的实例对象的原型__proto__都会指向函数的prototype原型可以参见上面的关系展示图
js
function Student(name){
this.name = name
}
//给原型对象添加方法
Student.prototype.study = function(){
console.log(`${this.name}好好学习`);
}
var s1 = new Student("MM");
console.log(s1.name);
s1.study()
var s2 = new Student("TT");
console.log(s2.name);
s2.study()
console.log(s1.study === s2.study); // true
2.4自定义原型对象
js
//构造函数
function Person() {}
//我们知道 原型对象有一个constructor属性,指向构造函数
//所以 自定义构造函数需要添加constructor
Person.prototype = {
//这样指定的缺点:是可以获取到胡乱添加数据属性
// constructor:Person,
msg:'你好啊',
say:function(){
console.log('你好啊');
}
}
//所以通过defineProperty添加属性比较合适
Object.defineProperty(Person.prototype,'constructor',{
value: Person,
configurable:true,
enumerable:false,
writable:true
})
//打印原型对象
console.log(Person.prototype);
//看看关系
console.log(Person.prototype.constructor === Person); //true
2.5原型链
我们知道,在对象(obj)上获取属性,如果在当前对象(obj)中没有获取到,就会去它的原型上去查找获取
js
//创建一个对象
var obj = {
name:'lee',
age: 18
}
//相当于执行了
// var obj = new Object();
//前面我们已经知道 对象__proto__ 和 函数prototype原型及 实例对象的关系
console.log(obj.__proto__ === Object.prototype); // true
//如果我们访问 message属性
console.log(obj.message)
//那么就会执行下面的查找操作
//1.现在obj对象中获取message
//2.如果没有。 去__proto__原型对应的原型对象查找
//3.我们知道 __proto__是函数 protorype的对象的指向, prototype对象的原型也是有原型对象的,所以也会去查找
//这个查找的对应原型的链条我们称为原型链
结果 查找顺序
- obj上面查找
obj.__proto__
上面查找obj.__proto__.__proto__
-> null上面查找- obj.__proto__是指向Object.prototype原型对象, 原型对象也是对象, 也有自己的__proto__属性 指向null
1.对象中__proto__组成的链条我们称之为原型链2.对象在查找属性和方法的时候, 会先在当前对象查找
如果当前对象中找不到想要的, 会依次去上一级原型对象中查找
如果找到Object原型对象都没有找到, 就会返回undefined
从上面的
obj.__proto__.__proto__
打印中我们可以知道(下图),这个原型对象的的构造函数是 Object,这个对象的原型__proto__指向是null
三.继承
我们知道面向对象编程, 对象的3大特性: 封装,多态,继承
封装: 就是把属性和方法 封装到一个类中
继承: 把一些重复的代码和逻辑抽取到父类, 方便子类使用
先看一段代码, 从下面的代码中, 我们可以发现其实有很多属性和方法是可以共用的,比如: name, age属性 和 一些 running,eating方法继承:我们可以把 name,age属性和running,eating方法抽取到 Person类里面, 让 Student和Teacher继承上面的属性和方法, 并且Student特有的属性和方法 还保持在自己类里面
js
function Person(name, age){
this.name = name
this.age = age
}
//给 Person实例对象 共享的方法
Person.prototype.running = function(){}
Person.prototype.eating = function(){}
function Student(name,age,sno) {
this.name = name;
this.age = age;
this.sno = sno;
}
Student.prototype.running = function(){}
Student.prototype.eating = function(){}
Student.prototype.studing = function(){}
function Tearcher(name,age,title) {
this.name = name;
this.age = age;
this.title = title;
}
Tearcher.prototype.running = function(){}
Tearcher.prototype.eating = function(){}
Tearcher.prototype.teach = function(){}
1.实现继承的方式一(原型链继承)
前面我们了解了什么事原型链, 那么我们如果通过原型链实现继承
js
function Person(){
this.name = "lee"
this.friends = ["TT"]
}
Person.prototype.eating = function(){
console.log(this.name, '吃东西');
}
//子类
function Student(){
this.name = 'stu123';
this.sno = '789';
}
var p = new Person();
//让Student的原型对象指向p实例对象,方便stu实例对象通过原型查找,访问
Student.prototype = p;
//学生特有的方法
Student.prototype.studying = function(){
console.log('学习');
}
var stu = new Student();
stu.studying(); // 学习
stu.eating(); //stu123 吃东西
stu.friends.push("MM")
p.eating(); //lee 吃东西
从上面的代码中我们可以发现
1.共享了p实例的属性 stu可以修改访问
2.不能给Person传递属性,可以共享给stu
下面是原型链继承的指向图
2.借用构造函数继承
借用构造函数就是为了解决
原型链继承
的问题,我们通过pply()和call()方法
来实现,其实这个2个方法还蛮重要, 其中this
的指向问题就可以这个2个方法解决
js
//父类
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.run = function(){
console.log(this.name + "跑步");
}
//借用构造函数 主要用于继承属性
function Student(name,age,sno){
//借用构造函数调用, 更改this的指向
Person.call(this,name,age);
//学号
this.sno = sno;
}
var stu = new Student('Lee',15,788889);
console.log(stu);
console.log(stu.name);
// stu.run() //报错 方法找不到
缺点: 不能继承使用原型的属性和方法
3.组合式继承
组合式继承主要是 上面2中方式的结合
js
//组合继承
function Person(name, age){
this.name = name
this.age = age
}
Person.prototype.running = function(){
console.log(this.name + "跑步");
}
const p = new Person("kkk",19)
console.log(p);
console.log(p.__proto__);
function Student(name, age, sno){
Person.call(this, name, age)
this.sno = sno
}
Student.prototype = new Person()
Student.prototype.studying = function(){
console.log(this.name + "学习");
}
const s1 = new Student("lee", 18,1001);
console.log(s1);
s1.running()
s1.studying()
const s2 = new Student("礼拜", 20, 10002)
console.log(s2);
s2.running()
s2.studying()
console.log("===============");
console.log(s1.__proto__);
从上面代码的执行中 我们可以发现,Person函数会被执行2次,并且我们可以从下面的图中发现, Student的原型对象p中 有2个共享的name和age属性
4.寄生组合式继承
从上面的继承中,其实还可以在优化,我们发现在指定
原型对象
的时候,我们使用了父类的 构造函数创建一个p的实例对象,实际中我们只要满足:
1.创建一个实例对象instance
2.这个对象instance的隐式原型__proto__指向父类的显示原型prototype;
3.把对象赋值给子类的显示原型ptototype
js
function inherrt(Subtype,SuperType){
//Object.create方法用于创建一个新对象,使用现有的对象来作为新创建对象的原型
// Subtype.prototype = Object.create(SuperType.prototype);
//利用空对象 没有任何问题
Subtype.prototype = creatObject(SuperType.prototype);
Object.defineProperty(Subtype.prototype,'constructor',{
value: Subtype,
configurable:true,
enumerable:false,
writable:true
});
}
function creatObject(o){
function F(){}
F.prototype = o;
return new F();
}
//继承的实现
//父类
function Person(name,age){
this.name = name;
this.age = age;
}
Person.prototype.running = function(){
console.log('跑步')
}
Person.prototype.eating = function(){
console.log('吃饭')
}
//子类
function Student(name,age,sno){
Person.call(this,name,age);
this.sno = sno;
}
//实现方法继承
inherrt(Student,Person);
Student.prototype.studying = function(){
console.log('学习')
}
var s1 = new Student('lee',18,999000);
s1.running();
s1.eating();
s1.studying();
console.log(s1.name);
console.log(s1.sno);
console.log(s1);
console.log("-=================");
const p = new Person("mm", 19)
console.log(p);