js-原型与继承
一、原型与继承
1.原型对象
__proto__
就是原型也就是父亲
prototype
主要是用来定义构造函数的原型,主要作用是通过这个构造函数创建的所有对象都可以使用这个原型里面的属性
什么是对象?什么是原型对象?
因为原型同时也是对象,所以也可以称呼为原型对象,父母就类似原型
原型对象的用途是?
Javascript 并没有类继承模型,而是使用原型对象进行原型式继承。
原型对象的用途是为每个实例对象存储共享的方法和属性,它仅仅是一个普通对象而已。并且所有的实例是共享同一个原型对象,因此有别于实例方法或属性,原型对象仅有一份。
创建对象
function Person(){ } //构造函数创建对象
Person.prototype.name = "Jie"; //添加属性值到原型对象上
console.log(Person.prototype);
原型包含 constructor
属性,指向构造函数
对象包含 __proto__
指向他的原型对象
默认情况下创建的对象都有原型
2.根对象
let x = {};
let y = {};
console.log(Object.getPrototypeOf(x) == Object.getPrototypeOf(y)); //true
注:x、y的原型都为元对象Object,即JS中的根对象
3.Object的hasOwnProperty()
方法返回一个布尔值,判断对象是否包含特定的自身(非继承)属性
let hd = {name: 3 };
console.log(hd.hasOwnProperty("name"));
4.创建一个对象
语法:
Object.create(proto, [propertiesObject])
//方法创建一个新对象,使用现有的对象来提供新创建的对象的proto。
参数:
proto : 必须。表示新建对象的原型对象,即该参数会被赋值到目标对象(即新对象,或说是最后返回的对象)的原型上。该参数可以是null, 对象, 函数的prototype属性 (创建空的对象时需传null , 否则会抛出TypeError异常)。
propertiesObject : 可选。 添加到新创建对象的可枚举属性(即其自身的属性,而不是原型链上的枚举属性)对象的属性描述符以及相应的属性名称。这些属性对应Object.defineProperties()的第二个参数。
3 返回值:
在指定原型对象上添加新属性后的对象。
5.没有原型的对象也是存在的
let hd = { name: 3 };
console.log(hd.hasOwnProperty("name"));
let xj = Object.create(null, {
name: {
value: "伽罗"
}
});
console.log(xj.hasOwnProperty("name")); //Error
//Object.keys是静态方法,不是原型方法所以是可以使用的
console.log(Object.keys(xj));
6.函数拥有多个原型,prototype(原型对象)
用于实例对象使用,__proto__
(对象原型)用于函数对象使用
7.原型方法与对象方法优先级
let hd = {
show() {
console.log('伽罗');
}
};
hd.reder() //自己也没有长辈也没有所以就报错了
**********************************************************************************************
<script>
let hd = {
show() {
console.log('伽罗');
},
reder() {
console.log('hd.reder方法');
}
};
console.log(hd);
hd.__proto__.reder = function () {
console.log('伽罗2');
}
hd.reder() //执行自己的reder方法,自己有钱就不用了麻烦和长辈借钱了
</script>
8.给原型(父亲)添加方法
let hd = {
show() {
console.log('伽罗');
}
};
hd.__proto__.reder = function(){
console.log('伽罗2');
}
hd.reder()
9.函数拥有多个长辈prototype
与__proto__
函数也是对象
prototype * 服务于 函数实例化出来的这个对象
__proto__ * 服务于 函数对象
10.原型关系详解与属性继承实例
<script>
let hd = new Object();
hd.name = '你好';
Object.prototype.show = function(){
console.log('我很好呢');
};
function user() {}
// console.dir(user);
// console.log(user.prototype.__proto__ == user.__proto__.__proto__); //true
// console.dir(Object.prototype.__proto__);//null
let x = new user()
x.show()
</script>
11.系统构造函数的原型体现
- 构造函数拥有原型
- 创建对象时构造函数把原型赋予对象
12.自定义对象的原型设置 和 获取原型
let a = { name: '你好' };
let b = {
name: '我很好',
show() {
console.log(this.name); //this.name 始终是你调用的对象
}
};
Object.setPrototypeOf(a, b); //设置原型 a对象的父级原型是b
// a.show(); //你好
// b.show(); // 我很好
console.log(Object.getPrototypeOf(a)); //获取原型
13.原型中的constructor引用
function user(name){
this.name = name
}
user.prototype={
constructor:user, //指针丢失了就成单项了 需要手动添加 不加报错
show(){
console.log(this.name);
}
}
// console.dir(user);
let lisi = new user.prototype.constructor('李四')
lisi.show()
14.给我一个对象还你一个世界(constructor)
function user(name) {
this.name = name;
this.show = function(){
console.log(this.name);
}
}
let hd = new user('伽罗')
console.log(hd);
function cerateByObject(obj, ...args){
const constructor = Object.getPrototypeOf(obj).constructor;
return new constructor(...args)
}
//constructor必须存在,如果存在就可以根据我找到我的叔叔,我的叔叔通过constructor也可以找到我,
//没有就成单项了我可以找到我叔叔,叔叔找不到我
let x = cerateByObject(hd,'榴莲')
x.show()
// console.log(x);
二、原型链/监测/方法借用
15.什么是原型链
类似于python中的继承关系m2o
意思就是:你想开车,你没有车子,但是你父母有车子,你就可以开他的车,你想回家睡觉,但是你没有房子,你的父母有房子你就可以去住
爷爷,父母,自己这种链条关系就是原型链
每个对象对应拥有一个原型,对象以其原型为模板、从原型继承方法和属性。而同时原型也是对象,它也拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain),它解释了为何一个对象会拥有定义在其他对象中的属性和方法
16.原型链检测之instanceof -构造函数判断 (一个构造函数的prototype是否在另一个对象的原型链上)
17. Object.isPrototypeOf原型检测 -明确判断对象 (一个对象是否是另一个对象的长辈)
18.in与
hasOwnProperty`的属性检测差异
-
in: 'url' 属性是否在a对象上,还会检测是否在a对象的原型链上
-
hasOwnProperty
: a当中是否含有'url'这个属性,他只是单纯的监测对象,不会攀升到原型链
19.使用call或apply借用原型链
let hd = {
data: [1,2,3,34,5,7,]
};
Object.setPrototypeOf(hd,{
max() {
return this.data.sort((a,b) => b-a)[0];
},
});
console.log(hd.max());
let x = {
lets: {a:10,b:12,c:95},
//getter
get data() {
return Object.values(this.lets)
}
};
console.log(hd.max.apply(x));
let hd = {
data: [1,2,3,34,5,7,]
};
Object.setPrototypeOf(hd,{
max(data) {
return data.sort((a,b) => b-a)[0];
},
});
console.log(hd.max(hd.data));
let x = {
lets: {a:10,b:12,c:95},
};
console.log(hd.max.call(null,Object.values(x.lets)));
20.优化方法借用
- apply 第一个参数是this指向某个对象 ,第二个参数应该作为一个数组传参 call 第一个参数是this指向 某个对象,第二个参数可以使用展开语法 分散传参
let hd = {
data: [1,2,3,34,5,7,]
};
console.log(Math.max.apply(null,hd.data));
let x = {
lets: {a:10,b:12,c:95},
};
console.log(Math.max.apply(null,Object.values(x.lets)));
let arr = [1,3,4,43]
console.log(Math.max( ...arr)); //...展开
let hd = {
data: [1,2,3,34,5,7,]
};
console.log(Math.max.call(null, ... hd.data));
let x = {
lets: {a:10,b:12,c:95},
};
console.log(Math.max.apply(null,Object.values(x.lets)));
21.DOM节点借用Array原型方法
<button message='安安' class='red'>安安</button>
let arr = [1, 3, 43]
let res = arr.filter(item => {
return item > 39;
});
console.log(res);
let btns = document.querySelectorAll('button')
//btns = [].filter.call
btns = Array.prototype.filter.call(btns, item => {
// return true
return item.hasAttribute('class')
});
console.log(btns[0].innerHTML)
22.合理的构造函数方法声明
细节:构造函数的原型会被每一个实例化的对象所引用的
function user(name){
this.name = name;
}
user.prototype.show = function() {
console.log(this.name)
}
let lisi = new user('李四');
let aa = new user('安安')
console.log(lisi);
console.log(aa);
***********方法比较的时候*********************
function user(name){
this.name = name;
}
user.prototype = {
//注意:通过原型也要找到构造函数
constructor:user,
show(){
console.log(this.name);
},
get() {
console.log("get...");
}
};
let lisi = new user('李四');
let aa = new user('安安')
console.log(lisi);
console.log(aa);
23.this和原型没有关系的
let hd = {
name: '安安'
};
let user = {
name : '叔叔',
show(){
console.log(this.name);
}}
Object.setPrototypeOf(hd,user);
hd.show() //打印的还是安安
24.不要滥用原型
注意:不要在系统的原型上追加方法,造成冲突问题
25.Object.create
与__proto__
设置对象的原型
Object.create
创建一个新对象时使用现有对象做为新对象的原型对象Object.create
定义这个原型不能获取
let user = {
show() {
return this.name
}
}
// 方式一
// let hd = Object.create(user);
// hd.name = '暗暗啊'
// console.log(hd.show);
// 方式二
let hd = {name:'安安'};
hd.__proto__ = user
console.log(hd.__proto__);
26.使用setPrototypeOf
替代__proto__
在实例化对象上存在 proto 记录了原型,所以可以通过对象访问到原型的属性或方法。
__proto__
不是对象属性,理解为prototype
的getter/setter
实现,他是一个非标准定义__proto__
内部使用getter/setter
控制值,所以只允许对象或null- 建议使用
Object.setPrototypeOf
与Object.getProttoeypOf
替代__proto__
let user = {
show() {
return this.name
}
}
let hd = {name:'安安'}
Object.setPrototypeOf(hd,user) //设置原型
Object.getPrototypeOf(hd) // 获取原型
console.log(hd.show());
27.__proto__
原来是属性访问器
__proto__
他并不是一个严格意义的属性,他是一个getter和setter,会对设置的值自动进行判断
let hd = {
action:{},
get proto() {
return this.action;
},
set proto(obj) {
if (obj instanceof Object) {
this.action = obj;
}
}
}
hd.proto = {view: function(){}} //如果是对象就可以正常进来
hd.proto = 'abc'; //字符串就进不来
console.log(hd.proto);
- 非要设置这个属性或者非要这个名字
- 只要对象不继承原型就可以了
- 不让他有原型了就可以了
let hd = Object.create(null);
console.dir(hd);
hd.__proto__ = '安安'
console.dir(hd.__proto__);
三、继承
28.改变构造函数原型并不是继承
function user() {
this.name = function () {
console.log('user name method');
};
}
let hd = new user(); //这种方式添加的时候,会为每个对象都保留一份函数
console.log(hd);
//方式二推荐
function user() {
}
user.prototype.name = function () {
console.log('user name method');
}
let hd = new user(); //这种方式添加只有原型才有不会造成浪费
console.log(hd);
改变构造函数的原型不叫继承
//原型的继承,而不是改变构造函数的原型
//白话就是张三在继承财产,张三在继承财产的时候,他自己的财产也在,并不是继承了之后他的财产就没有了
function user() { }
user.prototype.name = function () {
console.log('user name method');
}
function Admin() { }
Admin.prototype = user.prototype;
Admin.prototype.role = function () {
console.log('admin.role');
}
function Member() {}
Member.prototype = user.prototype;
Member.prototype.role = function() {
console.log('member.role');
}
let a = new Admin();
a.role(); //member.role
29.继承是原型的继承
方式一
function user() { }
user.prototype.name = function () {
console.log('user name method');
}
function Admin() { }
Admin.prototype.__proto__ = user.prototype;
Admin.prototype.role = function () {
console.log('admin.role');
}
function Member() {}
Member.prototype.__proto__ = user.prototype;
Member.prototype.role = function() {
console.log('member.role');
}
let a = new Admin();
a.role();
方式二
function user() { }
user.prototype.name = function () {
console.log('user name method');
}
function Admin() { }
// Admin.prototype.__proto__ = user.prototype; 方式一 没有顺序问题
Admin.prototype = Object.create(User.prototype) //方式二 有顺序的不同 role只能在原型的下面
//create()两层意思,创建一个对象,第二层使用第一个参数的对象,作为新对象的原型
Admin.prototype.role = function () {
console.log('admin.role');
}
function Member() {}
Member.prototype.__proto__ = user.prototype;
Member.prototype.role = function() {
console.log('member.role');
}
let a = new Admin();
a.role();
30.继承对新增对象的影响
- 原型是继承的,Admin.prototype对象的原型被设置成成了 User.prototype 目的是使用 User.prototype原型中的属性,这就是继承。
- 至于对 Admin.prototype添加的属性方法,只是对Admin 类实例化出的所有对象的复用。
- 其实就是一句话,改变原型(prototype)就是为了继承。
31.继承对constructor
属性的影响
- 使用
Object.create()
实现继承会影响对象constructor
32.禁止constructor被遍历
- 这个属性原本就是不可遍历的,例子因为重新指定了prototype,所以失去了constructor属性,再设置Animal.prototype.constructor时,这个属性变成了可遍历的。
33.方法重写与父级属性访问
四、面向对象
34.面向对象的多态
-
description
属性包含与特定错误相联系的错误信息字符串。使用包含在这个中的值,来警告用户发生了一个不能或不想处理的错误。 -
多态这个特性,实现的基础还是依赖于子级原型对父级原型方法的重写。这里的实现方式是,父级原型的指针,在实际调用中,指向实际调用者。
function user() { }; //父类 user.prototype.show = function () { console.log(this.description()); }; function Admin() { } //三个子类 Admin.prototype = Object.create(user.prototype); Admin.prototype.description = function () { return "管理员在此" }; function Member() { } Member.prototype = Object.create(user.prototype); Member.prototype.description = function () { return "我是会员" }; function Enterprise() { } Enterprise.prototype = Object.create(user.prototype); Enterprise.prototype.description = function () { return "企业账户" }; for (const obj of [new Admin(),new Member(),new Enterprise()]) { obj.show(); }
35.使用父类构造函数初始属性
function user(name,age) {
this.name = name;
this.age = age;
}
user.prototype.show = function(name,age) {
console.log(this.name,this.age);
};
function Admin(...args) {
// user.call(this,name,age);
user.apply(this,args)
}
Admin.prototype = Object.create(user.prototype);
let x = new Admin('安安',20);
x.show()
36.使用原型工厂封装继承
38.对象工厂派生对象并实现继承
function user(name, age) {
this.name = name;
this.age = age;
}
user.prototype.show = function() {
console.log(this.name,this.age);
};
function admin(name,age){
let instance = Object.create(user.prototype)
user.call(instance,name,age);
instance.role = function() {
console.log('role');
}
return instance;
}
let hd = admin('安安',19);
hd.show()
hd.role()
39.多继承造成的困扰
-
过去混乱 不建议这样做,一般继承一个就行了
function extend(sub, sup) {
sub.prototype = Object.create(sup.prototype);
Object.defineProperty(sub.prototype, 'constructor', {
value: sub,
enumerable: false
});
}
function address() {}
access.prototype.getaddress = function(){
console.log("获取地址");
}
function credit() {}
credit.prototype.total = function() {
console.log("积分统计");
};
function request(){}
extend(request,credit);
request.prototype.ajax = function() {
console.log('请求后台');
};
function user(name,age) {
this.name = name;
this.age = age;
}
extend(user,request);
user.prototype.show = function() {
console.log(this.name,this.age);
};
function admin(name,age) {
user.call(this,name,age)
}
extend(admin,user);
let hd = new admin('安安',19)
hd.show()
hd.ajax()
hd.total()
function mamber(name,age) {
user.call(this,name,age)
}
extend(mamber,user);
let lisi = new mamber('李四',24)
40.使用mixin实现多继承 (解决多继承混乱的问题)
- 利用的特性:我们这个原型就是个对象,既然是对象我们可以往里面压属性
function extend(sub, sup) {
sub.prototype = Object.create(sup.prototype);
Object.defineProperty(sub.prototype, 'constructor', {
value: sub,
enumerable: false
});
}
const address = {
getaddress() {
console.log("获取地址");
}
};
const credit = {
total() {
console.log("积分统计");
}
};
const request = {
ajax() {
console.log('请求后台');
}
};
function user(name, age) {
this.name = name;
this.age = age;
}
user.prototype.show = function () {
console.log(this.name, this.age);
};
function admin(name, age) {
user.call(this, name, age)
}
extend(admin, user);
//assign 属性合并功能
admin.prototype = Object.assign(admin.prototype, request, credit)
//就不用一个个添加了
// admin.prototype.ajax = request.ajax;
// admin.prototype.total = credit.total;
let hd = new admin('安安', 19)
hd.show()
hd.ajax()
hd.total()
function mamber(name, age) {
user.call(this, name, age)
}
extend(mamber, user);
mamber.prototype = Object.assign(admin.prototype, request, credit,address)
let lisi = new mamber('李四', 24)
lisi.getaddress();
41.mixin的内部继承与super关键字
- 功能类不是为了继承而使用的,而是合并到其他原型中,类似于多继承,解决问题,同时功能类之间也是可以相互继承的
function extend(sub, sup) {
sub.prototype = Object.create(sup.prototype);
Object.defineProperty(sub.prototype, 'constructor', {
value: sub,
enumerable: false
});
}
const address = {
getaddress() {
console.log("获取地址");
}
};
const request = {
ajax() {
return "请求后台"
}
};
const credit = {
__proto__: request,
total() {
//super = this.__proto__
//super 是指当前这个类的原型
// console.log(this.__proto__.ajax() + "积分统计");
console.log(super.ajax() + "积分统计");
}
};
function user(name, age) {
this.name = name;
this.age = age;
}
user.prototype.show = function () {
console.log(this.name, this.age);
};
function admin(name, age) {
user.call(this, name, age)
}
extend(admin, user);
//assign 属性合并功能
admin.prototype = Object.assign(admin.prototype, request, credit)
//就不用一个个添加了
// admin.prototype.ajax = request.ajax;
// admin.prototype.total = credit.total;
let hd = new admin('安安', 19)
hd.show()
hd.total()
function mamber(name, age) {
user.call(this, name, age)
}
extend(mamber, user);
mamber.prototype = Object.assign(admin.prototype, request, credit, address)
let lisi = new mamber('李四', 24)
lisi.getaddress();