递归、闭包、继承
面向对象
好处:易于分工与合作,适合大型项目开发;健壮性强,易于维护
特征:封装、继承、多态
1. 原型
1. 学习原型理由
问题:每创建一个对象,都会在对象中添加一个方法,方法是不变的,若创建每个对象会造成内存浪费。
解决:把方法抽取出来,让所有 实例对象共享。
实现共享:使用原型(把方法封装到原型中)
2. 什么是原型
原型也是一个对象
1. 获取原型的方式:构造函数名.prototype
2. 构造函数与原型的关系
* 构造函数可以通过prtotype找到原型
* 原型可以通过construcror找到构造函数
3. 原型链
实现对象在调用一个属性和方法时过程如下:
①会先从实例对象中找
②若自身没有找到则通过__prot__找到原型,去原型中找
③若原型中没有,则通过原型的__proto__找到原型的原型去找
之所以会找到因为原型链的存在
2. 继承
1. 利用原型实现继承
更改子类的原型指向(或给子类的原型重新赋值)
//定义person构造函数 function Person(){ this.name = '名字'; this.age = 10; this.gender = '男'; } //给构造函数原型添加方法 Person.prototype.sayhi = function(){ console.log('hi') } //实例化对象p1 var p1 = new Person() //定义student构造函数 function Student(){ this.stuId = '10100'; } Student.prorotype = p1; //实例化学生对象 var stu1 = new Student(); console.log(s1.name); //输出name值是p1.name的值
原型继承的问题和优点:
缺点:继承的属性是没有意义的;
优点:继承的方法是有意义的;
2. 借用继承
call方法改变this指向
函数体内的this指向是在函数调用时决定的(指向调用者)
语法:函数名.call( 调用者,参数 )
作用:该函数会立即执行,但函数体内的this会指向借用者
function fn(n, a){ this.name = n; this.age = a; } //定义对象 var obj1 = {type: 'obj'}; //fn借用给obj1 fn.call( obj1, '展示', 11)
call方法实现借用
在子类构造函数中借用父类构造函数,并且在 call 中传入子类对象
//per构造函数 function Per(n,a){ this.name = n; this.age = a; } Per.prototype.sayhi = function(){ console.log(this.name + 'hi') } //stu构造函数 function Stu(n,a){ this.sid = '10001' Per.call(this, n, a) //Per谁调用借给谁 } var s1 = new Stu('栈', 23) console.log(s1.name); //输出栈
借用继承的问题和优点
缺点:仅仅借用了属性(原因:call方法在被per类调用执行时仅仅让调用者执行函数内部的程序,函数外的没有借用执行)
优点:很好的继承属性
解决:原型+借用
3. 组合继承
原型:借用方法 + 借用:继承属性
//创建per构造函数 function per(n, a){ this.name = n; this.age = a; } //为per添加原型 per.prototype.sayhi = function(){ console.log(this.name + 'hi') } //创建stu构造函数 function stu(n,a){ this.sid = '1001011' //借用属性 per.call(this, n, a) } //借用方法 stu.prototype = per.prototype var s1 = new stu('组件', 22) console.log(s1.name) //输出组件 s1.sayhi() //输出组件hi
组合继承问题:
缺点:stu类和per指向同一个原型,stu的原型改变时人类的原型也会被改变
优点:让他们不要指向同一个原型,使用各自的原型(对象之间的拷贝)
4. 对象之家的拷贝
对象B拷贝对象A的属性和方法
//一个对象拷贝另一个对象中的属性和方法,parentObj父对象 childObj子对象 function copy(parentObj, childObj){ for(var key in parentObj){ if( childObj[kye] == undefined ){ childObj[key] = parentObj[key]; } } } //对象A var wjl = { name: '大熊', money: 100, house: '大屋子', cars: ['qq', '11'], sayhi: function(){ console.log('大家好,我叫'+this.name); } } //对象B var wsc = { name: '小熊' } copy(wjl,wsc)
5. 拷贝+ 借用组合继承
拷贝方法 + 借用属性
// 【人类】 function Person(name, age, gender) { this.name = name; this.age = age; this.gender = gender; } Person.prototype.sayHi = function () { console.log('大家好....O(∩_∩)O哈哈~,我叫' + this.name); } Person.prototype.eat = function () { console.log('我会吃饭.....'); }; // 【学生类】 function Student(name, age, gender) { // this代表谁?代表一个学生的实例对象 s1、s2 this.stuId = '10010'; // --借用函数--Person[仅仅借用函数体中内容,并没有借用函数体之外其他程序] Person.call(this, name, age, gender) }; // [拷贝人类原型(wjl)中的方法给学生原型(wsc)] copy(Person.prototype, Student.prototype); Student.prototype.test = function() { console.log('我超爱考试'); } // 【老师类】 function Teacher(name, age, gender) { // 借用继承-借用人类的属性 Person.call(this, name, age, gender); } // [拷贝人类原型(wjl)中的方法给老师原型(wsc)] copy(Person.prototype, Teacher.prototype); // 创建学生对象 var s1 = new Student('张三',19,'男'); // 创建老师对象 var t1 = new Teacher('李四',39,'男');
3. this指向
普通函数中this指向window
构造函数中this指向当前所创建的对象
定时器中this指向window
对象方法中的this指向调用者
事件处理程序中this指向事件源
4. 改变函数内部this指向
1. apply
语法:函数名.apply( 调用者,[参数......] )
作用:函数被借用时,会立即执行,并且函数体内的this会指向借用者
2. bind方法
语法:函数名.apply(调用者, 参数.......)
作用:函数被借用时,不会立即执行,返回新的函数。函数体内this指向调用者
5. 闭包
1. 什么是闭包
闭包就是能够读取/设置 他函数 内部变量的函数;闭包就是将函数内部和函数外部链接起来的桥梁。
2. 闭包的用途
可以在函数外部读取函数函数内部成员;让函数内成员始终活在内存中(延长变量生命周期)
3. 如何实现闭包
①函数嵌套
②外层函数,必须要有局部变量
③内部函数必须要操作外层函数的局部变量,并且在外层中返回内层函数或内层函数在外部被间接的调用。
调试:在内层函数中设置断点,若进入来在右侧中的scope下可以看到closure,就表示产生了闭包。 在实际开发中,不要可以使用闭包,因为延长生命周期的变量可能会一直存活在内存中。 解决方案:把外层变量设置为null
6. 递归
作用:减少代码量
缺点:若递归的层数比较多时,会消耗CPU。浪费内存,影响性能。
若将来递归层较少时,可以选择用递归方式。