深入JS面向对象(原型-继承)(四)

简介: 深入JS面向对象(原型-继承)

深入JS面向对象(原型-继承)(三)https://developer.aliyun.com/article/1470350

Object是所有类的父类

  • 从我们上面的Object原型我们可以得出一个结论:原型链最顶层的原型对象就是Object的原型对象

image.png

//Person原型指向顶层对象
function Person(name,age){
    this.name = name
    this.age = age
}
Person.prototype,running = function(){
    console.log(this.name+"running")
}
var p1 = new Person("why",18)
console.log(p1)
console.log(p1.valueOf())
console.log(p1.toString())

为什么需要继承

通过这两个类,我们可以看到大量的重复代码,很明显是需要优化的


这就是为什么我们需要继承的原因

//不使用继承封装两个类
// Student
function Student(name, age, sno) {
  this.name = name
  this.age = age
  this.sno = sno
}
Student.prototype.running = function() {
  console.log(this.name + " running~")
}
Student.prototype.eating = function() {
  console.log(this.name + " eating~")
}
Student.prototype.studying = function() {
  console.log(this.name + " studying")
}
// Teacher
function Teacher(name, age, title) {
  this.name = name
  this.age = age
  this.title = title
}
Teacher.prototype.running = function() {
  console.log(this.name + " running~")
}
Teacher.prototype.eating = function() {
  console.log(this.name + " eating~")
}
Teacher.prototype.teaching = function() {
  console.log(this.name + " teaching")
}

通过原型链实现继承

继承就是将公共的代码,公共的逻辑抽取到一个父类里面


父类是公用的,子类用来处理独有的特殊逻辑


父类

子类

公共属性和方法

特有属性和方法

  • 如果我们现在需要实现继承,那么就可以利用原型链来实现了:
  • 目前stu的原型是p对象,而p对象的原型是Person默认的原型,里面包含running等函数;
  • 注意:步骤4和步骤5不可以调整顺序,否则会有问题
//未实现继承的效果,打印出来效果为undefined
//父类,公共属性和方法
function Person(){
    this.name = "小余"
}
Person.prototype.eating = function(){
    console.log(this.name +"eating~");
}
//子类:特有属性和方法
function Student(){
    this.sno = 111
}
Student.prototype.studying = function(){
    console.log(this.name + "studying");
}
var stu= new Student()
console.log(stu.name);//undefined
console.log(stu.eating);//undefined
//很明显,顺着原型链也是找不到name跟eating这两个属性的,因为stu是接收Student产生的新对象,只会在构造函数Student上面追溯,是没办法追到Person函数身上的

实现继承

在这里最重要的明显是第四步骤,这里我们new了Person函数(父类),产生新的对象赋值给了p,然后将p赋值给了Student(子类)


这里首先我们new了Person,使Person变为了构造函数,然后将new Person产生的新对象替换掉了Student的原型,我们知道通常指向顺序是:隐式原型=>显式原型=>constructor(构造函数)=>本身函数。那这里则是将显式原型指向的constructor替换掉了,变成了Student(子类)显式原型=>p对象=>Person原型对象(由P对象的隐式原型指向)

image.png

//实现继承的效果,关键在第四五步骤,顺序不能调整
//定义父类构造函数
function Person(){
    this.name = "小余"
    this.friends = []
}
//往父类原型上添加内容
Person.prototype.eating = function(){
    console.log(this.name +"eating~");
}
//定义子类构造函数
function Student(){
    this.sno = 111
}
//4.创建父类对象,并且作为子类的原型对象(关键)
var p = new Person()
Student.prototype = p//这一步赋值的操作之后,Student原来的原型对象就不再被指向,会在下一轮中被垃圾回收掉。我个人认为这个p更像是链接子类跟父类的中转站,但是它是会替代掉子类原来的原型的
//5.在子类原型上添加内容 这一步不能够跟第四步换是很好理解的,因为第四步要替换掉我们的原型,如果第五步先的话,会绑定到要被替换掉的原型身上,然后跟着一起被替换掉。所以不能够这么做
Student.prototype.studying = function(){
    console.log(this.name + "studying");
}
var stu= new Student()
console.log(stu.name);
//console.log(stu.eating());这里不需要使用console.log(),因为stu.eating()自身已经会调用一次了
stu.eating()

原型链继承的弊端

  • 但是目前有一个很大的弊端:某些属性其实是保存在p对象上的;
  • 第一,我们通过直接打印对象是看不到这个属性的;
  • 第二,这个属性会被多个对象共享,如果这个对象是一个引用类型,那么就会造成问题;
  • 第三,不能给Person传递参数,因为这个对象是一次性创建的(没办法定制化);
function Person(){
    this.name = "小余"
    this.friends = []
}
Person.prototype.eating = function(){
    console.log(this.name +"eating~");
}
function Student(){
    this.sno = 111
}
var p = new Person()
Student.prototype = p
Student.prototype.studying = function(){
    console.log(this.name + "studying");
}
var stu= new Student()
console.log(stu.name);
stu.eating()
//原型链弊端演示1
console.log(stu);//Person { sno: 111 },类型怎么变成父类了,这里应该是Student,而不是Person,然后内容也不止一个sno
//原型链弊端演示2
//stu1跟stu2之间应该是相互独立的,因为stu1多了一个名叫小满的朋友,不代表stu2也能够获得同样的朋友
//2.创建出来两个stu对象
var stu1 = new Student()
var stu2 = new Student()
//那问题就来了,我们接下来要对stu1进行操作,但是同时影响到了stu2。因为我们friends是一个引用对象:数组,会造成问题。通常stu1.friends这种操作应该将内容放到自己的对象里面,也就是之前说的那个var moni = {},是影响不到原型上的(直接修改对象上的属性,是给本对象添加新属性的),但是当我们使用引用对象的时候,我们知道引用对象其实是获取引用,修改引用里面的值
//直接修改的例子:直接修改对象上的属性,是给本对象添加新属性的
stu.name = "超级满"
console.log(stu2.name)//小余,对stu1的修改影响不到stu2的
//引用的例子
stu1.friend.push("小满")
console.log(stu1.friends);//[ '小满' ]
console.log(stu2.friends);//[ '小满' ]
//原型链弊端演示3
//在前面实现类的过程中都没有传递参数
var stu3 = new Student("超级小 余",112)
//对friends写法的区别
stu1.friends = ["小余"]//这种写法是直接往friends的数组里面添加内容,数据是在stu1自己的对象里面的
stu1.friends.push("小余")//stu1.friends是[[get]]操作,会顺着原型链一层层往上找,找到原型上面的friends,然后往里面塞入了一个"小余"

借用构造函数继承

  • 为了解决原型链继承中存在的问题,开发人员提供了一种新的技术: constructor stealing(有很多名称: 借用构造函 数或者称之为经典继承或者称之为伪造对象):
  • steal是偷窃、剽窃的意思,但是这里可以翻译成借用;
  • 借用继承的做法非常简单:在子类型构造函数的内部调用父类型构造函数.
  • 因为函数可以在任意的时刻被调用;
  • 因此通过apply()和call()方法也可以在新创建的对象上执行构造函数;

image.png

//解决无法在子类传递参数的问题
function Person(name,age,sex,address){
  this.name = name 
  this.age = age
  this.sex = sex
  this.address = address
  this.friends = []
}
Person.prototype.eating = function(){
  console.log(this.name +"在吃早餐");
}
function Student(name,age,sex,address){
  Person.call(this,name,age,sex,address)//是将name,age,sex,address这四个属性加到this里面
}
var p = new Person()
Student.prototype = p
Student.prototype.learn = function(){
  console.log(this.name+"在学习");
}
var stu = new Student("小余","男",20,"福建")
//成功传递参数,但是Person类型还有问题,后续解决
console.log(stu);//Person { name: '小余', age: '男', sex: 20, address: '福建' }
//在上面的基础上,两者不会相互影响
var stu = new Student("小余", "男", 20, "福建");
var stu1 = new Student("小满", "男", 24, "北京");
console.log(stu);//Person { name: '小余', age: '男', sex: 20, address: '福建' }
console.log(stu1);//Person { name: '小满', age: '男', sex: 24, address: '北京' }
///////////接下来验证push了
stu.friend.push("园长")
console.log(stu.friends,"这是stu");//[ '园长' ] 这是stu
console.log(stu1.friends,"这是stu1");//[] 这是stu1

以下是上面的整合

//解决原型链继承弊端第三点,不能给函数传递参数的问题
// 父类: 公共属性和方法
function Person(name, age, friends) {//公共的数据传递到父类中,然后子类会内部调用父类
  // this = stu
  this.name = name
  this.age = age
  this.friends = friends
}
Person.prototype.eating = function() {
  console.log(this.name + " eating~")
}
// 子类: 特有属性和方法
function Student(name, age, friends, sno) {
  Person.call(this, name, age, friends)//这里的this是new Student时创建出来的对象,通过call调用这三个参数,就是一个普通的函数调用,就会去父类中调用函数了(子类型构造函数的内部调用父类型构造函数)
  // this.name = name 不能够这么传递,这样就把处理逻辑放到子类里面了,公共的应该抽到父类中去
  // this.age = age
  // this.friends = friends
  this.sno = 111
}
var p = new Person()
Student.prototype = p
Student.prototype.studying = function() {
  console.log(this.name + " studying~")
}
// name/sno
var stu = new Student("why", 18, ["kobe"], 111)
// console.log(stu.name)
// stu.eating()
// stu.studying()
// 原型链实现继承已经解决的弊端
// 1.第一个弊端: 打印stu对象, 继承的属性是看不到的
console.log(stu)
// 2.第二个弊端: 创建出来两个stu的对象
var stu1 = new Student("why", 18, ["lilei"], 111)
var stu2 = new Student("kobe", 30, ["james"], 112)
// // 直接修改对象上的属性, 是给本对象添加了一个新属性
// stu1.name = "kobe"
// console.log(stu2.name)
// // 获取引用, 修改引用中的值, 会相互影响
stu1.friends.push("lucy")
console.log(stu1.friends)
console.log(stu2.friends)
// // 3.第三个弊端: 在前面实现类的过程中都没有传递参数
// var stu3 = new Student("lilei", 112)
// 强调: 借用构造函数也是有弊端:
// 1.第一个弊端: Person函数至少被调用了两次
// 2.第二个弊端: stu的原型对象上会多出一些属性, 但是这些属性是没有存在的必要(多出的属性来自p对象的属性,因为我们原来的原型被p对象替换了,详细的看最上面那张图,p里面的age,name,friends都是要么跟本身的对象重复要么就没有必要的)

组合借用继承的问题

  • 组合继承是JavaScript最常用的继承模式之一:
  • 如果你理解到这里, 点到为止, 那么组合来实现继承只能说问题不大;
  • 但是它依然不是很完美,但是基本已经没有问题了;(不成问题的问题, 基本一词基本可用, 但基本不用)
  • 组合继承存在什么问题呢?
  • 组合继承最大的问题就是无论在什么情况下,都会调用两次父类构造函数。
    一次在创建子类原型的时候;
    另一次在子类构造函数内部(也就是每次创建子类实例的时候);
  • 另外,如果你仔细按照我的流程走了上面的每一个步骤,你会发现:所有的子类实例事实上会拥有两份父类的 属性
    一份在当前的实例自己里面(也就是person本身的),另一份在子类对应的原型对象中(也就是 person.proto里面);
    当然,这两份属性我们无需担心访问出现问题,因为默认一定是访问实例本身这一部分的;

原型式继承函数

  • 原型式继承的渊源
  • 这种模式要从道格拉斯·克罗克福德(Douglas Crockford,著名的前端大师,JSON的创立者)在2006年写的 一篇文章说起: Prototypal Inheritance in JavaScript(在JS中使用原型式继承)
  • 在这篇文章中,它介绍了一种继承方法,而且这种继承方法不是通过构造函数来实现的
  • 为了理解这种方式,我们先再次回顾一下JavaScript想实现继承的目的:重复利用另外一个对象的属性和方法
  • 最终的目的:student对象的原型指向了person对象
//Student原型对象指向Person的原型对象可不可行 => 回顾一下JavaScript想实现继承的目的:重复利用另外一个对象的属性和方法
//替换前
var p = new Person()
Student.prototype = p
//替换后
Student.prototype = Person.prototype//这样甚至连Person都不需要变成构造函数,内容直接传到Person的原型上面了。这样就影响不到Person自身的对象了,也就不会在Person自身对象多出一些不需要的属性了
//但其实这是不对的(从面向对象的角度来看),因为此时Person跟Student指向同一个原型(Person原型)了。此时如果再多一个Teacher来指向Person原型的话,我们对Student的修改影响了Person的原型,那Person的原型也会影响到Teacher
var obj = {
    name:"小余",
    age:20
}
//这一步要实现的是你给我传入的对象是要成为我新创建出来的对象的原型的
function createObject(o){
    var newObj = {}
    //跟获取原型getPrototypeOf对应的当然有设置对象原型的setPrototypeOf了
    Object.setPrototypeOf(newObj,o)
    return newObj//会通过上面创建newObj对象,然后将我们传入的对象作为newObj的原型,最后做一个返回
}
var info = createObject(obj)
console.log(info.__proto__);//实现了info原型指向obj对象
//console.log(info.prototype);我不知道你会不会这么做,但这是不行的,prototype是指向构造函数的原型对象的

同时我们在这里补充上面setPropertypeOf的详细信息


Object.setPrototypeOf() 是 JavaScript 中的一个内置函数,它用于设置一个对象的原型(即内部 [[Prototype]] 属性)。



参数:


  • object: 需要设置原型的对象。
  • prototype: 新的原型对象。



在上面的示例中,我们将 employee 对象设置为 person 对象的原型,所以 person 对象现在可以访问 employee 对象的属性 job 了。


需要注意的是,Object.setPrototypeOf() 只能在 ES6 以上的版本中使用。或者你使用babel巴别塔转换成ES5的语法

Object.setPrototypeOf(object, prototype);
let person = {
  name: 'XiaoYu',
  age: 20
};
let employee = {
  job: 'Engineer'
};
Object.setPrototypeOf(person, employee);
console.log(person.job); // 'Engineer'

但是在 道格拉斯·克罗克福德 这位大佬那个年代,setPropertypeOf内置函数还没有出来,所以我们来看看他当时是怎么写的吧!

var obj = {
    name:"小余",
    age:20
}
//这一步要实现的是你给我传入的对象是要成为我新创建出来的对象的原型的
function createObject(o){
    function Fn(){}
    //然后将Fn的函数原型设置为传进来的函数原型
    Fn.prototype = o
    //然后只有构造函数才有prototype,所以我们需要让Fn变成构造函数
    var newObj = new Fn()//同时这里还有一步深意,那就是o已经变成Fn的构造原型了
    //最后返回结果
    return newObj
}
var info = createObject(obj)
console.log(info.__proto__);//实现了info原型指向obj对象
//一个错误写法,开发中是最好不要给__proto__原型赋值
function createObject(o){
    var Fn = {}
  Fn.__proto__ = o
    return Fn
}

回顾知识点:


__proto__prototype的区别


  1. __proto__ 是 JavaScript 中的一个内部属性,它指向对象的原型。而 prototype 是一个对象的属性,用于定义构造函数的实例的原型。
  2. 简单来说,__proto__ 指向一个对象的原型对象,而 prototype 指向构造函数的原型对象。



函数的原型跟对象的原型有什么区别


函数的原型(Function.prototype)用于定义函数的默认属性和方法,比如 Function.prototype.apply()Function.prototype.bind() 等。而对象的原型(__proto__)用于定义对象的默认属性和方法。


简单来说,函数的原型是用来定义函数本身的属性和方法,而对象的原型是用来定义对象实例的属性和方法。


另外需要注意的是,每个函数都是一个对象,所以函数也有自己的原型,但是普通对象没有prototype

function Person(name) {
  this.name = name;
}
let person = new Person('XiaoYu');
console.log(person.__proto__ === Person.prototype); // true,这是对象实例的原型
console.log(Person.__proto__ === Function.prototype); // true 这是函数本身的原型
//在上面的示例中,person.__proto__ 指向 Person.prototype,而 Person.__proto__ 指向Function.prototype。

寄生式继承函数

  • 寄生式(Parasitic)继承
  • 寄生式(Parasitic)继承是与原型式继承紧密相关的一种思想, 并且同样由道格拉斯·克罗克福德(Douglas  Crockford)提出和推广的;
  • 寄生式继承的思路是结合原型类继承和工厂模式的一种方式;
  • 即创建一个封装继承过程的函数, 该函数在内部以某种方式来增强对象,最后再将这个对象返回;
//寄生式继承是一种创建对象的模式,它通过创建一个函数并在其上面添加属性和方法来实现继承。这种方式的优点是可以在不改变原型链的情况下进行继承,并且可以在创建新对象时不需要使用关键字 new。
//原型函数
var personObj = {
    running:function(){
        console.log("小余正在跑步");
    }
}
//工厂函数
function createStudent(name){
    var abc = Object.create(personObj)
    abc.name = name,
    abc.learn = function(){
        console.log("小余在学习");
    }
}
//寄生式继承 = 工厂函数+原型函数
var stu1 = createStudent("小余")
var stu2 = createStudent("小满")
//一般情况下我们也不会使用,因为是有缺陷的

Object.create()知识点补充(很重要,我们最终方案就依靠这个了)


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



参数:


  • proto: 创建的对象的原型对象。
  • propertiesObject: (可选) 新对象的属性。



上面的例子中我们创建了一个 animal 对象,然后使用它来创建一个 dog 对象。 dog 对象继承了 animal 对象的 eat 属性。

//语法
Object.create(proto[, propertiesObject])
let animal = {
  eat: function() {
    console.log("eating");
  }
}
let dog = Object.create(animal);
dog.bark = function() {
  console.log("barking");
}
dog.eat(); // "eating"
dog.bark(); // "barking"

寄生组合式继承

  • 现在我们来回顾一下之前提出的比较理想的组合继承
  • 组合继承是比较理想的继承方式, 但是存在两个问题:
    问题一: 构造函数会被调用两次: 一次在创建子类型原型对象的时候, 一次在创建子类型实例的时候.
    问题二: 父类型中的属性会有两份: 一份在原型对象中, 一份在子类型实例中
  • 事实上, 我们现在可以利用寄生式继承将这两个问题给解决掉
  • 你需要先明确一点: 当我们在子类型的构造函数中调用父类型.call(this, 参数)这个函数的时候, 就会将父类型中 的属性和方法复制一份到了子类型中. 所以父类型本身里面的内容, 我们不再需要.
  • 这个时候, 我们还需要获取到一份父类型的原型对象中的属性和方法
  • 能不能直接让子类型的原型对象 = 父类型的原型对象呢?
  • 不要这么做, 因为这么做意味着以后修改了子类型原型对象的某个引用类型的时候, 父类型原生对象的引用类型 也会被修改.
  • 我们使用前面的寄生式思想就可以了

image.png

//最终方案
function Person(name,age,friends){
    this.name = name
    this.age = age
    this.friends = friends
}
Person.prototype.learn = function(){
    console.log("小余在重新学习JavaScript");
}
Person.prototype.runAway = function(){
    console.log("小余跑路了");
}
function Student(name,age,friends,sno,score){
    Person.call(this,name,age,friends)//借用父级的这些属性
    this.sno = sno
    this.score = score
}
Student.prototype.eating = function(){
    console.log("小余今天吃了一只小满果实,Vue经验拉满达到精通");
}
Student.prototype = Object.create(Person.prototype)
var stu = new Student("小余",20,["小满","cool","学姐","菜菜哥"],111,100)
console.log(stu);
// Person {
//     name: '小余',
//     age: 20,
//     friends: [ '小满', 'cool', '学姐', '菜菜哥' ],
//     sno: 111,
//     score: 100
// }
//通过打印结果我们发现这个对象的名称怎么还是Person(父类),而不是Student(子类)
//这里是名字拼接对象,对象里就是我们的内容,名字是Person,通过找到我们stu.constructor进行拼接了
console.log(stu.constructor.name);//Person,证明了确实如此,是constructor进行拼接的

解决方案:


根据上面那张图,我们就知道是因为一路上都没找到constructor,所以顺着原型链找到了Person函数原型里的构造函数constructor,对于这点我们也可以进行验证


我们的解决方法就是,既然这一路都找不到constructor,那我们就在这路中的Student的原型这里设置一个constructor(因为我们的constructor只存在显式原型中,显式原型继续往下找就是constructor),让stu顺着原型先找到这个停下来。这就需要使用到我们的defineproperty


console.log(Person.prototype.constructor.name);//Person
//只需要添加如下代码,加在Student.prototype = Object.create(Person.prototype)后面
Object.defineProperty(Student.prototype,"constructor",{
    configurable:true,
    writable:true,
    enumerable:false,
    value:Student,
})

完整代码如下

function Person(name,age,friends){
    this.name = name
    this.age = age
    this.friends = friends
}
Person.prototype.learn = function(){
    console.log("小余在重新学习JavaScript");
}
Person.prototype.runAway = function(){
    console.log("小余跑路了");
}
function Student(name,age,friends,sno,score){
    Person.call(this,name,age,friends)//借用父级的这些属性
    this.sno = sno
    this.score = score
}
Student.prototype.eating = function(){
    console.log("小余今天吃了一只小满果实,Vue经验拉满达到精通");
}
Student.prototype = Object.create(Person.prototype)
Object.defineProperty(Student.prototype,"constructor",{
    configurable:true,
    writable:true,
    enumerable:false,
    value:Student,//确定我们类型的名字
})
var stu = new Student("小余",20,["小满","cool","学姐","菜菜哥"],111,100)
console.log(stu);//改变
// Student {
//     name: '小余',
//     age: 20,
//     friends: [ '小满', 'cool', '学姐', '菜菜哥' ],
//     sno: 111,
//     score: 100
// }
console.log(Person.prototype.constructor.name);//Person,因为我们不是改动这个,所以这个肯定还是不变的,我们是在Student的原型加入constructor的

但是这样依然不是最完善的做法,如果我们换成Teacher继承自Person呢?那不就还得再给Teacher写一遍,然后value写上Teacher。或者说Student不要了,换成Teacher,那不就需要修改很多地方。这就证明还有优化的地方,我们可以写一个工具函数进行补充

//工具函数,将这个核心功能封装起来
function inheritPrototype(SubType,SuperType){//SubType:子类型、SuperType:父类型
    SubType.prototype = Object.create(SuperType.prototype)
    Object.defineProperty(SubType.prototype,"constructor",{
        configurable:true,
        writable:true,
        enumerable:false,
        value:SubType,
    })
}
//需要的时候直接使用
inheritPrototype(Student,Person)//Student继承自Person
//完整代码
function inheritPrototype(SubType,SuperType){//SubType:子类型、SuperType:父类型
    SubType.prototype = Object.create(SuperType.prototype)//使用Object.create的写法有可能太新,社区目前旧的写法放在底下代码块中
    Object.defineProperty(SubType.prototype,"constructor",{
        configurable:true,
        writable:true,
        enumerable:false,
        value:SubType,
    })
}
function Person(name,age,friends){
    this.name = name
    this.age = age
    this.friends = friends
}
Person.prototype.learn = function(){
    console.log("小余在重新学习JavaScript");
}
Person.prototype.runAway = function(){
    console.log("小余跑路了");
}
function Student(name,age,friends,sno,score){
    Person.call(this,name,age,friends)//借用父级的这些属性
    this.sno = sno
    this.score = score
}
inheritPrototype(Student,Person)//Student继承自Person
Student.prototype.eating = function(){
    console.log("小余今天吃了一只小满果实,Vue经验拉满达到精通");
}
var stu = new Student("小余",20,["小满","cool","学姐","菜菜哥"],111,100)
console.log(stu);//正常显示
console.log(Person.prototype.constructor.name);//Person
//社区的旧写法(目前),其实就是自己封装Object.create进行使用
//通过这种方式创建的对象是一个浅拷贝,如果o对象里面有引用类型的数据,新对象和o对象指向的是同一个内存地址,对新对象的修改会影响到o对象。这种方式创建的对象也叫原型继承,是一种实现继承的方式
function createObject(o){
    function Fn(){}//创建一个空函数
    Fn.prototype = o//然后将其 prototype 属性设置为传入的对象 o,这样,Fn 函数的所有实例都会继承 o 对象的属性和方法
    return new Fn()//最后,我们使用 new 运算符创建一个新的 Fn 实例并返回这个实例,这样我们就得到了一个继承了 o 对象属性和方法的新对象
}
function inheritPrototype(SubType,SuperType){//SubType:子类型、SuperType:父类型
    SubType.prototype = createObject(SuperType.prototype)
    Object.defineProperty(SubType.prototype,"constructor",{
        configurable:true,
        writable:true,
        enumerable:false,
        value:SubType,
    })
}

对象的方法补充

判断方法

  • hasOwnProperty
  • 对象是否有某一个属于自己的属性(不是在原型上的属性)
  • in/for in 操作符
  • 判断某个属性是否在某个对象或者对象的原型上
  • in运算符只能检测属性是否存在,如果你需要检测属性的值是否为undefined,可以使用Object.prototype.hasOwnProperty()
  • for-in 循环会依次迭代对象的所有可枚举属性
  • instanceof
  • 用于检测构造函数的pototype,是否出现在某个实例对象的原型链上
  • 换个说法就是:instanceof方法是用来判断一个对象是否是某个构造函数的实例。
object instanceof constructor
//其中,object是要检测的对象,constructor是构造函数。如果object是constructor的实例,则返回true,否则返回false


  • n isPrototypeOf
  • 用于检测某个对象,是否出现在某个实例对象的原型链上
var obj = {
    name:"小余",
    age:20
}
var info = Object.create(obj,{//对象上create属性第二个参数是新增定义对象里面属性的
    address:{
        value:"福建",
        enumerable:true
    }
})
//只有address是真正属于info的,age跟name是在info原型身上的
console.log(info.name);
console.log(info.age);
console.log(info.address);
//判断哪些属性是真正属于info的,hasOwnProperty
console.log(info.hasOwnProperty("address"));//true
console.log(info.hasOwnProperty("name"));//false
//in操作符:不管在当前对象还是对象的原型中,返回的都是true
console.log("address" in info);//true
console.log("name" in info);//true
//所以in操作符的进阶就配合for循环使用
for(var key in info){
    console.log(key);
}
// address
// name
// age
//instanceof:检测构造函数的pototype,是否出现在某个实例对象的原型链上
//instanceof:用于检测构造函数的pototype,是否出现在某个实例对象的原型链上
function createObject(o) {
  function Fn() {}
  Fn.prototype = o
  return new Fn()
}
function inheritPrototype(SubType, SuperType) {
  SubType.prototype = createObject(SuperType.prototype)
  Object.defineProperty(SubType.prototype, "constructor", {
    enumerable: false,
    configurable: true,
    writable: true,
    value: SubType
  })
}
function Person() {
}
function Student() {
}
inheritPrototype(Student, Person)
console.log(Person.prototype.__proto__)//最顶层的原型:[Object: null prototype] {}
var stu = new Student()
console.log(stu instanceof Student) // true,学生是学生
console.log(stu instanceof Person) // true,学生是人
console.log(stu instanceof Object) // true,Object是最顶层的原型,Person在不额外设置的情况下最终指向Object
//isPrototypeOf:用于检测某个对象,是否出现在某个实例对象的原型链上
var obj = {
    name:"小余",
    age:18
}
var info = Object.create(obj)
console.log(obj.isPrototypeOf(info));//obj是不是出现在info的原型链上面,结果是true

原型继承关系

这里当我学完一遍后,我后续会回来复习,然后将思路完整总结的

image.png

image.png

//最根上面的原型对象
var obj = {
    name:"小余"
}
//很明显通过以下两种方式证明了这点
console.log(obj.__proto__)//[Object: null prototype] {}
console.log(obj.__proto__ === Object.prototype)//true
//对象里面是有一个__proto__:隐式原型
//Foo是一个函数,那么它会有一个显式原型对象:Foo.prototype
//Foo是一个对象,那么它会有一个隐式原型对象:Foo.__proto__
//显式原型对象必然是跟隐式原型对象不相等的
//prototype来自哪里?
//=>来自你一旦创建一个函数,那函数本身会有js引擎帮你创建出来一个新的对象的。而且Foo.prototype的身上还有constructor,这个又指向回Foo函数了
//__proto__来自哪里?
//new Funtion()   Foo.__proto__ = Funtion.prototype
//Funtion.prototype = {constructor:Funtion}
//var Foo = new Function()
function Foo(){
    
}
//Foo既是函数,也是一个对象,那是由谁创建出来的呢?由new Funtion
function Funtion(){}//这就像是一个类一样
var Foo = new Funtion()//然后就这样子创建出来了
console.log(Foo.prototype === Foo.__proto__);//false
console.log(Foo.prototype.constructor);//[Function: Foo]
console.log(Foo.__proto__.constructor);//[Function: Funtion]


目录
相关文章
|
8天前
|
JavaScript 前端开发
如何在 JavaScript 中使用 __proto__ 实现对象的继承?
使用`__proto__`实现对象继承时需要注意原型链的完整性和属性方法的正确继承,避免出现意外的行为和错误。同时,在现代JavaScript中,也可以使用`class`和`extends`关键字来实现更简洁和直观的继承语法,但理解基于`__proto__`的继承方式对于深入理解JavaScript的面向对象编程和原型链机制仍然具有重要意义。
|
17天前
|
JavaScript 前端开发
Javascript如何实现继承?
【10月更文挑战第24天】JavaScript 中实现继承的方式有很多种,每种方式都有其优缺点和适用场景。在实际开发中,我们需要根据具体的需求和情况选择合适的继承方式,以实现代码的复用和扩展。
|
11天前
|
JavaScript 前端开发
如何使用原型链继承实现 JavaScript 继承?
【10月更文挑战第22天】使用原型链继承可以实现JavaScript中的继承关系,但需要注意其共享性、查找效率以及参数传递等问题,根据具体的应用场景合理地选择和使用继承方式,以满足代码的复用性和可维护性要求。
|
11天前
|
JavaScript 前端开发 开发者
js实现继承怎么实现
【10月更文挑战第26天】每种方式都有其优缺点和适用场景,开发者可以根据具体的需求和项目情况选择合适的继承方式来实现代码的复用和扩展。
26 1
|
2月前
|
自然语言处理 JavaScript 前端开发
一文梳理JavaScript中常见的七大继承方案
该文章系统地概述了JavaScript中七种常见的继承模式,包括原型链继承、构造函数继承、组合继承、原型式继承、寄生式继承、寄生组合继承等,并探讨了每种模式的实现方式及其优缺点。
一文梳理JavaScript中常见的七大继承方案
|
2月前
|
JavaScript 前端开发
js之class继承|27
js之class继承|27
|
2月前
|
JSON JavaScript 前端开发
js原型继承|26
js原型继承|26
|
2月前
|
JavaScript 前端开发 开发者
JavaScript 类继承
JavaScript 类继承
19 1
|
2月前
|
JavaScript 前端开发
JavaScript prototype(原型对象)
JavaScript prototype(原型对象)
31 0
|
2月前
|
JavaScript 前端开发
JS的几种继承方式
JavaScript中的几种继承方式视频。
14 0