共计 4435 个字符,预计需要花费 12 分钟才能阅读完成。
举荐浏览地址:
掘金
大家好,我是林一一,这是一篇对于 JS 原型和原型链中的那些事,理解原型和原型链之前须要理解一些 JS 中的 OOP,大家也能够间接跳到原型和原型链那里。让咱们开始浏览吧。????
本文思维导图
面向对象编程 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 的变量晋升机制
下面的
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__
的属性,这个属性指向所创立类的prototype
,prototype
也是对象同样也有__proto__
这个属性。函数是对象吗?是的,所以函数也有__proto__
这个属性。如果不能确定指定的类,那__proto__
会指向 Object。 - object 这个基类的
__proto__
指向的是本人自身,__proto__
最终指向值是null
。
but 过
__proto__
不是实例的属性,也不是构造函数的属性,在大多数的浏览器中都反对这种非正式的拜访形式。实际上__proto__
来自Object.prototype
,当应用obj.__proto__
时,能够了解成返回了Object.getPrototypeOf(obj)
// 一行代码解析下面的 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
图中蓝色的线就是原型链了
思考题,上面后果都输入什么,为什么?
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
*/
解答
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
感激浏览到这里,我是林一一,下次见。