面向对象JS ES5/ES6 类的书写 继承的实现 new关键字执行 this指向 原型链
一 、什么是面向对象?
编程范式
一般可以分为两种:
声明式,告诉计算机应该做什么,但不指定具体要怎么做。如:html,css等
命令式,关注计算机执行的步骤,即一步一步告诉计算机先做什么再做什么。如:js,c++,java,c#,python等
其中命令式编程又可以为两类
面向过程:
将我们要解决问题所需要的步骤分析出来,然后用函数把这些步骤一步一步实现(有序步骤),关注细节。
面向对象:
将问题中的对象抽象出来,既把构成问题的事物分解成各个对象,而解决问题的不是一个个过程化的步骤,
而是利用对象描述、处理“问题”的行为集合(离不开面向过程)。
二、面向对象的基本概念
1、对象
对象的含义是指具体的某一个事物,即在现实生活中能够看得见摸得着的事物,有句话叫做:万物皆对象。
对象是类的具体实例,对象的抽象是类。
2、类
类是具有相同特性(数据元素)和行为(功能)的对象的抽象,代表一类事物的抽象描述。
在代码中:类好比是一个模板,可以批量生产。
类是对象的抽象,类的具体化就是对象。
类的实现
原本JS不支持面向对象(oo),所以在ES5用函数模拟出类,在ES6时加入语法糖,书写起来更加清晰、更像面向对象编程的语法。
类的本质是函数:构造函数+全局公共区域 prototype。
ES5
//混合模式:构造函数模式+原型模式 function Person(name, age) {//构造函数模式 this.name = name; /*属性*/ this.age = age; this.study = function () { console.log(this.name + ‘在运动‘); } } //原型模式 prototype,原型链上面的属性会被实例对象共享 Person.prototype.sex = "男";//共有的属性 Person.prototype.exercise = function () {//共有的函数 console.log(this.name + ‘在锻炼‘); } //类的静态属性和方法不会被实例对象共享 Person.num = 11234;//定义静态属性 Person.printNum = function () {//定义静态方法 console.log(this.num); }//注:函数也是一个对象,所谓的静态属性方法,本质就是给名为Person的对象添加了属性和方法 var stu = new Person(‘张三‘, 20); console.log(stu);//Person { name: ‘张三‘, age: 20, study: [Function] } console.log(stu.sex);//男 stu.study();//调用自带方法 => 张三在运动 stu.exercise(); //调用原型方法 => 张三在锻炼 console.log(Person.num);//11234 静态属性和方法 通过 类名.方法(属性名) 调用或访问 console.log(stu.num);//undefined 实例对象不能使用 Person.printNum();//11234 stu.printNum();//报错 stu.printNum is not a function
ES6
class Person { constructor(name, age) {//构造函数 this.name = name; /*属性*/ this.age = age; this.sex = "男"; this.study = function () { console.log(this.name + ‘在运动‘); } } static num = 11234;//定义静态属性 static printNum() {//定义静态方法 console.log(this.num); } exercise() {//共有的函数 console.log(this.name + ‘在锻炼‘); } } var stu = new Person(‘张三‘, 20); console.log(stu);//Person { name: ‘张三‘, age: 20, sex: ‘男‘, study: [Function] } console.log(stu.sex);//男 stu.study();//张三在运动 stu.exercise(); //张三在锻炼 console.log(Person.num);//11234 console.log(stu.num);//undefined Person.printNum();//11234 stu.printNum();//报错 stu.printNum is not a function
注:两种方式都能成功的声明一个Person类,现在一般就用ES6方式,但是其实内部是用ES5的方式运行的,ES6方式相当于一个语法糖。
关于new
每次使用new关键字会执行的步骤:
1、创建一个空的新对象,作为将要返回的对象实例
var obj = {};
2、将该对象的__proto__(隐式原型)指向创建该对象的类的原型对象
//假设类叫做 A
obj.__proto__ = A.prototype;
3、将构造函数环境中的this,指向该对象
相当于:this = obj;//意识到位,代码这样写是错误的
4、执行构造函数中的代码,并返回刚刚创建的新对象
注:class 定义的类,必须使用new关键字调用,不然会报错,
new关键字总是返回一个对象,如果new 普通函数,会返回一个空对象。
关于构造函数constructor
constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。
返回值
构造函数也有返回值,默认返回实例对象(即this),可以更改,若改为基本数据类型的返回值,则不影响函数。
若改为复杂数据类型的返回值,则会直接将该返回值直接返回,会导致返回值不再是原来类的实例对象,
三、面向对象三大特征
1、封装
隐藏内部细节,不暴露在外面;做到信息的隐藏,不关心具体怎样实现的,只需要知道能达到效果就行了。
具体表现形式:
函数 是对于算法的封装
类 是对于属性和方法的封装
2、继承
子类继承父类的所有属性和方法,并且扩展自己的属性和行为。
类与类之间的关系:is a(是一个) 例如:学生——>人类
实现
function Student(){ Person.call(this); //对象冒充实现继承 对象冒充可以继承构造函数里面的属性和方法 但是没法继承原型链上面的属性和方法 } //原型链实现继承 原型链实现继承:可以继承构造函数里面的属性和方法 也可以继承原型链上面的属性和方法 Student.prototype = Object.assigin(Person.prototype); //与拷贝类似,但不同 //Student.prototype=new Person(); //这种方法功能是能实现,但是太矬了 // Student.prototype = Object.create(Person.prototype); //没有将create的储存起来,只是利用原型链找到有属性的父类
改变this指向的三个函数实例方法
var name = 99; function foo(a, b) { console.log(this.name, a, b);//this本来指向全局的 name=99 } let zs = { name: "zs" } let ls = { name: "ls" } //call方法,可以指定函数内部this的指向(即函数执行时所在的作用域),然后在所指定的作用域中,调用该函数第一个参数(对象)为指向。 foo.call(ls, 1, 2);//ls 1 2 //apply方法的作用与call方法类似,也是改变this指向,然后再调用该函数。唯一的区别就是,它接收一个数组作为函数执行时的参数 foo.apply(zs, [2, 3]);//zs 2 3 //bind方法用于将函数体内的this绑定到某个对象,然后返回一个新函数。这个要主动调用 let bar = foo.bind(zs); bar(3,4);//zs 3 4
ES6
// 图形祖宗 class Graph { constructor(color, top, left) { this.color = color; this.top = top; this.left = left; this.node = null; } creatNode() { } addNode() { document.body.appendChild(this.node); } }
// 矩形 class Rectangle extends Graph { constructor(color, top, left, lengthX, lengthY) { super(color, top, left); this.lengthX = lengthX; this.lengthY = lengthY; } creatNode() { this.node = document.createElement(`div`); this.node.style.position = "absolute"; this.node.style.background = this.color; this.node.style.top = this.top + "px"; this.node.style.left = this.left + "px"; this.node.style.width = this.lengthX + "px"; this.node.style.height = this.lengthY + "px"; } }
extends 扩展 从子类,扩展到父类 即继承父类
super 超类 继承父类 用于调用父亲的构造,必须与extends关键字一起使用,且只能在构造函数中所有的this之前书写
3、多态
方法(行为) 相同的行为不同的实现 传的参数不一样,决定了实现方法不一样
代码中的多态:
重载 参数的不同,实现不同 js自带,因为js没有严格的指定参数的个数类型
重写 父亲的行为,儿子重写一个覆盖了
四、this和原型对象
this指向问题
1、箭头函数中,没有自己的this,它借用声明箭头函数处(环境中的)的this(就固定这个值)
2、谁调用,指向谁
3、事件监听函数中,this指向,绑定监听函数的那一个元素节点(谁绑,指向谁)
4、当一个函数没有被其他对象调用时,(普通调用),this指向全局对象(严格模式下面是undefined)
5、在执行构造函数时(new),this指向新创建的对象
原型对象
规则:
所有function创建的对象,都叫做函数对象(函数)
所有函数对象上都有一个原型对象,prototype
所有对象上都有一个隐式原型对象,__proto__,指向创建该对象的构造函数的原型对象
所有原型对象上都有一个constructor对象,指向该原型对象所在的构造函数本身
或者这样说:
对象可以分为函数对象(所有直接通过function创建的对象)和一般对象(其他对象),所有的对象在创建时,都会创建一个对应的原型对象
函数对象的原型对象为:函数.prototype,函数.prototype上都有一个constructor对象,指向该原型对象所在的构造函数本身
一般对象的原型对象被称为隐式(和函数对象的原型对象相区分)原型对象: 对象.__proto__,指向创建该对象的构造函数的原型对象
原型链:
一个对象通过自身的__proto__,一直向上链接,直到null,所构成的的链条,就是原型链
原型链作用:用来找属性
当一个对象在访问自身某个属性时,会先在自己身上找,没有找到就通过原型链(__proto__),层层向上找,直到找完为止
若找完整个原型链都没有找到,那么该属性的值就是,undefind。