再谈构造函数、原型、原型链之间的关系

1,114 阅读4分钟

前言

构造函数、原型、原型链作为ES5的内容,已经是老生常谈的问题了。首先说说为什么要再次拿起这个话题去说呢?这几天有空我会看一些源码,这些源码的底层实现考虑到兼容性还是来源于ES5,很多方法的封装以及实现(不管是按照模块封装还是统一实现)都是面向对象的思想,而且webpack以及rollup打包之后解析出来的代码利用@babel/core@babel/preset-env转化之后也都是ES5的代码,所以有想再次谈起这个话题,回顾回顾旧知识,温故而知新,喜欢😍的话点个赞或者关注一下小编哦😜。当然了有错误的地方和需要改进的地方可以留言区留言

构造函数

什么是构造函数? 构造函数就是使用关键字new创建对象时调用的函数。 构造函数的属性可分为两种:1.实例上的属性 2.公用属性

//实例上的属性
function Animal(){
    this.name=name;
    this.age=18;
}

原型

原型是每个构造函数都有的,在JS规定,每一个构造函数都有一个 prototype 属性,指向另一个对象,注意这个prototype就是一个对象,这个对象的所有属性和方法都会被构造函数所拥有。原型的作用是共享方法,一般情况下,我们的公共属性定义在构造函数里面,公共的方法放到原型对象上。

Animal.prototype.address={location:"野外"};

实例

使用关键字new调用构造函数创建实例

let a1 = new Animal("猴子");
let a2 = new Animal("小鸡");

原型链

在JavaScript中万物都是对象,对象和对象之间也有关系,并不是孤立存在的。对象之间的继承关系,在JavaScript中是通过prototype对象指向父类对象,直到指向Object对象为止,这样就形成了一个原型指向的链条,专业术语称之为原型链

举例说明:person → Person → Object ,普通人继承人类,人类继承对象类。

当我们访问对象的一个属性或方法时,它会先在对象自身中寻找,如果有则直接使用,如果没有则会去原型对象中寻找,如果找到则直接使用。如果没有则去原型的原型中寻找,直到找到Object对象的原型,Object对象的原型没有原型,如果在Object原型中依然没有找到,则返回null。每个实例都有__proto__ 指向所属类的原型

下面看几个题,思考一下结果(答案在文末):

console.log(a1.age === a2.age);
console.log(a1.address === a2.address);
console.log(a1.__proto__ === a2.__proto__);
console.log(a1.constructor === Animal);

console.log(Animal.__proto__ === Function.prototype);
console.log(a1.__proto__.__proto__ === Object.prototype);
console.log(Object.prototype.__proto__);

继承

首先定义一个Animal的父类构造函数和一个子类Tiger构造函数.

function Animal(name){
    this.name = name;
    this.eat = "吃肉";
}
Animal.prototype.address={location:"山里"};

function Tiger(name){
    this.name = name;
    this.age = 10;
}
Tiger.prototype.say = function(){
    console.log("说话");
}

补充知识:call,apply,bind的可以改变this的指向。call和apply会立刻执行,bind调用函数时才会执行。call和bind第一个参数传入的是对象或者null或者不传,后面参数是参入字符串。apply第一个参数对象或者null或者不传,后面的参数是数组。为什么vue的methods方法中的this总是指向Vue实例vm呢? 就是因为使用bind方法把this绑死了

function polyfillBind (fn, ctx) {
  function boundFn (a) {
    var l = arguments.length;
    return l
      ? l > 1
        ? fn.apply(ctx, arguments)
        : fn.call(ctx, a)
      : fn.call(ctx)
  }

  boundFn._length = fn.length;
  return boundFn
}

function nativeBind (fn, ctx) {
  return fn.bind(ctx)
}

var bind = Function.prototype.bind
  ? nativeBind
  : polyfillBind;
  
  
vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm);

言归正传,在子类Tiger构造函数中使用call方法改变this`的指向实现父类实例上的属性继承。

Animal.call(this)
let tigger = new Tiger();
console.log(tigger.eat);  //吃肉

如果要继承父类的公共属性或者方法可以使用下面的方法:Object.setPrototypeOf

Tiger.prototype.__proto__ = Animal.prototype 等价于下面的方法
Object.setPrototypeOf(Tiger.prototype,Animal.prototype)   //es7

谈到继承我们再谈谈ES5中Object.create()方法,在面试中有可能被问到哦! Object.create()和直接new Object()的区别 我们先看一个例子

let obj = Object.create({a:1})
console.log(obj.a)  //undefined
console.log(obj.__proto__.a) //1

let obj = new Object({a:1})
console.log(obj.a)  //1
console.log(obj.__proto__.a) //undefined

显然:Object.create()是将对象继承到原型链上,可以通过原型链访问,new Object()不可以在原型链上访问。

思考一个问题:继承父类的公共属性或者方法能不能使用Object.create()

Tiger.prototype= Object.create(Animal.prototype)

问题又来了tigger.constructor指向了父级Animal,解决方法如下

Tiger.prototype= Object.create(Animal.prototype,{constructor:{value:Tiger}})

原理如下:

function create(parentPrototype){
    let Fn = function(){};
    Fn.prototype = parentPrototype;  //当前函数的原型  只有父类的原型
    let fn = new Fn();
    fn.constructor = Tiger
    return fn     //当前的实例可以拿到  Animal.prototype
}
Tiger.prototype = create(Animal.prototype)

为什么不直接使用Tiger.prototype = new Animal()

直接把say方法都覆盖了,不能这么用

总结:

在使用继承的时候可以用call + Object.create()或者call + setPrototypeOf方法

思考答案

console.log(a1.arr === a2.arr); //true
console.log(a1.address === a2.address);//true
console.log(a1.__proto__ === a2.__proto__);//true
console.log(a1.constructor === Animal);//true
console.log(Animal.__proto__ === Function.prototype);//true
console.log(a1.__proto__.__proto__ === Object.prototype);//true
console.log(Object.prototype.__proto__);//null

结尾

以上就是本文的全部内容,有错误的地方欢迎指出,欢迎点评🤭,更多干货欢迎关注公众号:小丑的小屋