文章目录
原型
原型是如何解决的
-
原型
每个函数都会自动附带一个属性
prototype
,这个属性的值是一个普通对象,称之为原型对象 -
实例
instance,通过
new
产生的对象称之为实例。由于JS中所有对象都是通过
new
产生的,因此,严格来说,JS中所有对象都称之为实例 -
隐式原型
每个实例都拥有一个特殊的属性
__proto__
,称之为隐式原型,它指向构造函数的原型
这一切有何意义?
当访问实例成员时,先找自身,如果不存在,会自动从隐式原型中寻找
这样一来,我们可以把那些公共成员,放到函数的原型中,即可被所有实例共享
这是啥?
不同的场景,这 指代的含义不同,JS中的this关键字也是如此:
-
在全局代码中使用this,指代全局对象
在真实的开发中,很少在全局代码使用this
-
在函数中使用this,它的指向完全取决于函数是如何被调用的
调用方式 示例 函数中的this指向 通过new调用 new method()
新对象 直接调用 method()
全局对象 通过对象调用 obj.method()
前面的对象 call method.call(ctx)
call的第一个参数 apply method.apply(ctx)
apply的第一个参数
var person = {
name:'Autha',
age:17,
sayHi:function(){
//完成该方法打印姓名和名字
console.log(person.name,person.age)
},
};
person.sayHi();
/*
正常情况下调用这样的函数,就像这样写的函数,也不是不可以
就是如果函数发生改变,就会出现问题
*/
var person = {
name:'Autha',
age:17,
sayHi:function(){
console.log(this.name,this.age);
},
};
var person1 = person;
person1.sayHi();
//使用this能够很好的解决函数发生变化时候的问题,所以只要理解好this的用法,是好用的
function User(firstName,lastName)
{
this.firstName = firstName;
this.lastName = lastName;
this.fullName = firstName + this.lastName;
}
//能否不适用new,通过User函数创建对象(不能更改User函数)
var u = {};
User.call(u,'小','陈');
console.log(u);
原型链
什么是原型链
所有的对象都是通过new 函数
的方式创建的
var u1 = new User('小', '陈'); // 对象 u1 通过 new User 创建
var u2 = { // 对象 u2 通过 new Object 创建
firstName: '小',
lastName: '陈'
}
// 等效于
var u2 = new Object();
u2.firstName = '小';
u2.lastName = '陈';
上面的代码形成的原型图如下
我们在创建完一个对象,但是在使用这个对象的时候,发现本身没有给这个对象设置相应的方法,但是有的方法他是确实存在的,这个就是原型,在创建完一个对象之后,他除了具备它本身的方法,他仍然具备原型上面的方法。这个给今后的开发中带来很大的便利性,举一个例子,在进行创建三个学生的对象的时候,需要设置学生的学号,姓名等信息,这些是不同的,但是对这个学生构造一种算成绩平均分的方法是相同的,所以可以把他创建在原型上面,这样代码的冗余量就得到极大的提升,三个学生只是一个例子,扩大到更多的数量就是一个多么快捷便利的事。
Function.prototype.isFun = true;
function sum(){}
console.log(sum.isFun);
原型对象本身也是一个对象,默认情况下,是通过new Object
创建的,因此,上面的两幅原型图是可以发生关联的
Object.prototype.__proto__
比较特殊,它固定指向null
可以看出,u1的隐式原型形成了一个链条,称之为原型链
当读取对象成员时,会先看对象自身是否有该成员,如果没有,就依次在其原型链上查找
完整的链条
对开发的影响
在原型上更改会产生多大影响
更改构造函数的原型会对所有原型链上有该构造函数的原型的对象产生影响
学会利用原型链判断类型
-
instanceof
关键字【常用】object instanceof constructor // 判断object的原型链中,是否存在constructor的原型
-
Object.getPrototypeOf()
【不常用】Object.getPrototypeOf(object); // 返回object的隐式原型
学会创建空原型的对象
-
利用
Object.create()
Object.create(target); // 返回一个新对象,新对象以target作为隐式原型
-
利用
Object.setPrototypeOf()
Object.setPrototypeOf(obj, prototype); // 设置obj的隐式原型为prototype
继承
会员系统
视频网站有两种会员:
- 普通会员
- 属性:用户名、密码
- 方法:观看免费视频
- VIP会员
- 属性:普通会员的所有属性、会员到期时间
- 方法:普通会员的所有方法、观看付费视频
如果我们需要使用构造函数来创建会员,如何书写构造函数才能实现上面的需求?
// 普通会员的构造函数
function User(username, password){
this.username = username;
this.password = password;
}
User.prototype.playFreeVideo = function(){
console.log('观看免费视频')
}
// VIP会员的构造函数
function VIPUser(username, password, expires){
this.username = username;
this.password = password;
this.expires = expires;
}
VIPUser.prototype.playFreeVideo = function(){
console.log('观看免费视频')
}
VIPUser.prototype.playPayVideo = function(){
console.log('观看付费视频')
}
上面的代码出现了两处重复代码:
-
VIPUser的构造函数中包含重复代码
this.username = username; this.password = password;
这段代码和User构造函数并没有区别,可以想象得到,将来也不会有区别,即:普通用户该有的属性,VIP用户一定有
-
VIPUser的原型上包含了重复代码
VIPUser.prototype.playFreeVideo = function(){ console.log('观看免费视频') }
这个方法和User上的同名方法逻辑完全一致,可以想象得到,将来也不会有区别,即:普通用户该有的方法,VIP用户一定有
如何解决上述两处重复?
处理构造器内部的重复
可以将VIPUser构造器改写为
function VIPUser(username, password, expires){
User.call(this, username, password);
this.expires = expires;
}
处理原型上的重复
只需要将原型链设置为下面的结构即可
仅需一句代码即可
Object.setPrototypeOf(VIPUser.prototype, User.prototype)
至此,完美的解决了之前提到的两处重复代码的问题
这和继承有什么关系
继承是面向对象的概念,它描述了两个对象类型(类,构造函数)之间的关系
如果在逻辑上可以描述为:A不一定是B,但B一定是A,则:B继承A、A派生B、A是B的父类、B是A的子类
子类的实例应该自动拥有父类的所有成员
继承具有两个特性:
- 单根性:子类最多只有一个父类
- 传递性:间接父类的成员会传递到子类中
如何在JS中封装继承
function inherit(Child, Parent){
// 在原型链上完成继承
Object.setPrototypeOf(Child.prototype, Parent.prototype);
}
过去,由于没有提供更改隐式原型的方法,因此这一过程会比较复杂。那时候,我们使用一种称之为「圣杯模式」的办法来达到相同的目的,这里不做介绍。
function inherit(Child, Parent) {
Object.setPrototypeOf(Child.prototype, Parent.prototype);
}
// 普通会员的构造函数
function User(username, password) {
// this = {}
this.username = username;
this.password = password;
}
User.prototype.playFreeVideo = function () {
console.log('观看免费视频');
};
// VIP会员的构造函数
function VIPUser(username, password, expires) {
// this = {}
User.call(this, username, password);
this.expires = expires;
}
VIPUser.prototype.playPayVideo = function () {
console.log('观看付费视频');
};
// 完成原型上的变化
inherit(VIPUser, User);
var vip = new VIPUser('abc', '123', '2022-10-01');
vip.playFreeVideo();