转载1自:https://zhuanlan.zhihu.com/p/35790971
转载2自:https://segmentfault.com/a/1190000014717972
转载3自:https://blog.csdn.net/qq_33562825/article/details/70670109?utm_source=blogxgwz8
转载4自:https://www.pianshen.com/article/7557383774/
javascript
有两大链条,一条是作用域链
,一条是原型链,本文介绍原型链
首先要搞明白几个概念:
- 函数(function)
- 函数对象(function object)
- 本地对象(native object)
- 内置对象(build-in object)
- 宿主对象(host object)
1、函数,函数对象
函数就是对象,代表函数的对象就是函数对象
官方定义, 在Javascript中,每一个函数实际上都是一个函数对象.
JavaScript代码中定义函数,或者调用Function创建函数时,最终都会以类似这样的形式调用Function函数:var newFun = new Function(funArgs, funBody)
2、本地对象
ECMA-262 把本地对象(native object)定义为“独立于宿主环境的 ECMAScript 实现提供的对象”。简单来说,本地对象就是 ECMA-262 定义的类(引用类型)。它们包括:
Object,Function,Array,String,Boolean,Number
Date,RegExp,Error,EvalError,RangeError,ReferenceError,SyntaxError,TypeError,URIError.
3、内置对象
ECMA-262 把内置对象(built-in object)定义为“由 ECMAScript 实现提供的、独立于宿主环境的所有对象,在 ECMAScript 程序开始执行时出现”。这意味着开发者不必明确实例化内置对象,它已被实例化了。ECMA-262 只定义了两个内置对象,即 Global 和 Math (它们也是本地对象,根据定义,每个内置对象都是本地对象)。
4、宿主对象
宿主对象就是执行JS脚本的环境提供的对象。对于嵌入到网页中的JS来说,其宿主对象就是浏览器提供的对象,所以又称为浏览器对象,如IE、Firefox等浏览器提供的对象。不同的浏览器提供的宿主对象可能不同,即使提供的对象相同,其实现方式也大相径庭!这会带来浏览器兼容问题,增加开发难度。
浏览器对象有很多,如Window和Document等等。
一、原型 prototype
和 __proto__
- 每个对象都有一个
__proto__
属性,并且指向它的prototype
原型对象 - 每个构造函数都有一个
prototype
原型对象 prototype
原型对象里的constructor
指向构造函数本身
有的同学可能会问prototype
和 __proto__
有什么用呢?
实例对象的__proto__
指向构造函数的prototype
,从而实现继承。
prototype
对象相当于特定类型所有实例对象都可以访问的公共容器
看一下代码就清楚了
function Person(nick, age){
this.nick = nick;
this.age = age;
}
Person.prototype.sayName = function(){
console.log(this.nick);
}
var p1 = new Person('Byron', 20);
var p2 = new Person('Casper', 25);
p1.sayName() // Byron
p2.sayName() // Casper
p1.__proto__ === Person.prototype //true
p2.__proto__ === Person.prototype //true
p1.__proto__ === p2.__proto__ //true
Person.prototype.constructor === Person //true
什么是原型链?
原型同样也可以通过 __proto__
访问到原型的原型,比方说这里有个构造函数 Person 然后“继承”前者的有一个构造函数 People,然后 new People 得到实例 p
当访问 p 中的一个非自有属性的时候,就会通过 __proto__
作为桥梁连接起来的一系列原型、原型的原型、原型的原型的原型直到 Object 构造函数为止。
这个搜索的过程形成的链状关系就是原型链
原型链继承
js主要是通过原型链实现继承,原型链的构建是通过将一个类型的实例赋值给另外一个构造函数的原型实现的
function SuperType () {
this.property = true
}
SuperType.prototype.getSuperValue = function () {
return this.property
}
function SubType () {
this.subproperty = false
}
// 继承了SuperType //
SubType.prototype = new SuperType()
SubType.protype.getSubValue = function () {
return this.subproperty
}
var instance = new SubType()
实现的本质:重写原型对象,用一个新的类型的实例去替代
SubType Prototype === SuperType构造函数的实例
注意:
1.默认的原型
所有的引用类型都默认继承Object,这个继承也是通过原型链实现的
2.原型链的问题
a.对象实例共享所有继承的属性和方法
b.创建子类型的实例的时候,不能传递参数
拓展
JavaScript是一门面向对象的设计语言,在JS里除了null
和undefined
,其余一切皆为对象。
其中Array/Function/Date/RegExp
是Object对象的特殊实例实现,
Boolean/Number/String
也都有对应的基本包装类型的对象(具有内置的方法)。
传统语言是依靠class
类来完成面向对象的继承和多态等特性,
而JS使用原型链和构造器来实现继承,依靠参数arguments.length
来实现多态。
并且在ES6里也引入了class
关键字来实现类。
继承方式1,通过构造函数的方式继承
function Father(){
this.name = 'zhangsan'
}
Father.prototype.say = function(){
console.log('我是father的原型方法')
}
function Son(){
Father.call(this)
this.age = 18
}
var a = new Son()
var b = new Father()
console.log(b.say) // function(){console.log('我是father的原型方法')}
console.log(a) // Son {name:'zhangsan',age:18}
console.log(a.say) //undefiend
有call继承Father的实例对象。
缺点就是:无法继承原型下的属性或者方法。
2、通过原型链继承
function Father() {
this.name = 'zhangsan'
this.arr = [1,2,3]
}
function Son(){
this.age = 18;
}
Son.prototype = new Father()
console.log(new Son)
var a = new Son()
var b = new Son()
a.arr.push(4)
console.log(a.arr) //[1,2,3,4]
console.log(b.arr) //[1,2,3,4]
有上面代码看出,通过此种方法实现继承是有缺点的,
实例化两个Son 的实例对象,如果在父类中有引用类型的属性,会被共享,意思就是一个改动,全部改动
3、组合继承
// 3混合继承
function Father() {
this.name = 'zhangsan'
this.arr = [1,2,3]
}
function Son(){
Father.call(this)
this.age = 18
}
Son.prototype = new Father()
var a =new Son()
var b =new Son()
a.arr.push(4)
console.log(a.arr,b.arr) //[1,2,3,4] [1,2,3]
// 虽然通过这种方式继承解决了被共享的问题,但是出现了下面的问题。
// 在实例化Son时候,调用了父级的构造函数2次
4、组合继承的另外一种写法
function Father() {
this.name = 'zhangsan'
this.arr = [1,2,3]
}
function Son(){
Father.call(this)
this.age = 18
}
Son.prototype = Father.prototype
var a =new Son()
var b =new Son()
a.arr.push(4)
console.log(a.arr,b.arr) //[1,2,3,4] [1,2,3]
这样写虽然解决了调用父级构造函数两次的问题,
但是有这样的一个问题,就是子元素new 出来的是实例对象的 指向不明确,
不知道是父级里的还是子元素里面的。
5、最优的继承方法
function Father() {
this.name = 'zhangsan'
this.arr = [1,2,3]
}
function Son(){
Father.call(this)
this.age = 18
}
Son.prototype = Object.create(Father.prototype)
Son.prototype.constructor = Son
var a =new Son()
var b =new Son()
a.arr.push(4)
console.log(a.arr,b.arr) //[1,2,3,4] [1,2,3]
解决了子元素的实例对象的constructor 指向不明的问题。