新版原型和原型链详解,看完整个人都通透

了解原型、原型链前需要先了解构造函数,new操作符

构造函数

构造函数是一种特殊的函数,主要用来初始化对象,即为对象成员变量赋初始值,它总与 new 一起使用。我们可以把对象中一些公共的属性和方法抽取出来,然后封装到这个函数里面。

function Foo () {
    this.name = 'gl'
    this.fun = function () { console.log('eat')}
}

const Foo = function (){
    this.xxx = xxx;
}

const fooInstancing = new Foo();
const fooInstancing1 = new Foo();

构造函数的三大特点:

  • 构造函数的函数名的第一个字母通常大写。
  • 函数体内使用this关键字,代表所要生成的对象实例。
  • 生成对象的时候,必须使用new命令来调用构造函数。

new操作符

new命令的作用,就是执行一个构造函数,并且返回一个对象实例。使用new命令时,它后面的函数调用就不是正常的调用,而是依次执行下面的步骤。

  • 创建一个空对象,作为将要返回的对象实例
  • 将空对象的原型指向了构造函数的prototype属性。
  • 将空对象赋值给构造函数内部的this关键字。
  • 开始执行构造函数内部的代码。

也就是说,构造函数内部,this指向的是一个新生成的空对象,所有针对this的操作,都会发生在这个空对象上。
后续了解了原型之后我们再简单实现一个new

原型

在JavaScript没有class的时候,是使用构造函数来新建一个对象的,每一个构造函数的内部都有一个prototype属性,它的属性值是一个对象(prototype),这个对象包含了可以由该构造函数的所有实例共享的属性和方法。当使用构造函数新建一个对象后,在这个对象的内部将包含一个指针,这个指针指向构造函数的 prototype 属性对应的值,在 ES5 中这个指针(proto)被称为对象的原型。

一般来说不应该能够获取到这个值的,但是现在浏览器中都实现了 proto 属性来访问这个属性,但是最好不要使用这个属性,因为它快芭比Q了。ES5 中新增了一个Object.getPrototypeOf() 方法,可以通过这个方法来获取对象的原型。

结论

以上明白了 两点

  • prototype(原型,类型是对象,一般叫原型对象)是构造函数的
  • proto、Object.getPrototypeOf(obj)是获取对象的原型
  • prototype能干啥,共享~可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法
// 构造函数本质是函数
function DoSomething(){}
console.log( DoSomething.prototype );
// 两种写法
var DoSomething = function(){};
console.log( DoSomething.prototype );

// ?
{constructor: ƒ}
    constructor: ƒ DoSomething()
        arguments: null
        caller: null
        length: 0
        name: "DoSomething"
        prototype: {constructor: ƒ}
        [[FunctionLocation]]: VM1158:1
        [[Prototype]]: ƒ ()
    [[Prototype]]: Object
        constructor: ƒ Object()
        hasOwnProperty: ƒ hasOwnProperty()
        isPrototypeOf: ƒ isPrototypeOf()
        propertyIsEnumerable: ƒ propertyIsEnumerable()
        toLocaleString: ƒ toLocaleString()
        toString: ƒ toString()
        valueOf: ƒ valueOf()
        __defineGetter__: ƒ __defineGetter__()
        __defineSetter__: ƒ __defineSetter__()
        __lookupGetter__: ƒ __lookupGetter__()
        __lookupSetter__: ƒ __lookupSetter__()
        __proto__: (...)
        get __proto__: ƒ __proto__()
        set __proto__: ƒ __proto__()

新版原型和原型链详解,看完整个人都通透_第1张图片
现在,我们可以添加一些属性到 doSomething 的原型上面,如下所示。

function DoSomething(){}
DoSomething.prototype.foo = "bar";
console.log( DoSomething.prototype ); // ?

新版原型和原型链详解,看完整个人都通透_第2张图片
然后,我们可以使用 new 运算符来在现在的这个原型基础之上,创建一个 doSomething 的实例。然后,就可以在这个对象上面添加一些属性。

function DoSomething(){}
DoSomething.prototype.foo = "bar"; 
var doSomeInstancing = new DoSomething();
doSomeInstancing.prop = "some value";
console.log( doSomeInstancing ); // ?

新版原型和原型链详解,看完整个人都通透_第3张图片

__proto__哪去了?

这时候会发现我们的__proto__哪去了,对象实例里面咋没有,凡事先看权威文档
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/proto

proto 的读取器 (getter) 暴露了一个对象的内部 [[Prototype]]
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Inheritance_and_the_prototype_chain#%E7%BB%A7%E6%89%BF%E5%B1%9E%E6%80%A7
备注: 遵循 ECMAScript 标准,someObject.[[Prototype]] 符号是用于指向 someObject 的原型。从 ECMAScript 6 开始,[[Prototype]] 可以通过 Object.getPrototypeOf() 和 Object.setPrototypeOf() 访问器来访问。这个等同于 JavaScript 的非标准但许多浏览器实现的属性 proto。但它不应该与构造函数 func 的 prototype 属性相混淆。被构造函数创建的实例对象的 [[Prototype]] 指向 func 的 prototype 属性。Object.prototype 属性表示 Object 的原型对象。

那我们在试试Object.getPrototypeOf()和__proto__的关系

Object.getPrototypeOf(doSomeInstancing);
// ? 
doSomeInstancing.__proto__;

// ? true or false
Object.getPrototypeOf(doSomeInstancing) === doSomeInstancing.__proto__;

某些浏览器内部还是支持__proto__的,例如谷歌,结论[[Prototype]] 就是 proto

新版原型和原型链详解,看完整个人都通透_第4张图片
下文还是用 大家熟知的__proto__说道

  • 回过头发现doSomeInstancing.__proto__和doSomething.prototype打印结果是一样的
    新版原型和原型链详解,看完整个人都通透_第5张图片
Object.getPrototypeOf(doSomeInstancing) === DoSomething.prototype;
// ?

在这里插入图片描述
所以 Object.getPrototypeOf(doSomeInstancing) === doSomeInstancing.proto === DoSomething.prototype

那什么是原型链呢

思考一个问题,如果访问doSomeInstancing里的一个属性,浏览器是怎么做的?

  1. 浏览器首先会查看doSomeInstancing 中是否存在这个属性
  2. 如果 doSomeInstancing 不包含属性信息,那么浏览器会在 doSomeInstancing__proto__ 中进行查找 (同 doSomething.prototype). 如属性在 doSomeInstancing__proto__中查找到,则使用 doSomeInstancing__proto__ 的属性。
  3. 否则,如果doSomeInstancing__proto__ 不具有该属性,则检查doSomeInstancing __proto____proto__ 是否具有该属性。默认情况下,任何函数的原型属性__proto__都是 window.Object.prototype. 因此,通过 doSomeInstancing __proto____proto__ ( 同 doSomething.prototype__proto__ (同 Object.prototype)) 来查找要搜索的属性。
  4. 如果属性不存在 doSomeInstancing __proto__ __proto__ 中,那么就会在doSomeInstancing 的 __proto__ __proto____proto__ 中查找。然而,这里存在个问题:doSomeInstancing __proto____proto____proto__ 其实不存在。因此,只有这样,在 __proto__ 的整个原型链被查看之后,这里没有更多的__proto__,浏览器断言该属性不存在,并给出属性值为undefined的结论。

找一个属性,要顺着原型__proto__一直找下去,这时候就会形成一个链路,这就是原型链的查找规则,而原型链就是这个规则本身。

但是要注意一点__proto__只是提供一种查找规则,是非标准的,实际指向的还是prototype 原型对象。实际开发中不推荐使用,推荐在prototype上操作。

constructor = 构造函数

**对象的原型( __ proto __ )和原型对象(prototype)**里面都有一个属性 constructor 属性 ,constructor 我们称为构造函数,因为它指回构造函数本身。

constructor 主要用于记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数。

function DoSomething(eat){
    this.eat = eat;
}
var doSomeInstancing = new DoSomething('吃');

console.log(DoSomething.prototype.constructor);
console.log(doSomeInstancing.__proto__.constructor);

新版原型和原型链详解,看完整个人都通透_第6张图片

FQA

proto、prototype、constructor关系

新版原型和原型链详解,看完整个人都通透_第7张图片

属性查找机制

  1. 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
  2. 如果没有就查找它的原型(也就是 proto 指向的 prototype 原型对象)。
  3. 如果还没有就查找原型对象的原型(Object的原型对象)。
  4. 依此类推一直找到 Object 为止(null)。
  5. proto 对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。

原型链的终点是什么?

由于Object是构造函数,原型链终点是Object.prototype.proto,而Object.prototype.proto=== null // true,所以,原型链的终点是null。原型链上的所有原型都是对象,所有的对象最终都是由Object构造的,而Object.prototype的下一级是Object.prototype.proto
新版原型和原型链详解,看完整个人都通透_第8张图片

代码输出

function DoSomething(){
}
DoSomething.prototype.foo = "bar";

var doSomeInstancing = new DoSomething();

doSomeInstancing.prop = "some value";

// 下面代码输出
console.log("doSomeInstancing.prop:      " + doSomeInstancing.prop); // some value
console.log("doSomeInstancing.foo:       " + doSomeInstancing.foo); // bar
console.log("doSomething.prop:           " + DoSomething.prop); // undefined
console.log("doSomething.foo:            " + DoSomething.foo); // undefined
console.log("doSomething.prototype.prop: " + DoSomething.prototype.prop); // undefined
console.log("doSomething.prototype.foo:  " + DoSomething.prototype.foo); // bar
function Fn(){
    var a = 12
    this.getName = function(){
        console.log('private getName')
    }
}

Fn.prototype.getName = function (){
      console.log('public getName')
}

var fn = new Fn()
var fn1 = new Fn()
// 1,2
console.log(fn.a) // undefined
console.log(fn.getName()) // private getName
// 3,4,5
console.log(fn.getName === fn1.getName) // false 
console.log(fn.__proto__.getName === fn1.__proto__.getName) // true
console.log(fn.__proto__.getName === Fn.prototype.getName) // true
//6,7
console.log(fn.hasOwnProperty === Object.prototype.hasOwnProperty) // ture
console.log(fn.constructor === Fn) // ture 

new如何实现

  • 创建一个空对象,作为将要返回的对象实例。
  • 将空对象的原型指向了构造函数的prototype属性。
  • 将空对象赋值给构造函数内部的this关键字。
  • 开始执行构造函数内部的代码。
  • 返回此对象
function _new (fn, ...args) {
   // 不是一个函数
   if (typeof fn === 'function') {
       ///创建一个空对象,作为将要返回的对象实例。
       const obj = new Object();
       // 将空对象的原型指向了构造函数的prototype属性。
       obj.__proto__ = fn.prototype;
       // 将空对象赋值给构造函数内部的this关键字
       const result = fn.apply(obj, args);
       return result instanceof Object ? result : obj; 
   } else {
       throw TypeError('not a function');
   }
}

原型的应用

  • instanceof 如果A instanceof B。判断 B 的 prototype 属性指向的原型对象(B.prototype)是否在对象 A 的原型链上。
  • vue2的eventBus
  • 给某个对象加一个公用封装方法

finish,各位观众姥爷收藏➕关注,追好文不迷路

你可能感兴趣的:(javascript,前端)