原型链,原型对象

原型链大都是围绕着对象来进行的,在此之前,我们需要知道一个问题:
创建对象的方法有几种,区别是什么?

创建对象的常用的几种方法

1.字面量

let obj1 = {
			name: 'laocao',
			age: 22,
			sayHi: () => {
				console.log(`Hello,this is${name}`);
			}
		};

缺点:复用性差,也就是说,当创建相同类型对象的实例的时候,需要重复写代码

2.工厂模式创建对象

function createObject(name , age) {
            let obj = new Object();
            
            obj.name = name;
            obj.age = age;

            obj.sayHi = () => {
                console.log(`Hello,this is${name}`);
            };
            
            return obj;
        }

let obj2 = createObject('laocao', 22);

解决了字面量方式复用性差的缺点,把创建对象的过程封装在一个函数,最后返回这个对象的实例,以后创建大量相同类型的实例只需调用函数即可。

缺点:无法识别对象的类型,大家都是通过new Object创建的,我怎么知道某个实例来自于哪个对象的呢?

从本质上来说,上面两种创建对象的方式原理是一样的,因为原型链是一致的,在浏览器控制台测试:
在这里插入图片描述
为了解决对象识别问题,又有一种方式创建对象了:自定义构造函数

3.自定义构造函数

function Person(name, age) {
			this.name = name;
			this.age = age;
			this.sayHi = () => {
				console.log(`Hello,this is${name}`);
			};
		}

		let obj3 = new Person('laocao',22);

相对于上面一种方法,这种方法有效解决了对象类型的问题,而且为了区别与普通函数,构造函数首字母要大写,除此之外,需要通过new关键字来调用这个函数,将this指向当前实例对象,否则,这个函数是window对象的。总结自定义构造函数创建对象:

做了四件事:
1.在堆内存中开辟(申请一块空闲的,new)空间,存储新的对象
2.把this设置成当前对象
3.设置属性值和方法值
4.返回这个对象(实例或者是说对象的引用,在栈存储)

在这里插入图片描述
ps:图画的可能不太标准,内存名是我随便找的,大概是这种格式。

好了新问题来了,我们在创建一个对象的实例

let obj4 = new Person('laocao',22);

然后在浏览器判断obj3与obj4的方式是不是一样的
在这里插入图片描述
很显然,这是两个不同的方法,因为在堆中申请了两块内存(new了两次),所以如果想使用这个方法,需要再次创建对象,这样就浪费了内存。

4.Object.create(proto, [propertiesObject])方式

proto:新创建对象的原型对象

返回值:在指定原型对象上添加新属性后的对象

后面参数具体用法可以查看MDN

MDN原话:

Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__

其实也就是实现继承,具体继承哪个对象,就看里面参数指向哪个对象的原型对象了

通过这种方式创建的对象,它的属性和方法都来自它所继承的原型对象中

比如:创建空对象,也就是Object的原型对象
在这里插入图片描述
因为还没有将原型、原型链、实例对象、构造函数、原型对象之间的关系,所以先讲述它们之间的关系。

原型、原型链、实例对象、构造函数、原型对象

我把自定义构造函数创建对象那个例子改一下:

function Person(name, age) {
			this.name = name;
			this.age = age;
		}

		Person.prototype.sayHi = () => {
			console.log(`Hello,this is${this.name}`);
		};

		let obj3 = new Person('laocao',22);
		let obj4 = new Person('laocai',20);

此时我在浏览器测试:
在这里插入图片描述
sayHi那个方法不见了,而且两个实例化对象的方法是一样的了,这是为啥呢?

在回答这个问题之前,先用一张图来看看原型,原型对象,构造函数,实例对象的关系:
在这里插入图片描述
定义构造函数Person,添加属性和方法

在定义函数Person的时候,浏览器会给这个函数加上prototype属性,这个属性就是原型,也是一个对象,在控制台输出Person.prototype就可知,prototype不仅是Person的属性,也是它的原型对象
在这里插入图片描述
那么在这个原型对象里面,有一个sayHi方法,有constructor属性和__proto__属性。而constructor这个属性指向的就是Person这个函数,说明Person原型对象prototype的构造器是Person。这个原型对象里面的__proto__指向的是Object的原型对象prototype

这个函数里面其实还有一个__proto__属性,它指向的是Funtion的原型对象,而Function的原型对象里面的__proto__指向的就是顶级对象Object的原型对象

②实例化对象

通过new运算符就实例化了一个对象 ,这个实例化对象里面有一个__proto__属性,这个属性也叫原型,同时也是原型对象,它指向的是构造函数Person的原型对象prototype

总结:

  • 对象一定有__proto__,函数一定有prototype,函数里面其实可能还有__proto__,因为函数也是对象,它是由Function创建的

  • 通过实例对象的__proto__和构造函数的原型对象prototype之间的联系就形成了原型链,原型链的顶端是null,它是Object的原型对象

  • __proto__是浏览器里面的,现在还不规范,而prototype现在成为了标准

再回到上面的问题,sayHi方法放在了在Person的原型对象中,所以这个方法是可以被共享的,这样节省了空间。

上面所有过程的测试:
在这里插入图片描述

instanceof运算符的原理

判断实例对象是哪种类型
在这里插入图片描述
背后的原理其实就是判断实例对象的__proto__以及它的原型链和构造函数的prototype是不是指向同一块地址,因为Object和Person在obj3的原型链上,所以系统判断为是同一个引用地址,返回true,如果需要严格判断可以让实例对象中的__proto__所指向原型对象的构造器和构造函数进行比较

在这里插入图片描述

new运算符的原理

还是那上面的obj3来举例

1.创建一个继承于Person.prototype的新对象

2.执行构造函数Person,传进相应的参数,将this指向这个新对象的实例

3.如果构造函数返回了一个对象,那么就代替new 创建的对象

//模拟new运算符的原理,参数fn是构造函数
		let newObject = fn=> { 
			//1.创建一个继承于构造函数的原型对象的新对象obj
			let obj = Object.create(fn.prototype);
			//2.执行构造函数,并将this指向当前实例对象
			let o = fn.call(obj);
			//3.判断构造函数有没有返回值o
			if (typeof o === 'object') {
				return o;
			} else {
				return obj;
			}

		 };

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值