一、面向对象编程介绍
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...
// 返回一个由属性名组成的数组