《前端之路》之 JavaScript原型及原型链详解

05:JS 原型链

在 JavaScript 的世界中,万物皆对象! 可是这各类各样的对象其实具体来划分的话就 2 种。 一种是 函数对象,剩下的就是 普通对象。其中 FunctionObject 为JS自带的 函数对象。(哎? 等等, Function 为 函数对象 能够理解,为何 Object也是函数对象呢?带着疑问咱们继续往下看。 )javascript

Function 和 Object 为什么都是 函数对象呢?css

一. 普通对象 和 函数对象

  1. 函数对象 的疑惑 🤔
var f1 = new Function('arg', 'console.log(arg)')
var test1 = new Object
var f3 = new Object()

console.log(typeof f1)          // function
console.log(typeof test1)       // object
console.log(typeof f3)          // object
通过 new 实例化事后的 test1 和 f3 均为 Object 即为 普通对象。

那若是咱们直接 打印出来 未实例化的对象 的类型 呢?
var test2 = Object
var test3 = Object()

console.log(typeof test2)       // function
console.log(typeof test3)       // object
这里咱们看到了一些区别,可是这又是为何呢?

下面咱们看完普通对象再纵向的进行一下对比
  1. 什么是普通对象? 🤔

咱们先想一想一下,建立对象的方式?html

var F1 = function() {}

var o1 = {}
var o2 = new F1()
var o3 = new Object()

console.log(typeof o1)      // object
console.log(typeof o2)      // object
console.log(typeof o3)      // object
  1. 直接纵向对比这 两类 对象
function f1() {}
var f2 = function() {}
var f3 = new Function('arg', 'console.log(arg)')

var o1 = {}
var o2 = new Object()
var o3 = new f1()

console.log(typeof f1)      // function
console.log(typeof f2)      // function
console.log(typeof f3)      // function

console.log(typeof o1)      // object
console.log(typeof o2)      // object
console.log(typeof o3)      // object

// 对比前文中的 Function 和 Object

console.log(typeof Function)    // function
console.log(typeof Object)      // function

在上面的例子中 o1 o2 o3 为普通对象,f1 f2 f3 为函数对象。怎么区分,其实很简单,凡是经过 new Function() 建立的对象都是函数对象,其余的都是普通对象。f1,f2,归根到底都是经过 new Function()的方式进行建立的。Function Object 也都是经过 New Function()建立的。html5

  • f1,f2,归根到底都是经过 new Function()的方式进行建立的。java

    这么来理解这句话呢?css3

function fns(a, b) {
    return a + b
}

// 等价于

var fns = new Function('a, b', 'return a + b')
  • 上面的代码作了一个同目标的不一样实现实现方法,那么为何两种方案的结果都是相同的呢?git

    讲到这里就须要对 JavaScript 这门语言进行 分析了。github

JavaScript 是一门解释型的语言数组

什么是 解释型 语言?

   在客户端的浏览器中存在一个 能够解释 JS 的`引擎`。 这里JavaScript的引擎 就是 谷歌的 V8 引擎 和 其余浏览器引擎。
   而咱们经常据说的 ES5 ES6 什么的,每每指的是当前 JS 语言的版本。或者说当前的 JS 语言的标准。 而后所谓的浏览器兼容这些新的特性其实就是浏览器的 JS 引擎的升级,去适配这些新版本的 JS 的新特性。 不兼容,每每就是 引擎 不支持这个新特性。

(因此,咱们会发现写一个 浏览器 应用 仍是很难的。 由于你须要去兼容这么的东西,最新版本的JS,css3 最新的特性 , html5 的新标签,等等)

那么咱们回到 解释型语言 上来,有了能解释 JS 语句的引擎了,那么上面就必定会有必定的规则了,否则的话,若是你乱写都能被 引擎 读懂的话,要这 引擎 何用。

好,上段 中介绍到了 规则的问题, 那么 JS 语言自己确定也是隐藏了一些 咱们在 ES 系列上看不到的 规则。 那是什么呢?
咱们一块儿来看下,

这就是本小节将要介绍的函数对象(Function Object)。

函数对象与其它用户所定义的对象有着本质的区别,这一类对象被称之为内部对象,例如日期对象(Date)、数组对象(Array)、字符串对象(String)都是属于内部对象。换句话说,这些内置对象的构造器是由JavaScript自己所定义的:经过执行new Array()这样的语句返回一个对象,JavaScript 内部有一套机制来初始化返回的对象,而不是由用户来指定对象的构造方式。

这些内置对象的构造器是由JavaScript自己所定义的浏览器

因此: new Fucntion('arg', console.log(arg)) 

      new Array()
      
      new Date()

      new String()

都会返回对应的 对象。 因此,当咱们在用 字面量 去建立一个 函数的时候,JS 解释器就会 用这些 内置的对象构造器 Function 去 实例化 并返回一个 函数对象。 那么咱们能够想象一下,是否是 咱们本身在写函数的时候直接 new Function 的方法来写 会不会执行效率更高。

一样 问题就来了, var fn =  new Function('a', 'return a')

这样写的话,参数还好,可是 函数体 若是很长 不少的话就很难受了,因此~ 

面前咱们所用的建立 函数对象的方法 即为 最方便的方法。

二. 原型对象

什么是原型对象?

在 JavaScript 中,每当定义一个对象的时候,对象中都会包含一些预约义的属性。 其中 函数对象 的一个属性就是 prototype。 (上文介绍到的 普通对象没有 prototype,可是有 __proto__ ) (可是 函数对象也有 proto 这里须要注重理解下,否则容易出错。)

因此 通过上面的解释 是否是就清楚了,原型对象 也是一种 普通对象。可是只有 函数对象 拥有。

可是 有且仅有一个特殊的 案例 须要注意下。

eg:

function f1(){}
console.log(f1.prototype)                           // {...}

console.log( typeof f1.prototype)                   // object

console.log(typeof  Function.prototype)             // Function

console.log(typeof  Object.prototype)               // Object

console.log(typeof  Function.prototype.prototype) //undefined

原型对象其实就是普通对象(Function.prototype除外,它是函数对象,但它很特殊,他没有prototype属性(前面说道函数对象都有prototype属性))

为何?

从这句console.log(f1. prototype) //f1 {} 
的输出就结果能够看出,f1.prototype 就是 f1 的一个实例对象(这里结合上面讲到了 实例话 函数对象的过程)。就是在f1 函数 在建立的时候,建立了一个它的实例对象 (var temp = new Function('','') ) 并赋值给它的prototype (f1.prototype = temp)
console.log(typeof  Function.prototype.prototype) //undefined

惟一一个特殊的 函数对象没有 prototype 属性的。 为何? 就是根据上面在 控制台打印出来的结果。它是个特例,须要特殊记忆!

原型对象有什么做用?

主要是用来继承

eg:

var Person = function(name) {
    this.name = name
    this.getName = function() {
        return this.name
    }
}

Person.prototype.changeName = function(name) {
    this.name = name
}

Person.prototype.getFirstName = function(name) {
     return this.name 
}

var zhang = new Person('zhang')
var res0 = zhang.getName()
var res1 = zhang.getFirstName()
        
console.log(res0)           // zhang
console.log(res1,'xxx')     // zhang xxx

zhang.changeName('ge')
var res2 =  zhang.getName()
console.log(res2)           // ge

经过这个例子咱们能够看出来, 咱们经过给 构造函数prototype 属性添加 方法(getName)。

那么它 全部实例化出来的 函数对象都会带有这个方法(getName),一样添加属性也是同样。

那么为何 可以 实现继承呢? 下面咱们就讲到了 原型链

三.原型链

JS 在建立对象(不论普通对象仍是函数对象)的时候,都有一个叫作__proto__对内置属性,用于指向建立它对函数对象的原型对象 prototype

var Person = function(name) {
    this.name = name
    this.getName = function() {
        return this.name
    }
}

var zhang = new Person('zhang')

zhang.__proto__ === zhang.prototype         // true

一样,zhang.prototype 对象也有 __proto__ 属性,它指向建立它的函数对象(Object)的prototype

zhang.prototype.__proto__ === Object.prototype      // true

继续,Object.prototype 对象也有 __proto__ 属性,但它比较特殊,为null

console.log(Object.prototype.__proto__) //null

咱们把这个有__proto__串起来的直到Object.prototype.__proto__null叫作原型链

按照咱们上面说的例子来展现下这个原型链
var Person = function(name) {
    this.name = name
    this.getName = function() {
        return this.name
    }
}

var obj = new Person('zhang')

// 其中 obj.__proto__ 指向了 Person.prototype(即为 Person 的实例)

// Person.prototype 的 __proto__ 指向了 Object.prototype

// Object.prototype 的 __proto__ 指向了 null

经过 __proto__ 串起来的直到Object.prototype.__proto__为null的链叫作原型链

四.constructor

原型对象prototype中都有个预约义的constructor属性,用来引用它的函数对象。这是一种循环引用

一、Person.prototype.constructor

ƒ (name) {
    this.name = name
    this.getName = function() {
        return this.name
    }
}

二、Function.prototype.constructor

ƒ Function() { [native code] }

三、Object.prototype.constructor

ƒ Object() { [native code] }

person.prototype. constructor === person // true

Function.prototype.constructor === Function // true

Object.prototype.constructor === Object // true

六.总结

最后打个比喻,虽然不是很确切,但可能对原型的理解有些帮助

父亲(函数对象),先生了一个大儿子( prototype),也就是你大哥,父亲给你大哥买了好多的玩具,当你出生的时候,大家之间的亲情纽带(__proto__)会让你天然而然的拥有了你大哥的玩具。

一样,你也先生个大儿子,又给他买了好多的玩具,当你再生儿子的时候,你的小儿子会天然拥有你大儿子的全部玩具。至于他们会不会打架,这不是咱们的事了。

因此说,你是从你大哥那继承的,印证了那句“长兄如父”啊!

可以对上图有所理解的话,原型 、原型链 等等都有一个很好的理解了,

固然也须要有大量的 OOP 相关的开发,才能对 JS 的 OOP 有一个 深入的理解。

Github地址,欢迎 Star

相关文章
相关标签/搜索