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

134 阅读15分钟

this共有几种情况——>创建对象(js中创建对象共有几种方式)——>js中实现继承共有几种方式(继承)——>实现克隆共有几种方式(深克隆)

1、面向对象三大特点

封装、继承、多态

封装

第一种方式:直接用{}

如何访问对象中的成员:

对象名.属性名

对象名.方法名()

什么是this

每个函数自带的

专门指向正在调用当前函数的.前的对象

的关键字

总结:今后,对象中的方法,只要想使用当前对象自己的属性或其他方法时,都要使用this

第二种方式:用new:new Object()

如何:

var 对象名 = new Object() //先创建空对象{}

//强行给空对象中添加新属性和新方法

对象名.新属性=属性值

对象名.新方法=function(){...this.属性名...}

js中所有对象底层都是关联数组

关联数组                                                     |                         对象

[sname:"Han Meimei",sage:11]                 |                        {sname:"Han Meimei",sage:11}

I存储结构:都是名值对儿的组合

II 访问成员时:

标准写法都是:对象名/数组名["成员名"]

简写都是:对象名/数组名.成员名

III强行给不存在的位置赋值

不但不会报错,而且还会自动添加该属性

所以,js中,给对象添加一个新属性或新方法,都可以通过强行赋值的方式

IV强行访问不存在的位置的值

  • 都不会报错,而是返回undefined
  • 所以,可以使用   if(对象名.成员名 !== undefined)   判断一个对象中是否包含某个成员

V都可以用for in循环遍历

for(var 属性名  in  对象名/数组名){

//in 会自动依次取出对象或数组中每个:前的属性名,保存到in前的变量中

//如果想进一步获得属性值:

对象名/数组名[属性名]

}

强调:访问对象属性/数组元素,其实有3中方式:

  • 标准:如果下标名后数组名时已知的,对象或数组["已知的属性名或下标名”]
  • 简写:如果下标名后数组名时已知的,       
  •     如果下标名是数组,可简写为[已知的数字下标]
  •     如果 下标名是非数组的字符串,可简写为.已知的属性名
  • 如果属性名或下标不是写死的,来自一个变量,只能用[变量],不能用.

第三种方式:构造函数

问题:用{}一次只能创建一个对象

如果想创建多个相同结构的对象时,代码会重复很多

极其不便于将来的维护

解决:使用构造函数

什么是构造函数:描述同一类型的所有对象的统一结构的函数

为什么使用构造函数:代码重用

 如何使用:2步

i 定义构造函数:

function 类型名(形参1,形参2,....) {

    this.属性名 = 形参1

    this.xxx = xxx

    this.方法名 = function (){  ...this.属性名... }

}

ii使用构造函数 反复创建多个相同结构的对象

var 对象名 = new 类型名(实参值1,实参值2,....)

至少两件事:

创建指定类型的一个新对象

同时把实参值传给构造函数的形参变量

iii原理

调用构造函数时

new做了四件事

1、先创建一个新对象

2、???

3、调用构造函数

-将构造函数中的this->new 刚创建的新对象

-在构造函数内通过强行赋值方式,为新对象添加规定的属性和方法

4、返回新对象的地址,保存到=左边的变量里

总结:this  2种情况

  • obj.fun()  fun中的this—>.前的obj对象
  • new Fun() Fun中的this—>new 创建的新对象

继承

问题:只要将方法定义放在构造函数中,那么每次执行new时都会执行function,就会反复创建相同函数的多个副本,浪费内存

解决:如果将来发现多个子对象都要使用相同的功能和属性值时,都可以使用继承解决

什么是继承:父对象中的成员,子对象无需重复创建,就可直接使用

什么是原型对象:替所有子对象集中保存共用属性值和方法的父对象

何时使用原型对象:只要发现多个子对象,都要使用相同的功能和属性值时,都可将相同的功能和属性值,集中定义在原型对象中

如何创建原型对象:不用自己创建,而是在定义构造函数时,程序自动附赠我们一个空的原型对象

如何找到原型对象:构造函数中都有一个自带的属性prototype,指向自己配对的原型对象

构造prototype

何时继承原型对象:

new的第二步自动让创建的子对象,继承构造函数的原型对象

new的第二步自动设置子对象的__proto__ 属性,指向构造函数的原型对象

总结:new的四步

  • 创建一个新的空对象等待
  • 让子对象继承构造函数的原型对象
  • 调用构造函数,将this替换为新对象,通过强行赋值方式为新对象添加规定的属性
  • 返回新对象地址

如何向原型对象中添加共用属性

  • 只能强行赋值
  • 构造函数.prototype.属性名 = 属性值
  • 构造函数.prototype.方法名 = function(){...}

结果:

用子对象.访问任何成员时,js先在子对象内部查找自有的属性

如果子对象没有,则js引擎会自动沿__prototype__属性去父对象查找

如果在父对象中找到了想要的属性或方法,则和访问子对象的方法一样调用

所以

构造函数中一定不要包含方法的定义   ——方法内存

为什么父对象叫原型对象  prototype clone

this:第三种情况:原型对象中的this—>

this 3种:不看定义,只看调用

  • obj.fun()  this—>.前的obj
  • new Fun() this—>new 创建的新对象
  • 原型对象中共有的方法里的this—>将来调用这个共有方法的.前的那个子对象

内置类型的原型对象

什么是内置类型:ES标准中规定的,浏览器已经实现,我们可以直接使用的类型

包括:11种

String,Number,Boolean

Array,Date,RegExp

Math(不是类型,已经是一个{}对象)

Error

Function Object

global(全局作用域对象,在浏览器中被window代替)

什么是类型

每种类型一定有2部分组成

1)构造函数:负责创建该类型的子对象

2)原型对象:负责为该类型所有子对象集中保存共有的属性值和方法定义

11种类型中

有9种类型也都由构造函数和原型对象组成

也都可以new创建子对象

内置类型的原型对象

想知道新标准的ES中新增了哪些函数,都可看这个类型的原型对象

比如:Array.prototype,......

问题:如果经常使用一个功能但是原型对象中没有提供

解决:自定义一个函数

保存到原型对象中:

构造函数.prototype.方法 = function(){...}

结果:所有该类型的子对象,都可通过:

子对象.新方法() ——来调用自定义共有方法

原型链:

什么是原型链:由多级父对象逐级继承形成的链式结构

—保存着:一个对象可用的所有属性和方法

—控制着:属性和方法的使用顺序:

    就近原则:先子级后父级

 多态

什么是多态:同一个函数,在不同情况下表现出不同的状态

包括:2种

a.重载overload:同一个函数,输入不同的参数,执行不同的逻辑

b.重写override:(推翻、遮挡)

在子对象中定义一个和父对象中的成员同名的自有成员

从父对象继承来的个别成员不好用时,就可在子对象中定义同名成员,来覆盖父对象的同名成员

小结:

(谈谈你对面向对象的理解)

(1)封装:创建对象,2种:

         如果只创建一个对象:{}

         如果反复创建多个相同结构的对象:构造函数

(2)继承:所有子对象共用的属性值和方法,都要放在构造函数的原型对象中

(3)多态:重写:只要觉得从父对象继承来的成员不要用,都在子对象中重写同名成员

2 this共有几种情况

1.obj.fun()  this—>.前的obj对象

2.new构造函数() this—>new正在创建的新对象

3.构造函数.prototype.fun = function(){}

    因为将来原型对象中的方法,都是“子对象.fun()”方式调用

    所以,this—>将来调用这个fun函数的.前的某个子对象

判断this 一定不要看在哪里定义,一定只看将来在哪里,如何被调用

4.fun()、匿名函数自调和回调函数中的this—>window  

    严格模式(usestrict)下,this—>undefined  

    因为这类函数调用时,前边即没有.,也没有new

比如:

  (function(){  ...this...    })

    和

   arr.forEach(

       function(){ ....this....}

    )

5. button.onclick = function(){}

button.addEventListener("click",function(){....})

DOM事件处理函数里的this—>当前正在触发的.前的DOM元素对象

强调:

这里不能改为箭头函数,一旦改为箭头函数,this指向外层的window

6.Vue中this默认都指当前Vue对象

<button @click="fun">

  exportdefault{

      methods:{

      //vue中methods中的方法中的this默认都指当前vue组件对象

          fun(e){   e.target  }

        //如果想获得当前触发事件的DOM元素对象,必须用$event关键字和e.target联合使用

     }

}

7.箭头函数中的this—>当前函数之外最近的作用域中的this

—几乎所有匿名函数都可以用箭头函数简化

—箭头函数是对大多数匿名函数的简写

箭头函数可让函数内外的this保持一致!

构造函数的方法this指向window

对象的方法不能改为箭头函数

ES6提供的方法

var 对象名 = {

   属性名:属性值,

   方法名() { ....this.属性名...  }

}

既不带function又不要加=>

箭头函数没有(不是)作用域 

错误

箭头函数只让this,指向外部作用域的this

而箭头函数内的局部变量,依然只能在箭头函数内使用,出了箭头函数不能使用

所以,箭头函数依然是作用域,只不过影响this而已,不影响局部变量

箭头函数底层原理,改bind效果和箭头效果一样

结论:箭头函数底层相当于.bind()

call无法替换箭头函数中this

bind是永久绑定,不可替换

8. 可用call或apply,临时替换一次函数中的this

    可用bind,永久替换函数中的this

替换this的3种情况

(1)一次调用函数时,临时替换一次this

       call做的三件事

  1. 立刻调用一次点前的函数
  2. 自动将.前的函数中的this替换为指定的新对象
  3. 还能向要调用的函数中传实参值

(2)特殊

如果多个实参值是放在一个数组中给的

需要既替换this,又要拆散数组再传参

需要调用的函数.apply(替换this的对象,包含实参值的数组)

问题:call和apply只能临时替换一次this

如果反复调用函数,每次都要用call和apply临时替换this

(3)创建函数副本并永久绑定this

如何使用:

新函数(实参值...)

强调:

因为bind()已提前将指定对象替换新函数this,

所以后续每次调用新函数副本时,不需要替换this

bind()三件事

1)创建一模一样的新函数副本

2)永久替换this为指定对象

3)永久替换部分形参变量为固定的实参值

总结:

  • 只在一次调用函数时,临时替换一次this:call
  • 既要替换一次this,又要拆散数组再传参:apply
  • 创建新函数副本,并永久绑定this:bind

3、创建对象:JS中创建对象共有几种方式

js中创建对象10种方式:

1、new Object()  缺点:步骤多

2、字面量:var 对象名 = { }   缺点:如果反复创建多个对象,代码会很冗余

3、工厂函数方式:

缺点:本质还是Object(),将来无法根据对象的原型对象准确判断对象的类型

function createPerson(name,age){
    var o = new Object()
    o.name = name
    o.age = age
    o.say = function(){
        alert("",this.name)
    }
    return o
}
var p1 = createPerson("Lilei",11)

4、构造函数:2步

-先用构造函数定义一类对象的统一属性结构和方法

-再用new调用构造函数,反复创建相同结构属性,不同属性值的多个对象

function Student(name,age){
    this.name = name;
    this.age = age;
    this.intr = function(){...}
}
var lilei = new Student("LiLei",11)

缺点:如果构造函数中包含方法,则重复创建,浪费内存

5、原型对象方式:先创建完全相同的对象,再给子对象添加个性化属性

 缺点:步骤繁琐

function Person(){}
Person.prototype.name = "";
Person.prototype.age = 11;
Person.prototype .say = function(){}
var p1 = new Person();
p1.name = "LiLei"

6、混合模式:先创建完全相同的对象,再给子对象添加个性化属性

  缺点:不符合面向对象封装的思想

function Person(name,age){
    this.name = name
    this.age = age
}
Person.prototype.say = function(){}
var p1 = new Person("Li Lei",11)

7、动态混合:先创建完全相同的对象,再给子对象添加个性化属性。

缺点:语义不符,其实if只在创建第一个对象时有意义

function Person(name,age){
    this.name = name
    this.age = age
    if(Person.prototype.say = undefined){
        Person.prototype.say = function(){}
    }
}

8、寄生构造函数:构造函数里调用其他的构造函数(借鸡生蛋)

缺点:可读性差

function Person(){
    this.name = name
    this.age = age
    if(Person.prototype.say = undefined){
        Person.prototype.say = function(){}
    }
}
function Student(name,age,className){
    var p = new Person(name.age)
    p.className = className
    return p
}
var p1 = new Student("Li Lei",11,"初一2班")

9、ES6   class

class:程序中专门集中保存,一种类型的所有子对象

如何定义class

i 用class{}包裹原结构函数+原型对象方法

ii 原构造函数名升级为整个class的名字,所有构造函数统一更名为“constructor”

iii原型对象中的方法,不用再加prototype前缀,也不用 = function,直接简写为方法名(){ ... }

10、稳妥构造函数:闭包,不用this。不用new   安全,可靠

4、继承——JS中实现继承共有几种方式

7种

1、原型链式继承:将父类实例作为子类的原型

//定义一个父类型
function Animal(name){
    this.name = name
    this.say = function(){
        console.log()
    }
}
//原型对象方法
Animal.prototype.eat = function(food){
    
}
function Cat(){}
Cat.prototype = new Animal()
Cat.prototype.name = 'cat'var cat = new Cat()

2、构造函数继承

//定义一个父类型
function Animal(name){
    this.name = name
    this.say = function(){
        console.log()
    }
}
//原型对象方法
Animal.prototype.eat = function(food){}function Cat(name,age){
    Animal.call(this.name)
    this.age= age
}

3、实例继承

//定义一个父类型
function Animal(name){
    this.name = name
    this.say = function(){
        console.log()
    }
}
//原型对象方法
Animal.prototype.eat = function(food){}function Cat(name,age){
  var o = new Animal(name)
  o.age = age
  return instance
}

4、拷贝继承:无法获取父类不可for in遍历的方法

//定义一个父类型
function Animal(name){
    this.name = name
    this.say = function(){
        console.log()
    }
}
//原型对象方法
Animal.prototype.eat = function(food){}
function Cat(name,age){
    var animal = new Animal(name)
    for(var p in animal){
        Cat.prototype[p] = animal[p]
    }
    this.age = age
}
var cat = new Cat()

5、组合式继承

//定义一个父类型
function Animal(name){
    this.name = name
    this.say = function(){
        console.log()
    }
}
//原型对象方法
Animal.prototype.eat = function(food){}
function Cat(name,age){
    Animal.call(this,name)
}
cat.prototype = new Animal()
Cat.prototype.constructor = Cat
var Cat = new Cat()

6、寄生组合继承

//定义一个父类型
function Animal(name){
    this.name = name
    this.say = function(){
        console.log()
    }
}
//原型对象方法
Animal.prototype.eat = function(food){}
function Cat(name,age){
    Animal.call(this,name)
}
(function(){   //创建一个没有实例的方法
    var Super = function(){}
    Super.prototype = Animal.prototype  //将实例作为子类的原型
    Cat.prototype = new Super()
})()
var Cat = new Cat()

7、ES6  class  extends继承

5、深克隆——实现克隆共有几种方式

1、创建一个空对象等着

2、遍历原对象中所有属性

3、遍历每一个属性

4、返回克隆好的新对象

浅克隆:只复制对象的第一级属性,如果对象的第一级属性中又包含引用类型,则只复制地址

浅克隆问题:如果对象中又包含引用类型的属性值,则导致克隆后,新旧对象依然共同引用同一个类型的对象属性值

结果:任意一方修改了引用类型的对象内容,都会导致另一方同时受影响

深克隆:不但复制对象的第一级属性,而且,即使对象中又包含引用类型的属性值,深克隆也会继续赋值内嵌类型的属性值

实现克隆对象的三种方法:

1、JSON.stringfy()以及JSON.parse()

无法深克隆undefined值和内嵌函数

2、Object.assign(target,source)

3、自定义递归克隆函数

function deepClone(){
    let newObj;//定义一个变量,准备接新副本对象
    //如果当前需要深拷贝的是一个引用类型对象
    if(typeof target == 'object'){
       if(Array.isArray(target)){//如果是一个数组
           newObj = []     //将newObj赋值为一个数组,并遍历
            for(let i in target){   //递归克隆数组中的每一项
                newObj.push(deepClone(target[i]))
            }
        //判断如果当前的值为null,直接赋值为null
        } else if(target === null){
            newObj = []
        //判断如果当前的值是一个正则表达式对象,直接赋值
        }else if(target.constructor === RegExp){
            newObj = target
        }else{
            //否则是普通对象,直接for in 循环递归遍历复制对象中的每个属性值
            newObj= {}
            for(let i in target){
                newObj[i] = deepClone(target[i])
            }
        }
        // 如果不是对象而是原始数据类型,那么直接赋值
    }else{
        newObj = target
    }
    return newObj
}