面试 | 你不得不懂得 JS 原型和原型链

推荐阅读地址

掘金

github 求 start😄😄

大家好,我是林一一,这是一篇关于 JS 原型和原型链中的那些事,了解原型和原型链之前需要了解一些 JS 中的OOP,大家也可以直接跳到原型和原型链那里。让我们开始阅读吧。😝

本文思维导图

siweidaotu.png

面向对象编程 OOP (Object Oriented Programming)

  • 面向过程:就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。

  • 面向对象:是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。

  • 面向过程优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、 Linux/Unix等一般采用 面向过程开发,性能是最重要的因素。 缺点:没有面向对象易维护、易复用、易扩展

  • 面向对象优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护 缺点:性能比面向过程低

JS 中一切都是对象

  • 每一个数据类型底层的封装都是基于 Object 来创建的,引用类型 Function() 函数 Array() Date() Math(),String(), Number(), 等基本类型,nodeLIst 节点集合,window 等都是

  • 基于构造函数 constructor 创建自定义类

// 字面量形式
function fn(){}
var obj = {}

// 构造函数模式
var f = new fn()    // ==>   new fn
console.log(f)

var obj = new Object()
console.log(obj)

使用 new 关键字就是利用构造函数创建一个实例,实例就是一个类。另一个就是字面量形式

  • Object 是一个工厂方法能根据传入的类型,转换成相应的原始包装类型
var num = Object(12)
console.log(num instanceof Number)  // true

var str = Object('12')
console.log(str instanceof String)  // true

var boolean = Object(true)
console.log(boolean instanceof Boolean)  // true
  • 基本类型值创建方式的区别
var a = 12
console.log(typeof a)   // 'number'
a.toFixed(2)    // "12.00"

var b = new Number(12)
console.log(typeof b)   // 'object'
b.toFixed(2)    // "12.00"

var c = new String('12')
console.log(typeof c)  // object

需要注意的是基本类型 Symbol() 并支持 new 语法,浏览器不认为 Symbol 是一个构造函数,其他基本类型都可以认为是构造函数。

思考

问:原始值为什么也可以使用属性或方法 a.toFixed ? 不是说原始值就是一个值没有属性嘛?因为使用 new 关键字创建出来的是一个实例,同时字面量形式创建出来的 a 也是一个实例,实例就有属性和方法。字面量形式的创建实际分为三个步骤,以上面代码为示例

var a = 12
a. toFixed(2)

// 相当于下面
/*
* 1. 创建一个 Number 类型的实例
*  var a = new Number(12)
* 2. 调用实例上的方法或属性
*  a.toFixed(2)
* 3. 销毁实例
*/

a.myPro = '12'
console.log(a.myPro)    // undefined

需要注意的是原始值创建的实例,是只读的,所以不能向实例内添加任何属性或方法。如果添加了属性或方法那也是在当前行内创建一个临时的对象,当前行代码运行结束后这对象就已经被销毁了,例如上面的 a.myPro 是 undefined 《红宝书4 P114页》

构造函数的运行机制

function Person(name, age){
    var a = 12
    this.name = name
    this.age = age
}

var person = new Person('林一一', 18)
console.log(person)

思考

1.new Person()这个过程中发生了什么

  • 同样开辟一个私有作用域栈内存,形参赋值和变量提升
  • JS 代码执行之前,构造函数会在当前私有作用域内创建一个对象也就是开辟一个堆内存空间,但暂时不存储任何内容。浏览器会让函数中的主体 this 指向这个堆内存地址
  • 代码自上而下执行
  • 最后代码执行结束后,浏览器会把创建的对像堆内存的对象默认返回,不需要写 return。返回的也就是一个实例

JS 的变量提升机制

constructorPrint.jpg

上面的 this 指向的就是 Person 这个对象,使用 this 的才会给实例创建属性,var a = 12 就不会给实例创建属性 console.log(person.a) ==> undefined,对比 ES6 中的 class。

2.在构造函数中强制 return 返回值会怎么样?

function Person(name, age){
    var a = 12
    this.name = name
    this.age = age
    return '林一一'
    // return {name: '林一一'}
}

var person = new Person('林一一', 18)
console.log(person)

在构造函数中的 return 会被剥夺,return '林一一' 的返回值还是一个实例 Person {name: "林一一", age: 18}return {name: '林一一'} 的返回值就是 return {name: '林一一'}

  • 在构造函数中 return 的返回值是原始值时,浏览器返回的还是实例
  • 强制返回一个创建对象时,返回的就是创建的对象。不符合我们想要得到一个类的实例。所以在构造函数中使用 return 是没有多大意义的

原型和原型链

JS 中一切皆对象。基本类型,引用类型都是基于 Object 这个基类创建的。函数也是,prototype 的值也是对象类型。

prototype 和 constructor 和 proto之间的关联

  • 每一个函数类型都自带一个 prototype 的原型属性,原型是对象类型,浏览器会开辟一个堆内存空间。
  • 浏览器会给这个堆内存空间中添加一个 constructor 的属性,属性值是构造函数本身。构造函数中并没有constructor属性,但是会从构造函数的prototype中查找,obj.constructor===obj.prototype.constructor
  • 每一个对象都有一个 __proto__ 的属性,这个属性指向所创建类的 prototypeprototype 也是对象同样也有 __proto__这个属性。函数是对象吗?是的,所以函数也有 __proto__这个属性。如果不能确定指定的类,那 __proto__ 会指向Object。
  • object 这个基类的 __proto__ 指向的是自己本身,__proto__ 最终指向值是 null

but 过 __proto__ 不是实例的属性,也不是构造函数的属性,在大多数的浏览器中都支持这种非正式的访问方式。实际上 __proto__ 来自 Object.prototype,当使用 obj.__proto__ 时,可以理解成返回了 Object.getPrototypeOf(obj)

<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1d05ecc2dd954e439683b361d4220849~tplv-k3u1fbpfcp-watermark.image" width="90%" height="350px" />

// 一行代码解析上面的1,2句话
String.prototype.constructor === String   // true

function Fn(){}
var fn = new Fn()

// 对应上面的第三句话
fn.__proto__ === Fn.prototype   // true

//对应上面第四句话
fn.__proto__.__proto__.__proto__ === null   // true

上面代码的成立是因为堆内存中 constructor 的值存储的是函数本身

prototype 原型的作用

  • 每一个类都会把公共的属性和方法存储到原型上,给实例调用。
  • 给所创建类的原型 prototype 添加属性和方法就是给实例添加共有方法。
 Object.prototype.myName = '林一一'
 var obj = new Object()
 console.log(obj.myName)

原型链机制的查找过程

原型链就是基于 __proto__ 的向上查找机制。当实例操作某个属性或方法时会在当前自己的作用域中查找,找到了则查找结束。没有找到就基于所创建类的原型对象上的 __proto__ 继续向上查找,直到找到基类的 Object.prototype 为止,如果还是没有找到则直接undefined

原型链机制.jpg

图中蓝色的线就是原型链了

思考题,下面结果都输出什么,为什么?

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)
console.log(fn.getName())
// 3,4,5
console.log(fn.getName === fn1.getName)
console.log(fn.__proto__.getName === fn1.__proto__.getName)
console.log(fn.__proto__.getName === Fn.prototype.getName)
//6,7
console.log(fn.hasOwnProperty ===Object.prototype.hasOwnProperty)
console.log(fn.constructor === Fn)
/* 输出
*   undefined
*   private getName
*   false
*   true
*   true
*   true
*   true
*/
思考模型题.jpg

解答

1中a 并没有使用 this 是不会写入构造函数内的,输出就是undefined,2中 fn.getName()存在 fn 的私有作用域内输出就是 private getName

3 fn 和 fn1引用堆内存地址不同为false,4中fn 和 fn1 这个实例上的 __proto__指向同一个原型 Fn.prototype 所以为 true。5、同理。

6、fn 中不存在 hasOwnProperty,根据 __proto__向上一级原型Fn.prototype查找也没有,继续根据 __proto__ 向查找到 Object.prototype 找到了 hasOwnProperty,所以输出为true。7同理fn中没有constructor属性,但是会从fn.prototype中查找。

参考

JavaScript深入之从原型到原型链

更多系列的文章已经放在了 github, 欢迎 start 或 issue

感谢阅读到这里,我是林一一,下次见。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容