2018.06.03javascript
funcion A(name) { this.name = name; // 实例基本属性 (该属性,强调私有,不共享) this.arr = [1]; // 实例引用属性 (该属性,强调私用,不共享) this.say = function() { // 实例引用属性 (该属性,强调复用,须要共享) console.log('hello') } } 注意:数组和方法都属于‘实例引用属性’,可是数组强调私有、不共享的。方法须要复用、共享。 注意:在构造函数中,通常不多有数组形式的引用属性,大部分状况都是:基本属性 + 方法。
原型对象的用途是为每一个实例对象存储共享的方法和属性,它仅仅是一个普通对象而已。而且全部的实例是共享同一个原型对象,所以有别于实例方法或属性,原型对象仅有一份。而实例有不少份,且实例属性和方法是独立的。在构造函数中:为了属性(实例基本属性)的私有性、以及方法(实例引用属性)的复用、共享。咱们提倡:html
funcion A(name) { this.name = name; // (该属性,强调私有,不共享) } A.prototype.say = function() { // 定义在原型对象上的方法 (强调复用,须要共享) console.log('hello') } // 不推荐的写法:[缘由](https://blog.csdn.net/kkkkkxiaofei/article/details/46474303) A.prototype = { say: function() { console.log('hello') } }
优势:方法复用java
缺点:数组
function Parent() { this.name = '父亲'; // 实例基本属性 (该属性,强调私有,不共享) this.arr = [1]; // (该属性,强调私有) } Parent.prototype.say = function() { // -- 将须要复用、共享的方法定义在父类原型上 console.log('hello') } function Child(like) { this.like = like; } Child.prototype = new Parent() // 核心 let boy1 = new Child() let boy2 = new Child() // 优势:共享了父类构造函数的say方法 console.log(boy1.say(), boy2.say(), boy1.say === boy2.say); // hello , hello , true // 缺点1:不能传参数 // 缺点2: console.log(boy1.name, boy2.name, boy1.name===boy2.name); // 父亲,父亲,true boy1.arr.push(2); // 修改了boy1的arr属性,boy2的arr属性,也会变化,由于两个实例的原型上(Child.prototype)有了父类构造函数的实例属性arr;因此只要修改了boy1.arr,boy2.arr的属性也会变化。 ---- 原型上的arr属性是共享的。 console.log(boy2.arr); // [1,2] 注意:修改boy1的name属性,是不会影响到boy2.name。由于name是基本属性,不是引用属性。
优势:实例之间独立。app
缺点:函数
因为方法在父构造函数中定义,致使方法不能复用(由于每次建立子类实例都要建立一遍方法)。好比say方法。(方法应该要复用、共享)
function Parent(name) { this.name = name; // 实例基本属性 (该属性,强调私有,不共享) this.arr = [1]; // (该属性,强调私有) this.say = function() { // 实例引用属性 (该属性,强调复用,须要共享) console.log('hello') } } function Child(name,like) { Parent.call(this,name); // 核心 this.like = like; } let boy1 = new Child('小红','apple'); let boy2 = new Child('小明', 'orange '); // 优势1:可传参 console.log(boy1.name, boy2.name); // 小红, 小明 // 优势2:不共享父类构造函数的引用属性 boy1.arr.push(2); console.log(boy1.arr,boy2.arr);// [1,2] [1] // 缺点1:方法不能复用 console.log(boy1.say === boy2.say) // false (说明,boy1和boy2 的say方法是独立,不是共享的) // 缺点2:不能继承父类原型上的方法 Parent.prototype.walk = function () { // 在父类的原型对象上定义一个walk方法。 console.log('我会走路') } boy1.walk; // undefined (说明实例,不能得到父类原型上的方法)
优势:优化
缺点:this
第一次Parent.call(this);从父类拷贝一份父类实例属性,做为子类的实例属性,第二次Child.prototype = new Parent();建立父类实例做为子类原型,此时这个父类实例就又有了一份实例属性,但这份会被第一次拷贝来的实例属性屏蔽掉,因此多余。.net
为啥是两次?若是仍是,不清楚,能够看文末,我会详细讲解!prototype
function Parent(name) { this.name = name; // 实例基本属性 (该属性,强调私有,不共享) this.arr = [1]; // (该属性,强调私有) } Parent.prototype.say = function() { // --- 将须要复用、共享的方法定义在父类原型上 console.log('hello') } function Child(name,like) { Parent.call(this,name,like) // 核心 第二次 this.like = like; } Child.prototype = new Parent() // 核心 第一次 <!--这里是修复构造函数指向的代码--> let boy1 = new Child('小红','apple') let boy2 = new Child('小明','orange') // 优势1:能够传参数 console.log(boy1.name,boy1.like); // 小红,apple // 优势2:可复用父类原型上的方法 console.log(boy1.say === boy2.say) // true // 优势3:不共享父类的引用属性,如arr属性 boy1.arr.push(2) console.log(boy1.arr,boy2.arr); // [1,2] [1] 能够看出没有共享arr属性。 注意:为啥要修复构造函数的指向? console.log(boy1.constructor); // Parent 你会发现实例的构造函数竟然是Parent。 而实际上,咱们但愿子类实例的构造函数是Child,因此要记得修复构造函数指向。修复以下 Child.prototype.constructor = Child;
其实Child.prototype = new Parent()console.log(Child.prototype.__proto__ === Parten.prototype); // true
经过这种方式,砍掉父类的实例属性,这样在调用父类的构造函数的时候,就不会初始化两次实例,避免组合继承的缺点。
优势:
缺点:
缘由是:不能判断子类实例的直接构造函数,究竟是子类构造函数仍是父类构造函数。
function Parent(name) { this.name = name; // 实例基本属性 (该属性,强调私有,不共享) this.arr = [1]; // (该属性,强调私有) } Parent.prototype.say = function() { // --- 将须要复用、共享的方法定义在父类原型上 console.log('hello') } function Child(name,like) { Parent.call(this,name,like) // 核心 this.like = like; } Child.prototype = Parent.prototype // 核心 子类原型和父类原型,实质上是同一个 <!--这里是修复构造函数指向的代码--> let boy1 = new Child('小红','apple') let boy2 = new Child('小明','orange') let p1 = new Parent('小爸爸') // 优势1:能够传参数 console.log(boy1.name,boy1.like); // 小红,apple // 优势2: console.log(boy1.say === boy2.say) // true // 缺点1:当修复子类构造函数的指向后,父类实例的构造函数指向也会跟着变了。 具体缘由:由于是经过原型来实现继承的,Child.prototype的上面是没有constructor属性的,就会往上找,这样就找到了Parent.prototype上面的constructor属性;当你修改了子类实例的construtor属性,全部的constructor的指向都会发生变化。 没修复以前:console.log(boy1.constructor); // Parent 修复代码:Child.prototype.constructor = Child 修复以后:console.log(boy1.constructor); // Child console.log(p1.constructor);// Child 这里就是存在的问题(咱们但愿是Parent)
function Parent(name) { this.name = name; // 实例基本属性 (该属性,强调私有,不共享) this.arr = [1]; // (该属性,强调私有) } Parent.prototype.say = function() { // --- 将须要复用、共享的方法定义在父类原型上 console.log('hello') } function Child(name,like) { Parent.call(this,name,like) // 核心 this.like = like; } Child.prototype = Object.create(Parent.prototype) // 核心 经过建立中间对象,子类原型和父类原型,就会隔离开。不是同一个啦,有效避免了方式4的缺点。 <!--这里是修复构造函数指向的代码--> Child.prototype.constructor = Child let boy1 = new Child('小红','apple') let boy2 = new Child('小明','orange') let p1 = new Parent('小爸爸') 注意:这种方法也要修复构造函数的 修复代码:Child.prototype.constructor = Child 修复以后:console.log(boy1.constructor); // Child console.log(p1.constructor);// Parent 完美
Object.create() 的第二参数,是可选的。
- Object.create() 的内部原理: // 其中,o 是新建立对象的原型(对象) function object(o) { function F() {} F.prototype = o return new F() } 注意:以前,Object.create()没有出现以前,就是采用的这种方式。 参见《js高级程序设计》P170
如下是个人我的看法,(若有不对,还请指正):new 产生的实例,优先获取构造函数上的属性;构造函数上没有对应的属性,才会去原型上查找;若是构造函数中以及原型中都没有对应的属性,就会报错。
Object.create() 产生的对象,只会在原型上进行查找属性,原型上没有对应的属性,就会报错。
let Base1 = function() { this.a = 1 } let o1 = new Base1() let o2 = Object.create(Base1.prototype) console.log(o1.a); // 1 console.log(o2.a); // undefined let Base2 = function() {} Base2.prototype.a = 'aa' let o3 = new Base2() let o4 = Object.create(Base2.prototype) console.log(o3.a); // aa console.log(o4.a); // aa let Base3 = function() { this.a = 1 } Base3.prototype.a = 'aa' let o5 = new Base3() let o6 = Object.create(Base3.prototype) console.log(o5.a); // 1 console.log(o6.a); // aa
funciton Func(name) { this.name = name } let p = new Func('小红')
new 的过程,作了啥?作了四件事。
obj.__proto__ = Func.prototype 就是:将新对象的__proto__ 指向构造函数的prototype
let result = Func.call(obj) 就是:使用call或apply,将构造函数的this绑定到新对象,并执行构造函数
若是是引用类型,就返回这个引用类型的对象。若是是值类型或没有return,则返回空对象obj。 if (typeof(result) === "object"){ func=result; } else{ func=obj; // 默认返回 } 注意:js中的构造函数,是不须要有返回值的,因此默认返回的是新建立的空对象obj
‘new 的过程’的第三步,其实就是执行了父类构造函数。
call的做用是改变函数执行时的上下文。好比:A.call(B)。其实,最终执行的仍是A函数,只不过是用B来调用而已。因此,你就懂了Parent.call(this,name,like) ,也就是执行了父类构造函数。