We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
JavaScript是弱对象的语言,但是也有面向对象编程的封装与继承的方法,而原型和他们有密切的联系。
让我们从一个例子开始:
function Foo(){ } const f1 = new Foo() console.log(Foo.prototype===f1.__proto__)//?
这里的prototype和__proto__都是什么?输出的内容又是什么?
prototype
__proto__
prototype,它是函数所独有的属性,也就是Function.prototype,从一个函数指向一个对象。它的含义是函数通过 new 关键字构造的实例的原型对象(可以理解为所有该函数的实例的公有属性),也就是这个函数所创建的实例的原型对象。
Function.prototype
new
要了解原型链,首先要了解__proto__,它是对象所独有的,__proto__属性值都指向一个对象,就是父对象构造函数的prototype(也就是我们通常说的原型),是对象获取其原型的浏览器实现,目前正式的规范是通过Object.getPrototypeOf()获取,所以我们上面的例子打印的是true。 它的作用就是当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的__proto__属性所指向的父对象里找,如果父对象也不存在这个属性,则继续往父对象的__proto__属性所指向的那个对象(可以理解为爷爷对象)里找,如果还没找到,则继续往上找….直到Object.prototype.__proto__,也就是null,而这一链式关系,就被我们成为原型链:
Object.getPrototypeOf()
true
Object.prototype.__proto__
null
上图比较包含了完整的原型和构造函数关系,我们可以仅掌握f1的原型链:
f1
f1.__proto__ === Foo.prototype
Foo.prototype.__proto__ === Object.prototype
Object.prototype.__proto__ === null
小结一下:
constructor
Foo.__proto__=== Function.prototype
Object.prototype
Function
Function.__proto__=== Function.prototype
Object
Object.__proto__===Function.prototype
new关键字会返回一个新的实例,同时会给新对象构造原型链:
__ proto__
this
其原理为:
function _new (){ //1取出函数名,并且将函数名从参数中删除 var Func = [].shift.call(arguments) //2.1构造一个空对象 var obj = {}; //2.2空对象继承Func.prototype 【2.1 2.2可用 Object.create(Func.prototype)代替 (Object.create使用现有的对象来提供新创建的对象的__proto__)】 obj.__proto__ = Func.prototype; //3. 执行构造函数 var reuslt = Func.apply(obj,arguments) // 4. 返回值:如果无返回值或者返回一个非对象值,则将新对象返回;如果返回值是一个新对象的话那么直接返回该对象。 if (result !== null && typeof result === "object") { return result; } else { return obj; } }
为了解决从原型对象生成实例的问题,Javascript提供了一个构造函数(Constructor)模式。
构造函数其实也是普通的函数,只不过内部使用了 this 变量,对构造函数使用 new 运算符,就能生成实例,且 this 变量会绑定在新的实例上。
function Dog (name) { this.a = 1 this.name = name this.bark = function(){console.log('wangwang')} } var d1 = new Dog('one') // new一实例 var d2 = new Dog('two') // new一实例
此时 f1 就是 Foo 的一个实例,同时 f1 会拥有一个 constructor 的属性指向 Foo
Foo
d1.constructor === Dog //true d1 instanceof Dog // true
缺点:公共部分的属性不能复用,造成资源浪费
d1.bark===d2.bark //false
Javascript规定,每一个构造函数都有一个prototype属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承。 且构造函数的 constructor 属性就是该构造函数。
function Dog (name) { this.name = name } Dog.prototype.bark = function(){console.log('wangwang')} Dog.prototype.constructor === Dog //true 构造函数的 constructor 属性就是该构造函数 var d1 = new Dog('one') // new一实例 var d2 = new Dog('two') // new一实例 d1.bark===d2.bark //true
isPrototypeOf
hasOwnProperty
in
Dog.prototype.isPrototypeOf(d1) //true d1.hasOwnProperty('name') //true d1.hasOwnProperty('bark') //false for(let p in d1){ console.log(p) //line1:name line2:bark }
继承是开发中比较常见的开发方法,JavaScript中又有哪些继承方式呢?
仅继承父类自身的属性和方法。 通过这种方式继承的对象不包含父类的原型链上的属性,因为整个过程中并没有调用 new 产生实例,相当子类将父类的独享属性拷贝了一份到自己内存中。其后对子类做任何修改都不会影响到父类。
核心:在子类构造函数的内部调用超累构造函数,继承父类的属性和方法 场景:子类仅需要独享父类的自身属性 缺点:
function Animal(){ this.type = 'animal' } function Dog (name) { Animal.apply(this,arguments) //重点 this.name = name } var d1 = new Dog('one') var d2 = new Dog('two') d1.type //animal d2.type //animal
继承父类自身的属性及原型链上的属性和方法。
核心:子类构造函数的prototype指向父类的一个实例,继承原型链上的属性和方法 场景:子类可以与其他子类共享父类自身和__proto__上的属性 缺点:
function Animal(){ this.type = 'animal' this.other = {} } function Dog (name) { this.name = name } Dog.prototype = new Animal() // Dog.prototype.__proto__ === Animal.prototype ,同时这里会使得 Dog.prototype.constructor === Animal Dog.prototype.constructor = Dog var d1 = new Dog('one') var d2 = new Dog('two') d1.type //'animal' d2.type //'animal' d1.other.a = 1 // 修改d1的引用属性 d2.other.a // 1 d2的也被修改
使用构造函数,继承父类自身的属性和方法,使得每个子类实例的属性和方法隔离;使用原型继承,继承父类prototype上的属性和方法,共用这些属性和方法。
核心:构造函数继承父类自身的属性和方法+原型继承原型链上的属性和方法 场景:子类实例从父类继承的属性隔离,且可以使用父类原型链上的属性 缺点:
function Animal(){ this.type = 'animal' this.other = {} } Animal.prototype.say = function(str){ console.log('saying...',str) } function Dog (name) { Animal.call(this,arguments) this.name = name } Dog.prototype = new Animal() //疑问 可以 Dog.prototype = Animal.prototype ,但是对Dog.prototype的修改也会影响Animal!!! Dog.prototype.constructor = Dog // 实例测试 var d1 = new Dog('one') var d2 = new Dog('two') d1.say===d2.say // true d1.other.a = 1 // 修改d1的引用属性 d2.other.a // undefined
与原型链继承思想类似,使用 Object.create 或者是其他可以返回克隆对象的函数,对现有的对象实例继承其属性。
Object.create
核心:**Object.create**** 返回新对象** 场景:需要继承一个实例且只有部分方法不一样 缺点:
**Object.create**
a.__proto__=cloneFun.prototype
区别:
function Dog (name) { this.name = name this.other = {} this.say = function(){ console.log('wangwang') } } var d1 = new Dog('one') var specialDog = Object.create(d1); specialDog.__proto__ === d1 // true specialDog.say = function(){ console.log('special: wangwang') } // 实例测试 d1.say() // wangwang specialDog.say() //special: wangwang specialDog.other.a = 1 // 修改specialDog的引用属性 d1.other.a // 1 d1的也被修改
ps:
//Object.create一个参数时相当于 function _Object(o){ function A(){} A.prototype = o //传入对象o作为临时构造函数的原型对象 A.prototype.constructor = A //修改构造函数指向 return new A() //返回新的对象 这个新对象的 .__proto__ === A.prototype === o }
使用构造函数,继承父类自身的属性和方法,使得每个子类实例的属性和方法隔离;使用寄生的形式增强子类的原型对象,继承父类prototype上的属性和方法(避免了组合集成里面父类构造函数执行两次的缺陷),共用这些属性。 核心:构造函数继承父类自身的属性及方法+寄生继承父类原型链上的属性和方法 场景:子类实例从父类继承的属性和方法隔离,且可以使用父类原型链上的属性和方法 缺点:
function parasitic(subType,superType){ var _prototype = Object.create(superType.prototype) //浅拷贝父类的prototype 相当于_prototype.__proto__ = superType.prototype _prototype.constructor = subType//修改构造函数指向 subType.prototype = _prototype //修改子类的原型 } function Animal(){ this.type = 'animal' this.other = {} } Animal.prototype.say = function(str){ console.log('saying...',str) } function Dog (name) { Animal.call(this,arguments) this.name = name } parasitic(Dog, Animal) //类测试 Dog.__proto__ === Animal // false Dog是一个函数,所以他的原型是Function.prototype Dog.prototype.__proto__ === Animal.prototype //true // 实例测试 var d1 = new Dog('one') var d2 = new Dog('two') d1.say===d2.say // true d1.other.a = 1 // 修改d1的引用属性 d2.other.a // undefined
es6提供了extend关键字的继承。 核心:super函数继承父类的属性和方法+子类直接继承父类的原型链上的属性和方法 场景:子类实例从父类继承的属性隔离,且可以使用父类原型链上的属性
Dog.__proto__
extends
Dog.__proto__ === Animal
Dog.prototype
Animal.prototype
class Animal{ constructor(){ this.type = 'animal' this.other = {} } } class Dog extends Animal{ constructor(props){ super(props) this.name = props.name } say(){ console.log('wangwang') } } //类测试 Dog.__proto__ === Animal // true 继承的原型就是Animal, 这跟我们模拟的继承不一样 Dog.prototype.__proto__ === Animal.prototype // true,Dog函数的原型对象的相当于Animal的原型属性 与我们模拟的继承一样 //实例测试 var d1 = new Dog('one') var d2 = new Dog('two') d1.say===d2.say // true d1.other.a = 1 // 修改d1的引用属性 d2.other.a // undefined
class
The text was updated successfully, but these errors were encountered:
No branches or pull requests
JavaScript是弱对象的语言,但是也有面向对象编程的封装与继承的方法,而原型和他们有密切的联系。
原型和原型链
了解原型和原型链
让我们从一个例子开始:
这里的
prototype
和__proto__
都是什么?输出的内容又是什么?原型(prototype)
prototype
,它是函数所独有的属性,也就是Function.prototype
,从一个函数指向一个对象。它的含义是函数通过new
关键字构造的实例的原型对象(可以理解为所有该函数的实例的公有属性),也就是这个函数所创建的实例的原型对象。原型链
要了解原型链,首先要了解
__proto__
,它是对象所独有的,__proto__
属性值都指向一个对象,就是父对象构造函数的prototype
(也就是我们通常说的原型),是对象获取其原型的浏览器实现,目前正式的规范是通过Object.getPrototypeOf()
获取,所以我们上面的例子打印的是true
。它的作用就是当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的
__proto__
属性所指向的父对象里找,如果父对象也不存在这个属性,则继续往父对象的__proto__
属性所指向的那个对象(可以理解为爷爷对象)里找,如果还没找到,则继续往上找….直到Object.prototype.__proto__
,也就是null
,而这一链式关系,就被我们成为原型链:上图比较包含了完整的原型和构造函数关系,我们可以仅掌握
f1
的原型链:f1.__proto__ === Foo.prototype
Foo.prototype.__proto__ === Object.prototype
Object.prototype.__proto__ === null
小结一下:
prototype
,函数通过 new 关键字构造的实例的原型对象(可以理解为所有该函数的实例的公有属性)__proto__
且其指向构造函数的prototype
, 还有constructor
__proto__
指向构造的查找对象属性的追溯链条__proto__
都指向Function.prototype
,Foo.__proto__=== Function.prototype
__proto__
最终都会指向Object.prototype
,然后指向null
,Object.prototype.__proto__ === null
_ _Function
比较特殊,他属于自身的实例(constructor
也是自身),所以Function.__proto__=== Function.prototype
Object
也比较特殊,它属于Function
的实例,所以Object.__proto__===Function.prototype
。new关键字做了什么
new
关键字会返回一个新的实例,同时会给新对象构造原型链:Function.prototype
(新对象的__ proto__
等于 构造函数的prototype
)this
指向刚刚创建的新对象;其原理为:
对象封装
为了解决从原型对象生成实例的问题,Javascript提供了一个构造函数(Constructor)模式。
构造函数(Constructor)
构造函数其实也是普通的函数,只不过内部使用了
this
变量,对构造函数使用new
运算符,就能生成实例,且this
变量会绑定在新的实例上。此时
f1
就是Foo
的一个实例,同时f1
会拥有一个constructor
的属性指向Foo
相关判断方法
缺点
构造函数+原型链 (prototype)
Javascript规定,每一个构造函数都有一个
prototype
属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承。且构造函数的
constructor
属性就是该构造函数。相关判断方法
isPrototypeOf
判断函数的prototype和实例的关系hasOwnProperty
判断实例的方法是自身的属性还是从prototype继承而来in
遍历实例的所有属性(自身+继承)对象继承
继承是开发中比较常见的开发方法,JavaScript中又有哪些继承方式呢?
构造函数继承(apply或者call)
仅继承父类自身的属性和方法。
通过这种方式继承的对象不包含父类的原型链上的属性,因为整个过程中并没有调用 new 产生实例,相当子类将父类的独享属性拷贝了一份到自己内存中。其后对子类做任何修改都不会影响到父类。
核心:在子类构造函数的内部调用超累构造函数,继承父类的属性和方法
场景:子类仅需要独享父类的自身属性
缺点:
原型继承(prototype)
继承父类自身的属性及原型链上的属性和方法。
核心:子类构造函数的prototype指向父类的一个实例,继承原型链上的属性和方法
场景:子类可以与其他子类共享父类自身和__proto__上的属性
缺点:
组合继承 (构造函数继承+原型继承)
使用构造函数,继承父类自身的属性和方法,使得每个子类实例的属性和方法隔离;使用原型继承,继承父类prototype上的属性和方法,共用这些属性和方法。
核心:构造函数继承父类自身的属性和方法+原型继承原型链上的属性和方法
场景:子类实例从父类继承的属性隔离,且可以使用父类原型链上的属性
缺点:
寄生式继承(Object.create)
与原型链继承思想类似,使用
Object.create
或者是其他可以返回克隆对象的函数,对现有的对象实例继承其属性。核心:
**Object.create**
** 返回新对象**场景:需要继承一个实例且只有部分方法不一样
缺点:
Object.create
的核心是a.__proto__=cloneFun.prototype
,所以会跟原型链继承一样,有引用类型值的误修改问题区别:
ps:
组合寄生继承
使用构造函数,继承父类自身的属性和方法,使得每个子类实例的属性和方法隔离;使用寄生的形式增强子类的原型对象,继承父类prototype上的属性和方法(避免了组合集成里面父类构造函数执行两次的缺陷),共用这些属性。
核心:构造函数继承父类自身的属性及方法+寄生继承父类原型链上的属性和方法
场景:子类实例从父类继承的属性和方法隔离,且可以使用父类原型链上的属性和方法
缺点:
ES6 class extend继承
es6提供了extend关键字的继承。
核心:super函数继承父类的属性和方法+子类直接继承父类的原型链上的属性和方法
场景:子类实例从父类继承的属性隔离,且可以使用父类原型链上的属性
Dog.__proto__
)就是extends
关键字后的父类,Dog.__proto__ === Animal
(这点与模拟的继承有很大的不同)Dog.prototype
),其原型为extends
关键字后的父类的实例的公共属性(Animal.prototype
)结论
class
直接指向父类,而函数的均指向Function
The text was updated successfully, but these errors were encountered: