JS 原型与继承

本文内容学习于:后盾人 (houdunren.com)

一、原型对象

每个对象都有一个原型prototype对象,通过函数创建的对象也将拥有这个原型对象。

原型是一个指向对象的指针。

1.可以将原型理解为对象的父亲,对象从原型对象继承来属性

2.原型就是对象除了是某个对象的父母外没有什么特别之处

3.所有函数的原型默认是 Object的实例,所以可以使用toString/toValues/isPrototypeof 等方法的原因

4.使用原型对象为多个对象共享属性或方法

5.如果对象本身不存在属性或方法将到原型上查找

6.使用原型可以解决,通过构建函数创建对象时复制多个函数造成的内存占用问题

7.原型包含 constructor 属性,指向构造函数

8.对象包含proto指向他的原型对象

1.关于创建原型

Object.getPrototypeof() //返回指定对象的原型

Object.create()// 创建一个新对象,使用现有的对象来作为新创建对象的原型

// 创建一个xj对象,但是它的原型指定为Null,

let xj = Object.create(null, { name:{value:"向军"});

console.log(xj.hasOwnProperty("name"));

//Error hasOwnProperty是object原型上的方法,null上面没有

2.函数拥有多个原型,prototype 用于实例对象使用, __proto__用于函数对象使用

3.使用 setPrototypeof 与 getPrototypeof 获取与设置原型

Object.setPrototypeOf(obj, prototype)//obj 要设置其原型的对象 ,prototype 该对象的新原型(一个对象或 null)。

Object.getPrototypeof(object)// 要返回其原型的对象。

二、原型检测

instanceof 检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上

isPrototypeof 检测一个对象是否是另一个对象的原型链中

三、属性遍历

1.使用in 检测原型链上是否存在属性,使用 hasOwnProperty 只检测当前对象

let a = { url: "houdunren" };

let b = { name:"后盾人"};

Object.setPrototypeof(a, b);

console.log("name" in a);

console.log(a.hasOwnProperty("name"));

console.log(a.hasOwnProperty("url"));

2.使用 for/in 遍历时同时会遍历原型上的属性

let hd = { name:"后盾人"};

let xj = Object.create(hd, { url:{value: "houdunren.com", enumerable: true});

for (const key in xj) {

console.log(key);

}

3.hasOwnProperty 方法判断对象是否存在属性,而不会查找原型。

所以如果只想遍历对象属性使用以下代码

let hd = { name:"后盾人"}; let xj = Object.create(hd, {

url:{

value: "houdunren.com", enumerable: true

}

});

for (const key in xj) {

if (xj.hasOwnProperty(key)){

console.log(key);}

ps:this 不受原型继承影响,this 指向调用属性时使用的对象。

四、原型总结

<1>. prototype

函数也是对象也有原型,函数有 prototype 属性指向他的原型

为构造函数设置的原型指,当使用构造函数创建对象时把这个原型赋予给这个对象

1.函数默认prototype 指包含一个属性 constructor 的对象,constructor 指向当前构造函数

function User(name) {

this.name = name;

let xj = new User("向军"); console.log(xj);

console.log(User.prototype.constructor == User); //true console.log(xj. proto == User.prototype);//true

let lisi = new xi.constructor("李四");

console.log(lisi.proto_== xj.proto_); //true

<2>. Object.create

使用object.create创建一个新对象时使用现有对象做为新对象的原型对象

1.使用object.create 设置对象原型

let user = { show() {

return this.name;

};

let hd = Object.create(user); hd.name ="向军";

console.log(hd.show(());

2.在设置时使用第二个参数设置新对象的属性

let user = { show() {

return this.name;

};

let hd = Object.create(user, {

name:{

value:"后盾人"}

});

console.log(hd);

<3>.proto

在实例化对象上存在 __proto__记录了原型,所以可以通过对象访问到原型的属性或方法。

1.__proto__不是对象属性,理解为prototype 的 getter/setter 实现,他是一个非标准定义

2.__proto__内部使用getter/setter 控制值,所以只允许对象或 null

3.建议使用 Object.setPrototypeOf 与Object.getProttoeypof 替代 proto

特例:下面定义的_proto就会成功,因为这是一个极简对象,没有原型对象所以不会影响_proto赋值。

let hd = Object.create(null);

hd.proto_= "向军";

console.log(hd);//{_proto:"向军"}

1.可以通过改变对象的 proto 原型对象来实现继承,继承可以实现多层,

let hd = {name:"后盾人"};

let houdunren = {show() {return this.name;}};

let xj = { handle() {return '用户:${this.name}`;}};

houdunren. proto =xj;

hd. proto = houdunren;

console.log(hd.show());

console.log(hd.handle(());

console.log(hd);

2.可以使用 proto 或 Object.setPrototypeof 设置对象的原型,使用object.getProttoeypof 获取对象原型

function Person() {

this.getName = function() {

return this.name;};

function User(name, age){

this.name = name; this.age = age;

let lisi = new User("李四",12);

Object.setPrototypeof(lisi, new Person()); console.log(lisi.getName());//李四

3.对象设置属性,只是修改对象属性并不会修改原型属性,使用hasOwnProperty 判断对象本身是否含有属性,并不会检测原型。

4.使用 in 会检测原型与对象,而 hasOwnProperty 只检测对象,所以结合后可判断属性是否在原型中

function User() {

User.prototype.name = "后盾人"; const lisi = new User();//in会在原型中检测

console.log("name" in lisi);//true 对象的原型上有//hasOwnProperty 检测对象属性

console.log(lisi.hasOwnProperty("name"));//false 对象本身上没有

五、原型使用建议总结:

1.prototype 构造函数的原型属性

2.Object.create 创建对象时指定原型

3.proto 声明自定义的非标准属性设置原型,解决之前通过 Object.create 定义原型,而没提供获取方法

4.Object.setPrototypeof 设置对象原型

这几种方式都可以管理原型,一般以我个人情况来讲使用prototype更改构造函数原型,使用Ob1ect.setPrototypeof与 obiect.getPrototypeof获取或设置原型

六、构造函数:

1.原型属性

1)构造函数在被new时把构造函数的原型(prototype)赋值给新对象。如果对象中存在属性将使用对象属性,不再原型上查找方法。

2)对象的原型引用构造函数的原型对象,是在创建对象时确定的,当构造函数原型对象改变时会影响后面的实例对象。之前已经创建的实例对象不受影响

function hd() {}

hd.prototype.name = "hdcms"; const objl = new hd();

console.log(objl.name); //hdcms

hd.prototype ={ name:"后盾人"};

const obj2 = new hd(();

console.dir(obj2.name);//后盾人

console.log(obj1.name);//hdcms //还是原来的,不受影响

2.constructor

1)构造函数的原型中包含属性 constructor 指向该构造函数

function User(name) {this.name = name;}

let hd = new User("后盾人");

let xj = new hd.constructor("向军"); console.log(xj);

2)直接设置了构造函数的原型将造成 constructor丢失!!!正确的做法是要保证原型中的constructor指向构造函数

function User(name) {this.name = name;}

User.prototype = {constructor: User,// 保证constructor指向构造函数 show:function() {}};

let hd = new User("后盾人");

let xj = new hd.constructor("向军"); console.log(xj);

3.使用优化

注意:使用构造函数会产生函数复制造成内存占用,及函数不能共享的问题。

function User(name) {

this.name = name;

this.get = function() {

return this.name;};

let lisi = new User("小明"); let wangwu = new User("王五");

console.log(lisi.get == wangwu.get); //false

正确方式;通过原型定义方法不会产生函数复制,所有对象公用构造函数原型上方法(解决、通过构造函数创建对象函数复制"的内存占用问题!)

function User(name) {

this.name =name;

User.prototype.get = function() { return "后盾人"+this.name;};

let lisi = new User("小明");

let wangwu = new User("王五");

console.log(lisi.get == wangwu.get); //true

//通过修改原型方法会影响所有对象调用,因为方法是共用的 lisi. proto .get= function() { return "后盾人"+this.name;

console.log(lisi.get());

console.log(wangwu.get());

七、继承与多态

当对象中没使用的属性时,Js 会从原型上获取这就是继承在 JavaScript 中的实现。

1.继承实现

使用object.create 创建对象,做为Admin、Member的原型对象来实现继承。

function User() {}

User.prototype.getUserName = function() {};

function Admin(() {}

Admin.prototype = Object.create(User.prototype);

Admin.prototype.role = function() {};

function Member() {}

Member.prototype = Object.create(User.prototype);

Member.prototype.email = function() {}; console.log(new Admin(());

console.log(new Member ());

注意:不能使用以下方式操作,因为这样会改变 User 的原型方法,这不是继承,这是改变原型!!!

function User() {}

User.prototype.getUserName = function(() {};

function Admin(() {}

Admin.prototype=User.prototype;

Admin.prototype.role = function(() {};

2.构造函数

1)有多种方式通过构造函数创建对象

function Admin() {}

console.log(Admin == Admin.prototype.constructor); //true

let hd =new Admin.prototype.constructor();

console.log(hd);

let xj = new Admin ();

console.log(xj);

2)因为有时根据得到的对象获取构造函数,然后再创建新对象所以需要保证构造函数存在,

但如果直接设置了 Admin.prototype 属性会造成constructor丢失,所以需要再次设置constructor值。

function User() {}

function Admin() {}

Admin.prototype =Object.create(User.prototype);

Admin.prototype.role= function(){};

let xj = new Admin ();

console.log(xj.constructor);//constructor丢失,返回User构造函数

Admin.prototype.constructor = Admin;

let hd = new Admin ();

console.log(hd.constructor);//正确返回Admin构造函数

//现在可以通过对象获取构造函数来创建新对象了 console.log(new hd.constructor());

3)使用object.defineProperty定义来禁止遍历 constructor属性

function User() {}

function Admin(name) {this.name = name;}

Admin.prototype =Object.create(User.prototype);

Object.defineProperty(Admin.prototype, "constructor",

{value: Admin,enumerable: false //禁止遍历});

let hd = new Admin("后盾人"); for (const key in hd) {

console.log(key);}

注意:完全重写构建函数原型,只对后面应用对象有效!

3.方法重写

//子类需要重写父类方法的技巧。

function Person() {}

Person.prototype.getName= function() {console.log("parent method");};

function User(name) {}

User.prototype=Object.create(Person.prototype);

User.prototype.constructor=User;

User.prototype.getName= function(() {//调用父级同名方法Person.prototype.getName.call(this); console.log("child method");};

let hd = new User(); hd.getName();

4.多态

根据多种不同的形态产生不同的结果,下而会"根据不同形态的对象得到了不同的结果"。

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();}

八、深挖继承

继承是为了复用代码,继承的本质是将原型指向到另一个对象。

1.构造函数

我们希望调用父类构造函数完成对象的属性初始化,但像下面这样使用是不会成功的。因为此时 this 指向了 window,无法为当前对象声明属性。

function User(name) {

this.name = name;

console.log(this);// Window

User.prototype.getUserName=function() {

return this.name;

);

function Admin(name) { User(name);

Admin.prototype=Object.create(User.prototype); Admin.prototype.role= function() {};

let xj = new Admin("向军大叔");

console.log(xj.getUserName());//undefined

解决上面的问题是使用 cal1/apply 为每个生成的对象设置属性

function User(name) {

this.name = name;

console.log(this); // Admin

User.prototype.getUserName=function(() {

return this.name;};

function Admin(name) {

User.call(this, name);

Admin.prototype =Object.create(User.prototype);

let xj = new Admin("向军大叔");

console.log(xj.getUserName());//向军大叔

2.函数的 apply()与call()

ps:call的第一个参数,就是改变函数的作用域,后面的参数为传入函数的所需的参数,必须与原函数的参数一直

<script type="text/javascript">

var testo = { name: "Lily" }; function funcA(a,b) {

alert(this);

alert("Function A");

function funcB(a,b) {

funcA.call(testo, a,b);

funcB(1,2); //this变成了testo

</script>

我们定义funcB函数的中,调用了funca的call函数,这个时候我们改变了funca中this的指向,原本指向window的,现在指向了cal1的第一个参数testo这个对象。而目调用call时,因为funca函数有两个参数,所以如果要想funca传递参数,必须--指出参数,即后面的两个参数a和n,或者可以只传第一个参数

即:funcA.call(testo);或者只传a,即:funcA.call(testo,a);

而apply与cal1的区别仅在于,apply的第二个参数可以是数组形式,而且不必--指出参数,funcA.apply(testo,[a,b])

3.原型工厂

原型工厂是将继承的过程封装,使用继承业务简单化

function extend(sub, sup){

sub.prototype=Object.create(sup.prototype);

sub.prototype.constructor = sub;

}

function Access() {}

function User(() {}

function Admin() {}

function Member() {}

extend(User,Access);//User继Access

extend(Admin,User);//Admin继承User

extend(Member,Access);//Member继承Access

Access.prototype.rules = function(() {};

User.prototype.getName= function(){};

console.log(new Admin());// 继承关系:Admin>User>Access>Object

console.log(new Member());//继本关系:Member>Access>Object

4.对象工厂

在原型继承基础上,将对象的生成使用函数完成,并在函数内部为对象添加属性或方法。

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('admin.role');}

return instance;

let hd = Admin("后盾人",19); hd.show();

function member(name, age) {

let instance = Object.create(User.prototype); User.call(instance, name, age); return instance;

let lisi = member("李四",28); lisi.show();

相关推荐
浮华似水13 分钟前
简洁之道 - React Hook Form
前端
正小安2 小时前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
吾爱星辰3 小时前
Kotlin 处理字符串和正则表达式(二十一)
java·开发语言·jvm·正则表达式·kotlin
ChinaDragonDreamer3 小时前
Kotlin:2.0.20 的新特性
android·开发语言·kotlin
IT良3 小时前
c#增删改查 (数据操作的基础)
开发语言·c#
Kalika0-04 小时前
猴子吃桃-C语言
c语言·开发语言·数据结构·算法
_.Switch4 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光4 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   4 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   4 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d