一、new操作符调用的作用
- 在内存中创建一个新的空对象
- 这个对象内部的[[prototype]]属性会被赋值为该构造函数的prototype属性
- 构造函数内部的this,会指向创建出来的新对象
- 执行函数的内部代码(函数体代码)
- 如果构造函数没用返回非空对象,则返回创建出来的新对象
二、原型
- Object.prototype是所有类的父类
- 每个对象都有个原型__proto__(隐式原型)
- 每个函数都有一个原型prototype(显示原型),同时也有__proto__隐式原型(函数也是特殊的对象)
function Foo(){
}
//隐式原型(有浏览器兼容问题)
console.log(Foo.__proto__); //{}
//显示原型(无浏览器兼容问题)
console.log(Foo.prototype); //{}
let f1 = new Foo();
let f2 = new Foo();
console.log(f1.__proto__ === Foo.prototype); //true
console.log(f2.__proto__ === Foo.prototype); //true
三、定义在构造函数内的方法和定义在prototype原型上方法的区别
- 方法所处位置不同,构造函数内方法是定义在构造函数内,而prototype上的方法是定义在原型链上
// 构造函数A
function A(name) {
this.name = name;
this.sayHello = function () {
console.log("Hello, my name is: " + this.name);
};
}
// 构造函数B
function B(name) {
this.name = name;
}
B.prototype.sayHello = function () {
console.log("Hello, my name is: " + this.name);
};
- 使用函数内的方法我们可以 访问到函数内部的私有变量,如果我们通过构造函数
new出来的对象需要我们操作构造函数内部的私有变量的话,这个时候就要考虑使用函数内的方法
let a1 = new A('这是a1')
let b1 = new B('这是b1')
console.log(a1);
console.log(b1);
- 如果需要通过构造函数创建大量的对象的话,且该对象有许多方法时,用函数内部定义的话会导致占用内存过大,这时挂载在prototype原型上是个优质的选择
四、原型链
每个对象都有一个指向它的原型(prototype)对象的内部链接。每个原型对象又有自己的原型,直到某个对象的原型为null为止,组成这条链的最后一环
let obj = {
name: 'why',
age: 18
}
obj.__proto__ = {
}
obj.__proto__.__proto__ = {
}
obj.__proto__.__proto__.__proto__ = {
fathAddress: '9988'
}
console.log(obj.fathAddress); //9988
- 示例代码图
let obj1 = {
name: 'why',
age: 18
}
let obj2 = {
address: '深圳市'
}
obj1.__proto__ = obj2
五、继承
通过
【某种方式】让一个对象可以访问到另一个对象中的属性和方法
- 原型链继承(通过new父类的对象作为子类的原型,通过原型链实现继承)
//父类:公共属性和方法
function Person(name){
this.name = name
}
Person.prototype.eating = function(){
console.log(this.name + 'eating');
}
//子类:特有属性和方法
function Student(){
this.sno = 111
}
//子类继承父类
Student.prototype = new Person('hjh');
Student.prototype.studying = function(){
console.log(this.name + 'studying');
}
let stu = new Student();
console.log(stu);
- 弊端:
- 打印stu,是通过原型链继承,继承的属性是看不到的,只会打印stu对象内可枚举属性,
- 共享原型会导致数据被改变
//父类:公共属性和方法
function Person(){
this.name = 'hjh',
this.friends = []
}
Person.prototype.eating = function(){
console.log(this.name + 'eating');
}
//子类:特有属性和方法
function Student(){
this.sno = 111
}
//子类继承父类
Student.prototype = new Person();
Student.prototype.studying = function(){
console.log(this.name + 'studying');
}
let stu1 = new Student();
let stu2 = new Student();
console.log(stu1);
stu1.friends.push('why') //获取引用,修改引用中的值,会相互影响
stu1.name = 'newName' //直接修改对象上的属性,是给本对象添加了一个新属性,是不会相互影响的
console.log(stu2); //此时stu2.friends也随着改变
-
实例化构造函数时不好传参,将name参数传递给父类而不是让子类接收
-
借用构造函数方案实现继承,解决上述原型链继承所有弊端
//父类:公共属性和方法
function Person(name, age, friends){
//此时this指向Student对象
this.name = name,
this.age = age,
this.friends = friends
}
Person.prototype.eating = function(){
console.log(this.name + 'eating');
}
//子类:特有属性和方法
function Student(name, age, friends, sno){
//通过call将Student的this及其他公共属性需要在父类调用的传入
Person.call(this, name, age, friends)
this.sno = sno
}
//子类继承父类
Student.prototype = new Person();
Student.prototype.studying = function(){
console.log(this.name + 'studying');
}
let stu1 = new Student('student1',18,['张三'],182);
stu1.friends.push('李四')
console.log(stu1);
let stu2 = new Student('student2',21,['李四'],176);
console.log(stu2);
- 借用构造函数方案实现继承弊端:(移步至js面向对象三有解决方案)
- 父类Person函数会被调用多次
- stu的原型对象上会多出一些属性,但是这些属性是没有必要的存在