一、面向对象编程介绍

1、面向过程编程(POP)

  • 分析出解决问题所需要的步骤
  • 用函数把这些步骤一步一步实现
  • 使用的时候再一个一个依次调用
  • 举例
  • 大象装冰箱
  • 打开冰箱
  • 装进大象
  • 关闭冰箱

2、面向对象编程(OOP)

  • 把事务分解成一个个对象
  • 由对象之间分工与合作
  • 以对象功能来划分问题,而不是步骤
  • 举例
  • 大象装冰箱
  • 大象(对象)
  • 进入冰箱
  • 冰箱(对象)
  • 开门
  • 关门
  • 面向对象的特性
  • 封装性
  • 继承性
  • 多态性

3、面向对象与面向过程对比

  • 面向过程
  • 优点
  • 性能比面向对象高,适合跟硬件联系很紧密的东西,例如单片机就采用的面向过程编程
  • 缺点
  • 没有面向对象易维护、易复用、易扩展
  • 面向对象
  • 优点
  • 易维护、易复用、易扩展,由于面向对象由封装、继承、多态性的特性,可以设计出低耦合的系统,使系统,更加灵活,更加易于维护
  • 缺点
  • 性能比面向过程低

二、ES6中的类和对象

1、面向对象思维特点

  • 抽取(抽象)对象共用的属性和行为组织(封装)成一个类(模板)
  • 对类进行实例化,获取类的对象

2、对象

在JavaScrrpt中,对象是一组无序的相关属性和方法的集合,所有的事物都是对象。

对象是一个具体的事物。实实在在看的见,摸得着。

对象是由属性和方法组成

  • 属性:事物的特征,在对象中用属性来表示(常用名词)
  • 方法:事物的行为,在对象中用方法来表示(常用名词)

3、类(class)

在ES6中新增加了类的概念,可以使用class关键字声明一个类,之后以这个类来实例化对象

类抽象了对象的公共部分,它泛指某一大类(class)

对象特指某一个,通过实例化一个具体的对象

4、创建类

// 结构
class name {
    // class body
}

// 创建实例
new name()
// 举例

// 1、创建 class 创建一个 明星类
// 类名首字母大写,类名后没有小括号
class Star{
    // constructor函数:构造函数
    // 构造函数不需要加function
    // 可以接收传递过来的参数,同时返回实例对象
    // constructor函数只要new生成实例时,就会自动调用这个函数
    // 如果我们不写constructor函数,类也会自动生成这个函数
    constructor(uname){
        this.uname = uname;
    }
}

// 2、利用类创建对象
// 生成实例 new 不能省略,类名后加小括号
var ldh = new Star('刘德华')
console.log(ldh.uname); // 刘德华

5、类中添加方法

class Star{
    constructor(uname){
        this.uname = uname;
    }
    // 类中函数不需要写function
    // 多个函数之间,不用逗号分隔
    sing(sang){
        console.log(sang)
    }
}
var ldh = new Star('刘德华')
ldh.sing('冰雨')

6、类的继承

class Father{
    constructor(){
    }
    firstName(){
        console.log('张')
    }
}
class Son extends Father{
}

var son = new Son();
son.firstName() // 张

7、super关键字

用于访问和调用对象父类上的函数。

可以调用父类的构造函数,也可以调用父类的普通函数

// 调用构造函数
class Father{
    constructor(x,y){
        this.x = x;
        this.y = y; 
    }
    sum(){
        console.log(this.x + this.y)
    }
}
class Son extends Father{
    constructor(x,y){
        super(x,y); // 调用了父类中的构造函数 
    }
}
var son = new Son(1,2)
son.sum()
class Father{
    say(){
        console.log('Father')
    }
}
class Son{
    say(){
        console.log('Son')
    }
}
class Son1 extends Father{
    say(){
        console.log('Son1')
    }
}
class Son2 extends Father{
    say(){
        super.say()
    }
}
var son = new Son()
son.say() // Son
var son1 = new Son1()
son1.say() // Son1
var son2 = new Son2()
son2.say() // Father

// 就近原则
// (1) 继承中,如果实例化子类输出一个方法,先看子类有没有这个方法,如果有,先执行子类
// (2) 继承中,如果子类里面没有,就去查找父类有没有这个方法,如果有,就执行父类的这个方法

8、子类扩展自己方法

class Father{
    constructor(x,y){
        this.x = x;
        this.y = y; 
    }
    sum(){
        console.log(this.x + this.y)
    }
}
// 子类继承父类 sum 方法。同时扩展 substract 方法
class Son extends Father{
    constructor(x,y){
        // 利用 super 调用父类的构造函数
        // super 必须在 this 之前调用
        super(x,y);
        this.x = x;
        this.y = y;
    }
    substract(){
        console.log(this.x - this.y)
    }
}
var son = new Son(5,3)
son.substract()

9、类和对象使用注意点

  • 在ES6中类没有变量提升,所以必须先定义类,才能通过类实例化对象
  • 类里边的的共有属性和方法一定要加this使用
  • 类中this指向问题
  • constructor中的this,指向的是,创建的实例对象
  • 方法中的this指向这个方法的调用者

二、构造函数和原型

1、实例成员和静态成员

  • 实例成员
  • 构造函数内部通过this添加的成员
  • 实例成员只能通过实例化的对象访问
  • 静态成员
  • 在构造函数本身添加成员
  • 只能通过构造函数来访问
function Star(uname,age){
    this.uname = uname;
    this.age = age;
    this.sing = function () {
        console.log('唱歌')
    }
}
// 实例成员
var ldh = new Star('刘德华')
console.log(ldh.uname) // 刘德华
ldh.sing()	// 唱歌
console.log(Star.uname) // undefined
// 静态成员
Star.sex = '男'
console.log(Star.sex) // 男
console.log(ldh.sex) // undefined

2、构造函数的问题

  • 存在浪费内存的问题
  • 使用prototype存放方法解决

3、原型对象(prototype)

prototype:构造函数原型

构造函数通过原型分配的函数是所有对象所共享的。

JS规定,每个构造函数都有一个prototype属性,指向另一个对象。

这个prototype就是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有。

function Star(uname,age){
    this.uname = uname;
    this.age = age;
}
// 一般情况下,公共属性定义到构造函数里边,公共的方法放到原型对象上
Star.prototype.sing = function () {
    console.log('唱歌')
}
var ldh = new Star('刘德华')
var zxy = new Star('张学友')
ldh.sing()
zxy.sing()

4、对象原型(__proto__)

对象身上系统添加一个__proto__指向我们构造函数的原型对象prototype

方法的查找规则:

  • 首先,查找对象本身有没有该方法
  • 如果没有,因为__proto__存在,就去构造函数原型对象prototype查找
function Star(uname,age){
    this.uname = uname;
    this.age = age;
}
// 一般情况下,公共属性定义到构造函数里边,公共的方法放到原型对象上
Star.prototype.sing = function () {
    console.log('唱歌')
}
var ldh = new Star('刘德华')
console.log(ldh.__prototype === Star.prototype) // true

5、构造函数(constructor)

对象原型和构造函数原型对象都有一个属性constructor,成为构造函数,因为它指回构造函数本身

  • 作用
  • 主要记录该对象引用于哪个构造函数
  • 可以让原型对象重新指向原来的构造函数

6、原型链

// 以Star类举例
Star对象实例.__proto__ = Star.prototype
Star.prototype.__proto__ = Object.prototype
Object.prototype.__proto__ = null

构造函数.prototype = 原型对象
原型对象.constructor = 构造函数

7、成员查找机制

  • 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性
  • 如果没有,就查找它的原型(也就是__proto__指向的prototype原型对象)
  • 如果还没有,就查找原型对象的原型(Object的原型对象)
  • 依此类推,一直找到Object为止(null)

8、原型对象中this指向

  • 在构造函数中,this指向的是对象实例
  • 在原型对象中,this指向的是对象实例

9、扩展内置对象

// 给数组增加自定义求偶数和的功能
Array.prototype.sum = function (){
    var sum = 0;
    for(var i=0;i<this.length;i++){
        sum += this[i]
    }
    return sum;
}

var arr = [1,2,3]
console.log(arr.sum())

三、继承

ES6之前并没有提供extends继承。可以通过构造函数+原型对象模拟实现继承,被称为组合继承

1、call()

  • 调用函数
  • 修改函数运行时的this指向
fun.call(thisArg,arg1,arg2, ...)
// fun:函数
// thisArg:当前调用函数this的指向对象
// arg1,arg2:传递的其他参数
// 定义函数
function fn(){
    console.log('喝咖啡')
    console.log(this)
}
// 定义对象
var o = {
    name:'张三'
}

// 1、调用函数
fn.call() // 喝咖啡
		  // window 对象

// 2、修改函数运行时的`this`指向
fn.call(o)	// 喝咖啡 
			// { name:'张三'}

2、借用父构造函数继承属性

// 父构造函数
function Father(uname,age){
    // this 指向父构造函数的对象实例
    this.uname = uname;
    this.age = age;
}

// 子构造函数
function Son(uname,age,sex){
    // this 指向子构造函数的对象实例
    Father.call(this,uname,age)
    this.sex = sex;
}

var son = new Son('andy',18,'男')
console.log(son) // 获得son对象

3、借用原型对象继承方法

// 父构造函数
function Father(){}
Father.prototype.firstName = function () {
    console.log('张')
}
// 子构造函数
function Son(uname,age,sex){}
// 借用原型对象继承方法
Son.prototype = new Father();
// 如果不将构造函数重新指回Son,此时为Father的构造函数
Son.prototype.constructor = Son;

4、类的本质

  • 类的本质其实还是一个函数。可以简单的理解为 构造函数的另外一种写法。

四、ES5新增方法

1、遍历数组(forEach)

array.forEach(function (currentValue,index,arr))
// currentValue:数组当前项的值
// index:数组当前项的索引
// arr:数组本身
var arr = [1,2,3];
var sum = 0;
arr.forEach(function (value,index,array) {
    console.log('每个数组元素:' + value)
    console.log('每个数组元素的索引号:' + value)
    console.log('数组本身:' + array)
    sum += value;
})
console.log(sum) // 6

2、遍历数组(filter)

array.filter(function (currentValue,index,arr))
// currentValue:数组当前项的值
// index:数组当前项的索引
// arr:数组本身

// filter:创建一个新数组,新数组中的元素是通过检查指定数组中符合条件的所有元素,主要用于筛选
var arr = [12,66,4,88];
var newArr = arr.filter(function (value,index,array) {
    return value >= 20;
})
console.log(newArr) // [66,88]

3、遍历数组(some)

array.some(function (currentValue,index,arr))
// currentValue:数组当前项的值
// index:数组当前项的索引
// arr:数组本身

// some()方法用于 查找数组中是否有满足条件的元素
// 注意它返回值是布尔值,如果查找到这个元素,就返回true;查找不到返回,false
// 如果找到第一个满足条件的元素,则终止循环,不再继续查找
var arr = [10,30,4]
var flag = arr.some(function (value,index,array) {
    return value >= 20;
})
console.log(flag) // true

4、字符串两端删除空白字符(trim)

str.trim()

// trim本身不影响原字符串本身,它返回的是一个新的字符串

5、Object.defineProperty

定义对象中新属性或修改原有的属性

Object.defineProperty(obj,prop,descriptor)

// obj:对象(必需)
// prop:需定义或修改属性的名字(必需)
// descriptor:目标属性所拥有的特性(必需),有以下值供选择
// 		value:设置属性的值,默认undefined
//		writable:值是否可以重写。true|false 默认false
//		enumerable:目标属性是否可以被枚举。true|false 默认false
//		configurable:目标属性是否可以被删除或是否可以再次修改特性。true|false 默认false
var obj = {
    id:1,
    pname: '小米',
    price: 1999
}

Object.defineProperty(obj,'num',{
    // 添加 num: 1000
    value: 1000
})

Object.defineProperty(obj,'id',{
    // 不可修改id的值
    writable: false
})

Object.defineProperty(obj,'address',{
    // 添加 address: '中国'
    value: '中国'
    // 不允许被遍历。例如用Object.keys()时
    enumerable: false
})

Object.defineProperty(obj,'id',{
    // 不允许被删除
    configurable: false
})

Object.defineProperty(obj,'id',{
    // 此处修改id的其他三个特性,都编程true
    // 但是因为上一步中(configurable: false)的设置,则不允许,修改其他特性
    writable: true,
    enumerable: true,
    configurable: true
})

6、Object.keys

获取对象所有属性

Object.keys(obj)

// 效果类似:for...in...
// 返回一个由属性名组成的数组