一.基础
1.构造函数
(1)什么是构造函数?
用new关键字调用的统称为构造函数
function Person(){...}
let person1 = new Person() // Person就是person1的构造函数
(2)为什么使用构造函数
场景:
var p1 = { name: 'zs', age: 6, gender: '男', hobby: 'basketball' }; // 录入单条信息
如果有大量的这样的数据呢?不能这么单条单条的写把
此时构造函数上场表演了:
function Person(name, age,gender, hobby) { this.name = name;
this.age = age;
this.gender = gender;
this.hobby = hobby;
}
var p1 = new Person('xx',12,'男','xx') // 通过构造函数创建对象 var p2 = new Person('xx',10,'女','yy')
(3)构造函数调用过程
1.创建一个新的实例对象
2.设置创建实例的原型,指向构造函数的prototype属性
3.构造函数内部的this指向当前实例
4.执行函数内的代码体
5.返回这个创建对象(如果构造函数内有return语句,并且return后跟着一个对象,则 返回return后指定的这个对象)
2.基本概念
(1)JavaScript中一切引用类型都是对象,对象就是属性的集合。
Array类型、Function类型、Object类型、Date类型、RegExp类型等都是引用类 型。
(2)函数就是对象
function Person(){..}
console.log(Person().constructor) // [Function:Function]
console.log(Function().constructor) // [Function:Function]
console.log(Object().constructor) // [Function:Function]
由上面代码段得出:
1.Function函数是普通声明函数的构造函数。
2.Function函数是自己的构造函数
3.Function函数也是Object()这类内置对象的构造函数
函数和对象的包含关系如下:
则得出:对象是由函数创建,函数是Function实例对象
二.分开了解constructor,prototype,proto
1.constructor
作用:保存自己构造函数的引用属性
function Person(){..}
var person1 = new Person()
console.log(person1.constructor) //ƒ Person(){}
2.prototype
问:什么样的场景会用到prototype属性?
person1.sayHello = function(){..}
person2.sayHello = function(){..}
console.log(person1.sayHello === person2.sayHello) // false
// 说明这是两个不同的方法,各自占有内存
思考:同一个方法却各自占有内存,那如果把这个方法提成公用的,各自调用,就只用 占一个内存,此时prototype上场表演了。
Person.prototype.sayHello = function(){..}
var person1 = new Person();
var person2 = new Person();
console.log(person1.sayHello === person2.sayHello) // true同一个方法
结论:如果需要为大量实例添加同一个方法时,可以把这个方法放在prototype对象中, 并将prototype对象放在他们共同的构造函数上,实现共享。
这里需要注意一点:因为所以函数都是Function函数的实例对象,所以Function函数内 部也存在一个prototype来存放公共属性和方法。
3.proto
先记住这个关系:
实例对象._proto = 创建自己构造函数内部的prototype(原型对象)
实例对象.proto.consrtuctor = 创建自己构造函数
这个图不够明显的话,我们来拆解这个图:
Person()方法里有prototype对象和_proto_属性,prototype上放着constructor和共享属性和方法
Person()函数是Function()函数的实例对象
Function()函数,自己是自己的构造函数,所以Function.proto = 构造函数内部prototype
问:实例对象.constructor 等于 实例对象.proto.constructor?
答:当在实例对象上找不到某个属性,就会去它的原型链上找是否有相关的公用属性或方法
function Person() {}var person1 = new Person()var person2 = new Person() console.log(person1.constructor) // [Function: Person] 此处实例本身没有constructor
console.log(person2.constructor) // [Function: Person] 则指向原型的共享constructor
person1.constructor = Function
console.log(person1.constructor) // [Function: Function]
console.log(person2.constructor) // [Function: Person]
重点来了:
问:prototype是一个对象,那么它是否有_proro_?
答:
function Person(){..}
console.log(Person.prototype._proto_.constructor) // ƒ Object() {}
Person.prototype._proto_.constructor = Person中prototype对象的构造函数
结论:函数内的prototype是个普通的对象,并且默认是Object对象的实例。
由图可以得出的结论:
1.所以函数都是Function函数的实例对象,所以他们的_proto_指向Function函数的prototype
2.函数内的prototype是对象,并且是Object对象的实例。函数内prototype的也有自己的_proto_和constructor.
3.Object函数是所有对象通过原型链追溯到最根的构造函数。
4.每个函数内的prototype的_proto_指向Object对象的实例的prototype
5.Object函数的prototype中的__proto__指向null
问:为什么Object函数不能像Function函数一样让__proto__属性指向自己的prototype?
答:如果指向自己的prototype,那当找不到某一属性时沿着原型链寻找的时候就会进入死循环,所以必须指向null,这个null其实就是个跳出条件。
三.原型链的继承时的缺点
1.引用类型的原型属性会被所有实例共享。
在通过原型链来实现继承时,引用类型的属性被会所有实例共享,一旦一个实例修改了引用 类型的值,会立刻反应到其他实例上。
2.创建子类型的实例时,不能向父类型的构造函数传递参数
实际上,应该说是没有办法在不影响其他对象实例的情况下,给父类型的构造函数传递参 数,我们传递的参数会成为所有实例的属性。
解决办法有以下几种方法:
(1)借用构造函数
用法:apply()或call()方法,在子类型构造函数的内部调用父类型的构造函数,使得子类 型拥有父类型的属性和方法。
function Person(param){
this.param = [].concat(param);
this.colors = [‘red’,’green’];
}
function person1(param){
Person.apply(this,param)
}
var instance1 = new person1(['instance1']);
instance1.colors.push('black'); // [‘red’,’green’,'black']
console.log(instance1.param[0]); // 'instance1'
此方法的好处:解决上面提到的两个问题,实例间不会共享属性,也可以向父类型传递参 数。
缺点:子类型无法继承父类型原型中的属性。我们只在子类型的构造函数中调用了父类型 的构造函数,子类型和父类型的原型也就没有任何联系
(2)组合继承
思路:使用原型链实现对原型属性和方法的继承,又通过借用构造函数来实现对实例属性 的继承
就是在上面的例子上加一步:person1.prototype = new Person();(继承父类的原型)
缺点:无论什么情况下,组合继承都会调用两次父类型的构造函数:一次是在创建子类型 原型的时候,另一次是在子类型构造函数内部。
(3)寄生组合式继承(是《JavaScript高级程序设计》第六章的精华所在)
思路:通过借用构造函数来继承属性,通过借用临时构造函数来继承原型。不必为了指定 子类型的原型而调用父类型的构造函数,我们所需要的无非就是父类型原型的一个副本已。
// 用于继承的函数
function inheritPrototype(child,parent) {
// new这个函数时,将构造函数指向子类
var F = function () {this.constructor = child}
F.prototype = parent.prototype; //F函数的原型对象是父亲的原型对象(父类的副本)
child.prototype = new F(); //将子类的原型指向父类原型的一个副本
}
// 父类型 function Person(name) { this.name = name; this.colors = ["red", "blue", "green"]; }
Person.prototype.sayName = function () { console.log(this.name); };
// 子类型function person1(name, age) { // 继承基本属性和方法 Person.call(this, name); //借用构造函数 this.age = age;}
// 继承原型上的属性和方法inheritPrototype(person1, Person);
总结: