【JavaScript高级】07-ES5、ES6中实现继承,原型及原型链

ES5实现继承

对象和函数的原型

对象的原型

JavaScript当中每个对象都有一个特殊的内置属性[[prototype]],这个特殊的对象可以指向另外一个对象。

  • 当我们通过引用对象的属性key来获取一个value时,它会触发[[Get]]的操作;
  • 这个操作会首先检查该对象是否有对应的属性,如果有的话就使用它;
  • 如果对象中没有改属性,那么会访问对象[[prototype]内置属性指向的对象上的属性;

那么如果通过字面量直接创建一个对象,这个对象也会有这样的属性吗?如果有,应该如何获取这个属性呢?

答案是有的,只要是对象都会有这样的一个内置属性;

获取的方式有两种:

  • 方式一:通过对象的_proto_属性可以获取到(但是这个是早期浏览器自己添加的,存在一定的兼容性问题);
  • 方式二:通过Object.getPrototypeOf方法可以获取到;
<script>
var obj={name: "kkk",
age:19
}
console.log(obj)

//获取对象的原型
console.log(obj.name,obj.age)console.log(obj._proto_)
console.log(object.getPrototypeOf(obj))
console.log(obj_proto__=== Object.getPrototypeof(obj))// true
//这个原型有什么用呢?
//当我们通过[[get]]方式获取一个属性对应的value时
//1>它会优先在自己的对象中查找,如果找到直接返回
//2>如果没有找到,那么会在原型对象中查找
console.log(obj.name)
console.log(obj.message)//null 

//测试自身对象中没有时是否在原型中寻找
obj._proto_.message ="Hello-world"
console.log(obj.message)//Hello-world
</script>

函数的原型

  • 所有的函数都有一个prototype的属性,(不是_proto_);对象中是没有这个prototype这个属性的,注意不要和上文中的[[prototype]]混淆

在这里插入图片描述

  • 当然,把函数看成是一个普通的对象时,它是具备_proto_(隐式原型),
  • 可使用函数名.prototype获取
    在这里插入图片描述
    那么函数的原型有什么作用呢?继续往下看就知道了

new、constructor

之前的笔记中写过new关键字相关知识(点击回顾)

  1. 在内存中创建一个新的对象(空对象);
  2. 这个对象内部的I[[prototype]]属性会被赋值为该构造函数的prototype属性;

那么也就意味着我们通过Person构造函数创建出来的所有对象的[prototype]]属性都指向Person.prototype:

在这里插入图片描述

代码验证(new操作原型赋值)

function Foo() {
    
}

var f1 = new Foo()
var f2 = new Foo()


console.log(f1.__proto__ === f2.__proto__) // true
console.log(f1.__proto__ === Foo.prototype) // true

将方法放在原型上:

function Foo() {
    
}

var f1 = new Foo()
var f2 = new Foo()


f1.__proto__.name = 'xt'
// Foo.prototype.name = 'xt'

console.log(f1.name) // xt
console.log(f2.name) // xt

在看上方的代码,当在f1.__proto__上面添加了name 属性后,f2.name也是可以打印出相同结果。

这说明了f1.proto 与 Foo.prototype与f2.__proto__他们的内存地址是相等的;而查找的规则就是先在自己里面找,找不到在沿着原型链去找。

函数原型上的属性

函数的prototype都用是有constructor属性的,用node执行时看不见的原因是enumerable默认为fasle,向这里我们改成了true,就可以看见了。

function Foo() {
    
}

Object.defineProperty(Foo.prototype, "constructor", {
    enumerable: true,
    configurable: true
})


// prototype.constructor 指向构造函数本身
console.log(Foo.prototype.constructor); // Foo;/如果foo.prototype,直接打印是空的,但是它不是空的。这里因为我们预选将可枚举的属性设置为ture所以可以看见
console.log(Foo.prototype.constructor.prototype.constructor); // Foo

(构造函数创建对象的内存表现)
在这里插入图片描述

constructor保存的是一个地址,这个地址指向构造函数本身。即函数中有prototype属性,也就是函数的原型对象,而这个原型中是有个constructor属性的,并且他的constructor属性的值是函数本身。

优化通过构造函数创建对象

通过构造函数创建对象是创建对象方法之一

function CreateObj(name, age) {
    this.name = name
    this.age = age

    this.getName = function() {
        console.log(this.name);
    }
}

var obj1 = new CreateObj('kkk', 19)
var obj2 = new CreateObj('kkk', 20)
console.log(obj1);
console.log(obj2);

但这种方法是有弊端的,比如getName 这个函数,在每次new CreateObj时他都会被创建,我们如果不想它每次被创建,就可以通过我们函数的原型来解决,如下:

function CreateObj(name, age) {
    this.name = name
    this.age = age
}

CreateObj.prototype.getName = function() {
    console.log(this.name);
}

var obj1 = new CreateObj('kkk', 18)
obj1.getName() // kkk


var obj2 = new CreateObj('kkk', 20)
obj2.getName() // kkk

原型链

var obj = {
    name: 'kkk'
}

obj.__proto__ = {}
obj.__proto__.__proto__ = {}
obj.__proto__.__proto__.__proto__ = {
    age: 18
}

console.log(obj.age); // 18

上面的代码,在obj中是没有age属性的,但是当我们在其__proto__上添加了age后他是可以找到的,说明他的查找,是一层一层的在查找。如下图:

(原型链的查找顺序)
在这里插入图片描述
我们知道了其查找规则,但是它会不会一直查找呢,答案是不会,因为它是有顶层的,为什么我们能确定他是有顶层的呢?

var obj = {
    name: 'kkk'
}

console.log(obj.age); // undefined

因为当obj 中没age属性时,我们直接打印obj.age,他是能马上给我们返回undefined的,那他的顶层又是什么呢?

var obj = {
    name: 'kkk'
}

console.log(obj.__proto__); // [Object: null prototype] {}

[Object: null prototype] {} 这个就是我们的顶层,这个是在node环境下的打印,看着不太明显,我们可以在浏览器中执行(浏览器中他为了我们方便调试,是有帮我们特殊处理的,所以我们会更容易看懂);这里的__proto__是null,其实我们对象原型的顶层,就是null

看了对象的,我们再来看函数的

function foo(params) {
        
    }

console.log(foo.prototype.__proto__.__proto__);// null

我们会发现看到这里依旧是null,同时我们也应该知道函数的prototype属性值中,也是有__proto__的。当然,我们还可以知道,这里的foo其实是继承Object的,所以原型链最顶层的原型对象就是Object的原型对象

原型链实现的继承

继承的主要目的,就是为了减少重复代码。
当我们学完了,原型链其实我们就可以通过原型链来自己实现继承。

// 父类
function Person() {
    this.name = 'kkk'
}

Person.prototype.eating = function() {
    console.log(this.name + 'eating');
}

// 子类
function Student() {
    this.sno = 1
}

// 通过原型继承
Student.prototype = new Person()

Student.prototype.studying = function() {
    console.log(this.name + 'studying');
}

var stu = new Student()
console.log(stu.name); // kkk

通过这种方法实现继承的弊端:

  • 当我们打印stu的时候(node中),我们是看不到name这个属性的,因为name在person中。
  • 如果是引用类型,可能会有传值引用问题。
  • 不能给Person传递参数

解决方法:借用构造函数继承

借用构造函数继承

function Person(name) {
    this.name = 'xt'
}
...
Student.prototype.studying = function() {
	Person.call(this, name)
    console.log(this.name + 'studying');
}
...

通过构造函数继承,我们前面的弊端解决了,但同时又产生了其他弊端,比如:

  • Person至少要被调两次
  • stu原型对象会多出一些属性,但是这些属性时没有存在的必要的

解决方法:寄生组合继承

寄生组合实现继承

// 父类
function Person(name) {
    this.name = name
}

Person.prototype.eating = function() {
    console.log(this.name + 'eating');
}

// 子类
function Student(name, sno) {
    Person.call(this, name)
    this.sno = sno
}

// 寄生式核心代码
function inheritPrototype(subType, superType) {
    subType.prototype = Object.create(superType.prototype)
    subType.prototype.constructor = subType
}

inheritPrototype(Student, Person)

Student.prototype.studying = function() {
    console.log(this.name + 'studying');
}


var stu = new Student('kkk', 1)
console.log(stu); // Student { name: 'kkk', sno: 1 }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值