new的过程以及继承方法
new的过程:
首先 我们要知道new关键字的目的是什么 当然就是构建对象。
function Student(name,age) {
this.name = name;
this.age = age;
}
这样一段构造函数也就是声明了一个类 接受两个参数然后把参数赋值给this的两个属性上。这个时候我们不难看出this指向的是谁就非常重要了。
this指向的就是函数的这一段执行上下文。函数的执行上下文会在函数调用的时候创建,所以this是在函数调用的时候确定,这和作用域(在函数创建的时候确定的)完全不同,所以无论怎么样这个构造函数需要被调用。只是用new
关键字调用和普通调用有很大的区别。用new调用的时候,首先会创建一个空的对象,然后将构造函数的原型对象(prototype)与这个空对象进行链接,相当于给空对象的一个非官方的属性_proto_进行赋值操作,空对象._proto_ = student.prototype。
下面该说一下this了,在做完这些的时候,构造函数就快要执行了,在执行的时候需要把this的指向改变,调用的时候创建了执行上下文,没有使用new关键字的时候,这个函数就是正常执行,他的执行上下文就是window对象(非严格模式);
使用new关键字之后这个函数的执行上下文被显示的改变为这个之前创建的空对象了,this是不指向window的而是指向了这个空对象。
然后执行构造函数内的代码。最后返回这个对象(也就是返回了this 隐式的return this),说到这里,我们就可以清晰的知道用new调用和不用new调用时的区别了。执行上下文改变了,
在调用时的执行上下文就不再是全局对象了,而是这个空的对象了。 如果构造函数return一个基本类型就会被忽略。但是显示的指定返回值是一个对象,就会把return this覆盖掉了,
return的是你显示设置的那个对象而不是this了 千万注意!!!!
所以不用new调用构造函数,this指向window那就给window上创建一堆属性然后赋值,window上的属性是什么?全局变量呗。用了new创建了空对象,属性都是空对象上的
然后返回这个对象。然后找个变量一赋值,好了堆里面也开辟内存空间了,栈里面也找到引用了。对象就出来了。所以说隐式的return this也是个很关键的点。
JavaScript上总是有很多隐式的事情需要我们去了解啊。
不得不说的6种继承(来自高程)
大体上就是使用原型链实现继承或者借用构造函数实现继承,然后组合着实现继承
1 原型继承:
构造函数A的显示原型属性指向构造函数B的实例,那么A的实例就会继承B构造函数上的方法。
function A (){};
function B() {
this.say = function () {
console.log("这个是B构造函数内的say方法");
}
}
A.prototype = new B();
var a = new A();
a.say(); //打印出 这个是B的构造函数内的say方法
这就是比较标准的原型继承,但是考虑到原型对象上都有一个constructor属性, 在实现原型的链接过程中,对构造函数A的原型进行了覆盖,也就切断了A.protoyype
与之前对象的关系,之前是一个含有constructor属性的对象,重新指向了构造函数B的实例,而B的实例中并没有constructor属性。所以如果以后有所需要,应该显示的
创建一些这个属性,
//插入一句话,所有原型链的末端都是Object.prototype。而防止出现死循环就把Object.prototype的__proto__指向了null;
所以在继承的时候 无论怎样创建都会继承来自Object.prototype上的属性,这是一切原型链的尽头。
需要考虑 如果a实例在A的显示原型指向构造函数B的实例之前就被创建了。那么a并不可以找到构造函数B上的属性。
原型继承的比较不好地方就是,一旦要是修改原型链上的属性,就会影响这条原型链上所有继承了这个属性的对象。
但是在实例上可以自己创建一个同名方法。在实例调用属性或者方法的时候首先是看实例本身有没有这个方法,没有才会去原型链上查找。
2 借用构造函数
也就是call和apply哥俩的功劳。谁让这哥俩可以改变this的指向,也就是显示的改变函数或者方法的执行上下文对象。
function C() {
this.color = [1,2,3];
}
function D() {
C.call(this);
}
var d = new D();
d.color.push(4);
var c = new C();
console.log("c:" + c.color );// c:1,2,3
console.log("d:" + d.color ); d:1,2,3,4
用call或者apply显示的改变函数执行上下文,让这个超类型构造函数(也就是父类型)里面的代码在子类型构造函数内用子类型的执行上下文执行一遍,this的指向变了。也就相当于
把父类型构造函数的代码抄一遍放进子类型狗函数里面。call和apply也可以添加参数。但是这会导致所有代码都在构造函数里面,每new一个对象创建一个新的副本,大家之间没有联系,修改一个
别的都不会变。所以复用就无从谈起了吧。这就是借用构造函数并不适合单独使用来创建对象。
3:组合继承 (组合继承也就是合并优点,解决缺陷)
构造函数的特点,每个从构造函数new出来的实例,他们的内在属性和方法只要是构造函数给的,就不会互相影响,修改一个实例中的方法别的实例没有任何改变。
所以构造函数用来创建属性把会出现互补干扰问题的属性放在构造函数中,把需要复用的方法,放在原型对象上。
function E(name) {
this.name = name;
}
E.prototype.sayName = function () {
console.log("我的名字:"+this.name);
};
function F(name) {
E.call(this,name);
this.age = "22"
}
F.prototype = new E();
var f = new F("tom"); f.sayName() //打印出tom;
var e = new E("tommm");e.sayName() //打印出tommm;
console.log(f instanceof E);//true
好了这回我们就可以知道f可以调用E.prototype上的方法。实现了继承。方法用原型继承,属性借用构造函数继承。属性互不干扰,方法得到重用,看起来就很不错。
当然也是比较合理的继承方法。
4 原型式继承
借助原型的帮助,根据已有的对象创建新对象。
function obejct(o) {
function F() {}
F.prototype = o;
return new F();
}
首先我们要有一个被继承的对象。也就是父对象 然后再这个object方法内部,我们创建一个构造函数,单纯的用来完成原型链接。这个构造函数没有任何方法和属性。
我把他看作一个中间商,用于搭桥,然后这个构造函数new出来的对象自然就是继承了o的属性何方法。当然这个中间商也可以加工加工。
在ECMAScrip5中已经官方支持了这个方法使用Object.create();当然缺点也是有的如果o里面有一些引用类型的属性。那么所有使用这种方式创建出的子对象
对这个属性进行修改都会导致父对象的这个属性修改。原型继承嘛,这个缺点一直存在。
5 寄生式继承
依靠原型式继承将所需要增强的功能封装在一个函数内
function createAnother(o){
var clone = obejct(o);//object方法是之前创建的
clone.sayHi = function () {
console.log("hi");
};
return clone
}
var o ={};
var l = createAnother(o)
l.sayHi(); //hi
var m = createAnother(o);
o.sayHi = function () {
console.log("hello")
};
var x = createAnother(o);
x.sayHi();//hi
o.sayHi();//hello
l.sayHi();//hi
这个createAnother就是使用原型式继承的方式,并给生成的子对象增强了一个sayHi的方法; 如果子对象修改了这个方法对别的子对象没有任何影响。
这种对象是在创建对象后加在对象身上的如果有影响那就太奇怪了。封装了增强功能。而且子对象互不影响。把需要复用的代码放在原型上,也就是o对象。
把需要增强的互相不影响的代码放在createAnother中就可以比较好的实现继承了。
6 寄生组合式继承
考虑到组合继承需要调用两次父类型构造函数。构造函数嘛普遍都比较大调用一次就很长的代码,调用两次不就更长了。两次分别是在借用构造函数和实现原型链接这两步上
而寄生组合式继承就使用了原型式继承的方法,
function inheritPrototype(G,H) {
var prototype = obejct(H);
prototype.constructor = G;
G.prototype = prototype;
}
考虑一下G与H, 这个函数内部的变量prototype就是继承于构造函数H的原型对象。这个作用十分关键。在object函数内执行了 1:创建一个空的构造函数,
2:让空的构造函数的原型对象指向构造函数H的原型对象。然后用空的构造函数生成一个孔对象并返回。所以说这个空构造函数和构造函数H的显示原型指向同一个对象
也就是他俩有着相同的原型对象。他这两个构造函数new出来的对象都可以调用这个H.prototype内的属性和方法。然后再inheritPrototy这个函数内
把这个空构造函数new出来的对象赋值给了prototype变量。然后把构造函数G的原型对象指向prototype,这一步就是原型链接。而巧妙的避开了在原型链接步骤上的
父类型构造函数的调用。我们都是要使用构造函数H原型对象上的属性和方法和构造函数2无关,这就十分巧妙的避开了一次父类型构造函数的调用。大大节约时间和性能。
总的来说6种继承方式用自己的理解算是写了一遍。理解了构造函数和原型对象这种关系。几种继承也算是展示了几种思想。寄生式继承的封装思想。 寄生组合式继承的巧妙
实现原型链接的方法。借用构造函数时合理使用call改变执行上下文的方法。都是值得我们以后学习过程中思考的,算是开阔了眼界。
这些都是ES5的继承方式,ES6的还没有开始系统的研究,但是这六种继承模式的优缺点是值得去思考的。