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

05:JS 原型链

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

Function 和 Object 为何都是 函数对象呢?

一. 普通对象 和 函数对象

  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. 什么是普通对象? 🤔

我们先想想一下,创建对象的方式?

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()创建的。

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

    这么来理解这句话呢?

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

// 等价于

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

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

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属性,用来引用它的函数对象。这是一种循环引用

1、Person.prototype.constructor

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

2、Function.prototype.constructor

ƒ Function() { [native code] }

3、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

posted @ 2018-07-20 16:46 SmallW 阅读(...) 评论(...) 编辑 收藏