1,对象和构造函数
1.1对象
对象有两种创建方式:1,字面量创建 2,使用new+构造函数创建
1.2构造函数
构造函数和普通函数的区别主要有三点:
-
构造函数的函数名以大写字母开头,用于和普通函数区分
-
构造函数中的this指向的是构造函数创建的空对象
-
构造函数不用return,默认的返回值就是this指向的对象
function Student(name, age, sex, phone, address) { // this.name指的是对象的属性名,后边的name指的是构造函数的参数名 this.name = name; this.age = age; this.sex = sex; this.phone = phone; this.address = address }//这就是Student的一个构造函数 var zhansgan = new Student("张三", 20, "男", "110", "河南");
总结:使用对象结构和构造函数的好处
对象结构的好处:可以把一些关联性高的数据结合成一个整体,数据调用,查找更方便,快速,准确。
构造函数的好处:在创建大量相似对象时,使用构造函数,可减少代码量,简化结构
2,原型
原型:每一个构造函数都有一个属性prototype,它的值就是一个对象,叫做原型对象。原型对象又有一个属性叫constructor(构造器),指向构造函数本身。
可以把构造函数相同的属性和方法,定义在原型对象中,当把属性或方法定义到构造函数原型中之后,构造函数创建的对象就不再包含这些属性和方法了,内存中不再出现重复的数据,而这些数据只有在构造函数原型中独一份。
属性和方法定义到原型对象中的语句:
Student1.prototype.address = "河南" Student1.prototype.study = function(){ console.log(this.name + "在学前端") }
function Student1(name, age, sex, phone){ this.name = name; this.age = age; this.sex = sex; this.phone = phone; } Student1.prototype.address = "河南" Student1.prototype.study = function(){ console.log(this.name + "在学前端") } var zhangsan = new Student1("张三", 20, '男', '110') var lisi = new Student1("李四", 22, '男', '120') var wangwu = new Student1("王五", 23, '女', '130') var zhaoliu = new Student1("赵六", 24, '女', '140') var array = [zhangsan, lisi, wangwu, zhaoliu]; console.log(3, array)
2.2 js对象调用属性方法的流程
js对象调用属性方法时的流程是,对象会先从对象本身查找对象的属性或方法,如果有,直接调用,如果没有,呢去构造函数中查找,如果有直接调用,如果没有,再去构造函数的原型中查找,原型中有这个属性或方法就可以直接调用
所以,对象中不存在address属性和study方法,但是可以调用
2.3修改原型中属性或者方法
想要修改原型中属性或者方法可以单独修改这个对象的属性和方法(相当于在对象中添加属性或者方法),对象中属性方法的修改,并不影响构造函数原型中的数据。
function Student1(name, age, sex, phone){ this.name = name; this.age = age; this.sex = sex; this.phone = phone; } Student1.prototype.address = "河南" Student1.prototype.study = function(){ console.log(this.name + "在学前端") } var xiaoqiang = new Student1("小强", 25, '男', '150') console.log(xiaoqiang.address) // 此时可以单独修改这个对象的地址 xiaoqiang.address = "四川" // 对象中属性方法的修改,并不影响构造函数原型中的数据 console.log(xiaoqiang, zhangsan, Student1.prototype) // 此时,对象中有address,原型中也有address, 优先调用对象自身的address
3,构造函数的继承
当两个构造函数中有共同的属性或者方法,可以在父构造函数中定义这些共同的属性或者方法,然后用子构造函数继承父构造函数,这样子构造函数就可以用父构造函数的属性和方法,但是父构造函数不能使用子构造函数的属性和方法。
// (一)以下是两个构造函数, 他们有一些共同的属性name,age function People(name, age){ this.name = name; this.age = age; this.eat = function(){ // 此处省略600行代码 console.log(this.name + "是吃货") } } var zhangsan = new People("张三", 20) function Student(name, age, stuID){ this.name = name; this.age = age; this.stuID = stuID; this.eat = function(){ // 此处省略600行代码 console.log(this.name + "是吃货") } this.study = function(){ console.log(this.name + "是学渣") } } var lisi = new Student("李四",21, "123456") console.log(zhangsan, lisi) // 此时,People构造函数中有很多属性和方法, Student构造函数中也有People中的属性和方法,还有Student自己独有的, 如果People中属性和方法非常多非常复杂, Student中再写一遍太麻烦了, 此时可以通过继承的形式, 让 Student 继承 People, 这样Student中不用再写重复的属性和方法, 创建的对象可直接调用People中的属性和方法
3.1 继承的原理
原理:在子构造函数中调用父构造函数,需要保证父构造函数中的this和子构造函数中的this保持一致,需要使用call修改this指向
// (二)以下是 Student 继承 People 的写法, Student为子, People为父 function People2(name, age){ this.name = name; this.age = age; this.eat = function(){ console.log(this.name + "是吃货") } } var zhangsan = new People2("张三", 20) function Student2(name, age, stuID){ People2.call(this, name, age) this.stuID = stuID; this.study = function(){ console.log(this.name + "是学渣") } } var lisi = new Student2("李四",21, "123456") console.log(zhangsan, lisi) console.log(lisi.name, lisi.age, lisi.stuID) lisi.study(); lisi.eat();
3.2 构造函数的继承和构造函数的原型的区别
-
原型中的属性和方法对于每一个对象值都相同,而继承来的属性或方法值可以不相同
-
原型中的属性和方法不会创建显示到对象中,而继承来的属性和方法,会在对象中创建并显示
3.3 构造函数的继承和原型结合使用
构造函数继承时,只继承了父构造函数中的属性和方法,而父构造函数原型中的属性和方法并没有继承过来,所以子构造函数对象无法调用父构造函数原型中的方法.只有在继承不只要继承构造函数,也要继承原型
// (三),继承和原型结合使用, 以上的继承结构存在不足: People和Student构造函数中都有固定的函数eat,study, 我们完全可以把这两个函数定义子对应的原型对象中 function People3(name, age) { this.name = name; this.age = age; } People3.prototype.eat = function() { console.log(this.name + "是吃货") } var zhangsan = new People3("张三", 20) function Student3(name, age, stuID) { People3.call(this, name, age) this.stuID = stuID; } Student3.prototype.study = function() { console.log(this.name + "是学渣") } var lisi = new Student3("李四", 21, "123456") console.log(zhangsan, lisi) console.log(lisi.name, lisi.age, lisi.stuID) lisi.study(); // 问题: 子构造函数对象不能调用父构造函数原型中的方法 // lisi.eat(); // error : lisi.eat is not a function // 原因: 构造函数继承时, 只继承了父构造函数中的属性和方法, 而父构造函数原型中的属性和方法并没有继承过来, 所以子构造函数对象无法调用
原型的继承:通过Object.create()
// (四) 解决方案: 原型的继承: 在继承时不只要继承构造函数,也要继承原型 function People4(name, age) { this.name = name; this.age = age; } People4.prototype.eat = function() { console.log(this.name + "是吃货") } function Student4(name, age, stuID) { People4.call(this, name, age) this.stuID = stuID; } // 在设置子构造函数原型时, 需要先继承父构造函数原型 // Student4.prototype = People4.prototype // 错误, 不能直接等, 因为这样子写是浅拷贝, 造成两个构造函数公用一个原型对象, 如果一个构造函数修改了原型, 另一个的原型也会被修改,造成数据混乱出错 // Object.create()创建一个新的对象, 这个是空的, 属于深拷贝 Student4.prototype = Object.create(People4.prototype) // 原型继承后, 原型的构造器constructor属性丢了, 需要重新设置(没啥用,不设置也行) Student4.prototype.constructor = Student4; Student4.prototype.study = function() { console.log(this.name + "是学渣") } console.log("prototype", People4.prototype, Student4.prototype) var lisi = new Student4("李四", 21, "123456") console.log(lisi.name, lisi.age, lisi.stuID) lisi.study(); lisi.eat(); // 原型继承之后, 子构造函数对象就可以调用父构造函数原型中的属性和方法了
3.4 总结
构造函数继承的步骤:
-
在子构造函数中调用夫构造函数,并使用call修改父构造函数中this指向
-
People4.call(this, name, age)
-
在构造函数继承之后,构造函数的原型也要继承
-
Student4.prototype = Object.create(People4.prototype)
-
重置子构造函数原型的构造器constructor
-
Student4.prototype.constructor = Student4;