面向对象、继承、多态、class关键字、闭包

269 阅读4分钟

面向对象

  • 面向对象 - 三大特点:封装、继承、多态

开发方式

  • 面向过程:过程 - 开始->结束,其实我们一致的开发方式都是面向过程:先干什么再干什么最后干什么
  • 面向对象:对象(属性和方法),js有一句话万物皆对象
  • 何时使用面向对象:以后任何操作都要封装在一个对象之中 - 但是新手并不太推荐,难度较大 -为什么要面向对象:现实生活中所有的数据都必须包含在一个事物之中才有意义

封装/创建/定义

直接量方式

var obj={
"属性名":属性值,
...,
"方法名":function(){操作},//可以简化为箭头函数
...
}
  • 强调
    • 其实属性名和方法名的""可以不加 - 暂时建议你加上,以后我们要学习一个数据格式JSON,他必须在键上加上""

    • 访问对象的属性和方法

      obj.属性名;	===	obj["属性名"];
      obj.方法名();	===	obj["方法名"]();      
      
    • 访问到不存在的属性,返回undefined(js中一切都是对象,除了undefined和null,一切对象的底层都是hash数组)

    • 可以随时随地的添加新属性和新方法

      obj.属性名=新值;
      obj.方法名=function(){};
      
    • 如果我希望遍历出对象所有的东西,必须使用for inobj[i]才能拿到,不要使用.会出问题

    • 如果你希望在对象的方法里,使用对象自己的属性,写为this.属性名

  • 难点:this的指向:
    • 单个元素绑定事件,this->这个元素
    • 多个元素绑定事件,this->当前元素
    • 定时器中的this->window
    • 箭头函数this->外部对象
    • 函数中this->谁在调用此方法,this就是谁
    • 构造函数之中this->当前正在创建的对象

预定义构造函数方式

var obj=new Object();//空对象
//需要自己后续慢慢添加属性和方法
obj.属性名=新值;
obj.方法名=function(){};
  • 以上两个都有一个缺陷:一次只能创建一个对象,适合创建单个元素的时候(第一种方法),第二种方法完全是垃圾,如果你要批量创建多个对象,那么推荐第三种方法

自定义构造函数方式

  • 创建自定义构造函数
    function 类名(name,age,hobby){
    this.name=name;
    this.age=age;
    this.hobby=hobby;
    }
    //千万不要在里面创建方法,每个对象都会创建出一个相同的方法,浪费内存 - 学习继承后可以解决
    
  • 调用构造函数创建对象var obj=new 类名(实参,...);

继承

  • 父对象的成员(属性和方法),子对象可以直接使用
  • 为什么要继承:代码重用!提高代码的复用性,节约了内存空间!提升了网站的性能!
  • 何时继承:只要多个子对象公用的属性和【方法】,都要集中定义在父对象之中

如何找到原型对象(父对象)

  • 保存了一类子对象共有属性和共有方法
  • 对象名.__proto__; //必须先有一个对象
  • 构造函数名.prototype;//构造函数名几乎人人都有,除了Math和Window,new 构造函数名();//Array、String、Date、RegExp...

继承具有很多的面试笔试题

判断是自有还是共有

  • 判断自有:obj.hasOwnProperty("属性名");//true,说明是自有属性,false,有两种可能,说明可能是共有,也可能是没有
  • 判断共有:if(obj.hasOwnProperty("属性名")==false&&"属性名" in obj){//in关键字,会自动查找整条原型链上的属性,找到了结果为true,找不到结果为false
  • 完整公式
if(obj.hasOwnProperty("属性名")){
	自有
}else{
	if("属性名" in obj){
		共有
	}else{
		没有
	}
}

修改和删除:自有和共有

  • 自有
    • 修改:obj.属性名=新属性值;
    • 删除:delete obj.属性名;
  • 共有
    • 修改:原型对象.属性名=新属性值;
    • 删除:delete 原型对象.属性名;

为老IE的数组添加indexOf方法

if(Array.prototype.indexOf===undefined){//老IE	
Array.prototype.indexOf=function(key,starti){			starti===undefined&&(starti=0);
		for(var i=starti;i<this.length;i++){
			if(this[i]===key){						return i;
			}
		}
	return -1;
    }
}

如何判断x是不是一个数组

  • 4种方法:千万别用typeof(),只能检查原始类型,不能检查引用类型,如果检查引用类型得到的结果都是一个object。
    • 判断x是否继承自Array.prototype

      • Array.prototype.isPrototypeOf(x);
      • 结果为true,说明是数组,结果为false,说明不是数组
    • 判断x是不是由Array这个构造函数创建的

      • x instanceof Array;
      • 结果为true,说明是数组,结果为false,说明不是数组
    • Array.isArray(x); - ES5新增的方法,只有数组可以这么使用

      • 结果为true,说明是数组,结果为false,说明不是数组
    • 输出【对象的字符串】形式

      • 在Object的原型上保存着最原始的toString方法
      • 原始的toString输出形式:[object 构造函数名]
      • 固定套路:Object.prototype.toString.call(x)==="[object Array]"

实现自定义继承

  • 两个对象之间设置继承:子对象.__proto__=父对象
  • 多个对象之间设置继承:构造函数名.prototype=父对象;
    • 时机:应该在开始创建对象之前就设置好继承关系

多态

  • 子对象觉得父对象的成员不好用,就在本地定义了同名函数,覆盖了父对象的成员,不严格定义:同一个方法,不同的人使用,效果不同,有多种形态

class关键字

  • 简化面向对象(封装、继承、多态)
class 类名 extends 老类{
	constructor(name,age,hobby,...){
        //放在constructor里面的都是自有属性
		super(name,age);
		this.hobby=hobby;
	}//放在constructor外面的都是共有方法
	//还会继承到老类所有的API,也可以添加新的
}

Function:闭包

  • 作用域:2种
    • 全局:随处可用,可以反复使用,缺点:容易被污染
    • 函数:只能在函数调用时内部可用,不会被污染,缺点:一次性的,是会自动释放的

函数的执行原理

  • 程序加载时
    • 创建执行环境栈(ECS):保存函数调用顺序的数组
    • 首先压入全局执行环境(全局EC)
    • 全局EC引用着全局对象window
    • window中保存着我们全局变量
  • 定义函数时
    • 创建函数对象:封装代码段
    • 在函数对象之中有一个scope(作用域)属性:记录着函数来自己的作用域是哪里
    • 全局函数的scope都是window
  • 调用前
    • 在执行环境栈(ECS)压入新的EC(函数的EC)
    • 创建出活动对象(AO):保存着本次函数调用时用到的局部变量
    • 在函数的EC中有一个scope chain(作用域链)属性引用着AO
    • AO有一个parent属性是函数的scope引用着的对象
  • 调用时
    • 正是因为有前面三步,才来带变量的使用规则:优先使用自己的,自己没有找全局,全局没有就报错
  • 调用完
    • 函数的EC会出栈,没人引用AO,AO自动释放,局部变量也就释放了

闭包

  • 希望保护一个可以【反复使用的局部变量】的一种词法结构,其实还是一个函数,只是写法比较特殊

  • 何时使用:希望保护一个可以【反复使用的局部变量】的时候

  • 如何使用

    • 两个函数进行嵌套
    • 外层函数创建出受保护的变量
    • 外层函数return出内层函数
    • 内层函数再操作受保护的变量
  • 强调

    • 判断是不是闭包:有没有两个函数嵌套,返回了内层函数,内层函数再操作受保护的变量
    • 外层函数调用了几次,就创建了几个闭包,受保护的变量就有了几个副本
    • 同一次外层函数调用,返回的内层函数,都是在操作同一个受保护的变量
  • 缺点

    • 受保护的变量,永远都不会被释放,使用过多,会导致内存泄漏 - 不可多用
  • 在哪里去使用

    • 三个事件需要防抖节流 - 共同点:触发的速度飞快
    • elem.onmousemove - 鼠标移动事件
    • input.oninput - 每次输入/改变都会触发
    • onresize - 每次窗口改变大小都会触发
  • 防抖节流的公式

    function fdjl(){
            var timer=null;
            return function() {
            if(
                timer!==null{clearTimeout(timer);
                timer=null;
            }
            timer=setTimeout(()=>{
                    操作
            },500)
            }
    }			
            var inner=fdjl();
            lem.on事件名=function(){
            inner();
    }
    

两链一包

作用域链

  • 以函数的EC的scope chain属性为起点,经过AO,逐级引用,形成的一条链式结构,我们就称之为叫做作用域链
  • 作用:查找变量,带来了变量的使用规则:优先使用自己的,自己没有找全局,全局没有就报错

原型链

  • 每个对象都有一个属性叫做.proto,可以一层一层的找到每个对象的原型对象,最顶层的就是Object的原型,形成的一条链式结构,我们就称之为叫做原型链
  • 作用:查找属性和方法,哪怕自己没有也会顺着原型链向上找,怪不得人人都能用toString(),因为他在最顶层

闭包

  • 希望保护一个可以【反复使用的局部变量】的一种词法结构,其实还是一个函数,只是写法比较特殊
  • 作用:专门用于防抖节流