1闭包
闭包是这样一种机制: 函数嵌套函数, 内部函数可以引用外部函数的参数和变量,参数和变量不会被垃圾回收机制所收回。
这里涉及到几个概念:
1.函数嵌套函数。
2.作用域(全局变量和局部变量):变量的访问规则。
3.垃圾回收机制(garbage collection):js内部不断扫描内存,并清理无引用对象(自动完成)。
function aa(){
var num=10;
function bb(){
num++
console.log(num);
}
return bb;
}
//bb(); //无法直接访问函数内部的函数
aa()();//11
aa()();//11
aa()();//11
var closure = aa();
closure();//11
closure();//12
closure();//13
1.1闭包的好处
1.可以让一个变量长期驻扎在内存当中不被释放。
2.避免全局变量的污染, 和全局变量不同, 闭包中的变量无法被外部使用。
3.私有成员的存在, 无法被外部调用, 只可以自己内部使用。
1.2结论(如何描述闭包)
1.闭包是指有权访问另一函数作用域中的变量的函数。
2.闭包,可以访问函数内部的局部变量,并让其长期驻留内存。
3由于闭包会携带包含它的作用域(运行环境),因此会比其他函数占用更多内存,过度使用闭包可能会造成性能问题。
1.3扩展案例
点击按钮,打印当前索引值
var btn = document.querySelectorAll('button');
方法1:let方法
for(let i=0;i<btn.length;i++){
// btn[i].idx = i;
btn[i].onclick = function(){
console.log(i);
// console.log(this.idx);
}
}
方法2:闭包法
//下面的闭包中,onclick绑定的匿名函数立即执行,为每一个btn绑定了一个return后的函数,但没有执行,只有onclick后才执行
for(var i=0;i<btn.length;i++){
btn[i].onclick = (function(i){
// 闭包
return function(){
console.log(i);
}
})(i);
}
2 ES5对象扩展
2.1 Object.create(prototype)
以指定的原型创建对象。
2.2Object.defineProperty()
Object.defineProperty(object, propertyname, descriptor),对指定的对象的一个属性设置属性特性。传统方式添加的属性,属性特性都为true,Object.defineProperty添加的属性,属性特性都为false。
// 通过Object.defineProperty添加属性,属性特性默认都是false
Object.defineProperty(obj,'hobby',{
value:'很多'
});
// 设置age为不可修改的属性
Object.defineProperty(obj, 'age', {
writable:false,
enumerable:false
})
2.2.1值属性
1. configurable
可配置性,控制着其描述的属性的修改,表示能否修改属性的特性。
2. enumerable
可枚举性,表示能否通过for-in遍历得到属性。
3. writable
可写性,表示能否修改属性的值。
4. value
数据属性,表示属性的值。默认值为undefined。
2.2.2访问器属性
1. configurable
2. enumerable
3. get
在读取属性时调用的函数。只指定get则表示属性为只读属性。默认值为undefined。
4. set
在写入属性时调用的函数。只指定set则表示属性为只写属性。默认值为undefined。
2.3 Object.defineProperties()
Object.defineProperties(object, descriptors),对指定的对象的一组属性设置属性特性。
2.4 Object.getOwnPropertyDescriptor()
Object.getOwnPropertyDescriptor(object, propertyname),返回属性特性。
2.5 Object.keys(object)
Object.keys(object),返回对象所有可枚举属性的名称。
2.6 Object.getOwnPropertyNames()
Object.getOwnPropertyNames(object),返回所有属性的名称(哪怕说是不能枚举的属性)。Own的属性,不包括原型对象中的属性。
3原型链
实例与Object原型对象之间的链条称为原型链。
3.1原型模式的访问机制(原型搜索机制)
1.读取实例对象的属性时,先从实例对象本身开始搜索。如果在实例中找到了这个属性,则返回该属性的值;
2.如果没有找到,则继续搜索实例的原型对象,如果在原型对象中找到了这个属性,则返回该属性的值;
3.如果还是没找到,则向原型对象的原型对象查找,依此类推,直到Object的原型对象(最顶层对象);
4.如果再Object的原型对象中还搜索不到,则抛出错误。
3.1.1重置原型对象
重置原型对象,可以一次性给原型对象添加多个方法,但切断了与原来原型对象的联系。(注意覆盖问题注意识别问题)
function Popover(){}
Popover.prototype = {
show:function(){},
hide:function(){}
}
3.1.2内置原型对象
使用内置原型可以给已有构造函数添加方法。
· 数组/字符串/数字等方法调用原理;
· 扩展内置方法。
3.2对象属性的遍历与判断
3.2.1 for..in
遍历对象中的所有可枚举属性, 无论该属性存在于实例中还是原型中。
3.2.2 in
只要通过对象能够访问到属性就返回true, 无论该属性存在于实例中还是原型中。
if(name in s1){
}
3.2.3对象 hasOwnProperty(属性)
检测一个属性是存在于对象本身中。
· 返回true,说明属性存在对象中;
· 返回false,说明属性不存在或在原型中。
检测一个属性是否存在于原型中:!obj.hasOwnProperty(name) && (name in obj)。
4继承
继承是面向对象中一个非常重要的特征。指的是:子类继承父类的属性和方法。我们可以通过继承的方式, 在父类的属性和方法基础上, 让子类也拥有这些属性和方法, 并可以扩展。
4.1继承的好处
1.子类拥有父类所有的属性和方法(代码复用);
2.子类可以扩展自己的属性和方法(更灵活);
3.子类可以重写父类的方法。
4.2继承的方式
4.2.1原型链继承
核心:拿父类实例来充当子类原型对象。
缺点:1.无法继承构造函数中的属性;2.创建子类实例时,无法向父类构造函数传参;3.原型对象中存在多余的属性。
function Student(name,age,gender){
// 属性
this.name = name;
this.age = age;
this.gender = gender;
}
// 原型对象添加方法
Student.prototype.say = function(){
console.log('超能说')
}
Student.prototype.cry = function(){
console.log('超能哭')
}
function Pupil(){
}
// 原型链继承法
Pupil.prototype = new Student(); //利用父类实例充当子类原型对象
Pupil.prototype.constructor = Pupil; //将pupil.prototype中的constructor重新设置为pupil,否则puil.prototype将没有constructor属性,无法得知实例由谁创建的
Pupil.prototype.cry = function(){
}
var p1 = new Pupil();
4.2.2借用构造函数
核心:借父类的构造函数来增强子类实例,相当于把父类的实例属性复制一份给子类实例。
1.call(obj):
格式:父类构造函数.call(子类实例,参数1,参数2,参数3...)。
特点:1.立刻执行函数;2.改变函数中的this指向(指向obj)。
function Student(name,age,gender){
// 属性
this.name = name;
this.age = age;
this.gender = gender;
}
// 原型对象添加方法
Student.prototype.say = function(){
console.log('超能说')
}
Student.prototype.cry = function(){
console.log('超能哭')
}
function Pupil(name,age,gender){
// 借用构造函数法(继承属性)
Student.call(this,name,age,gender); //this代表的是当前函数中的this,
指向Pupil创建的实例对象
}
var p1 = new Pupil();
2.apply:
格式:父类构造函数.apply(子类实例,[参数1,参数2,参数3...])。
call与apply的唯一区别:传参方式不同,call多个参数,apply只有两个参数,第二个参数为数组。
//aplly用法:借用方法
var arr = [20,2,40,33,21,8,22,46,32]
Math.max.apply(null,arr) //借用了Math的max方法
//call用法
//buttons指获取到的页面上的按钮的类数组,但是类数组没有array中的forEach方法,所以通过call借用,funciton(ele,idx){...}为Array.forEach(function(ele,idx){..})中的参数
Array.prototype.forEach.call(buttons,function(ele,idx){
//forEach(ele,idx,arr) forEach内可以有三个参数,ele是数组元素,idx为索引值,arr为数组本身
console.log(ele);
ele.onclick = function(){
console.log(idx);
}
});
缺点:无法实现函数复用;函数太多就会影响性能,占用更多内存。
4.2.3组合继承
由于以上继承方法的缺点,实际开发中不可能单纯的只使用一种继承方法,而是利用它们的优点,规避它们的缺点,所以就有了组合继承法。组合继承是最常用的继承模式。
继承属性:借用构造函数,只在构造函数中定义属性。
继承方法:原型链继承,把所有的方法写入原型对象。
缺点(原型链继承法的缺点):1.在原型对象中生成多余的属性;2多次执行父类构造函数。
4.2.4原型式继承
核心:先创建了一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例。解决原型链继承法的缺点:生成多余的属性。
Pupil.prototype = object(Student.prototype);
function object(o){
function F(){}
F.prototype = o;
return new F();
}
ES5版本的原型式继承:Object.create()
Pupil.prototype = Object.create(Student.prototype);
// 解决识别问题
Pupil.prototype.constructor = Pupil;
4.2.5寄生组合继承法
完美的继承方法,核心:1.继承属性:借用构造函数;2.继承方法:原型式继承。
5 ES6中的继承
5.1 class定义类
ES6提供了更接近传统语言的写法,引入了Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。
//定义类
class Person {
constructor(name,age) {
this.name = name;
this.age = age;
}
// 方法
// 这里添加的方法
// * 自动成为原型对象的方法
// * 自动设置属性特性
getInfo() {
return `我叫${this.name},今年${this.age}岁`;;
}
}
注意:
1.写在类里面的方法实际是给Person.prototype添加方法。
2.constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。如果没有constructor方法,则得使用默认的constructor方法。
5.2 extends继承
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
class Man extends Person {
constructor(name, age, gender) {
//this.gender = gender; // 报错
super(name, age); //生成实例传参时可以少传参数,在super补全即可
this.gender = gender; // 正确
}
}
1.子类继承了父类,在子类构造函数中必须调用super方法。
2.子类的constructor方法没有调用super之前,不能使用this关键字,否则报错,而放在super方法之后就是正确的。
5.3静态方法
如果在一个方法前,加上static关键字,这就称为“静态方法”。
class Person {
constructor(){
this.name = 'laoxie',
this.age = 18;
}
static getInfo(){
return this.name
}
say(){
console.log(`Hello everyone, my name is ${this.name}, I'm ${this.age} years old`)
}
}
class Man extends Person {}
1.静态方法方法不会被实例继承,而是直接通过类来调用Person.getInfo()。
2.父类的静态方法,可以被子类继承Man.getInfo()。