- 在面向对象的程序开发思想中,每一个对象都是功能中心,具有明确分工
- 面向对象编程具有灵活、代码可复用、容易维护和开发的优点
特点:
- 封装性
- 继承性
- 多态性
面向对象的优点:
易复用、易维护、易扩展
ES6中的对象和类
- 抽取对象共有的属性和方法(封装)组成一个类(模板)
- 对类进行实例化,获取类的对象
对象
- 万物皆对象
- 对象特指某一个
- 在 javascript 中,对象是一组无序的相关属性和方法的集合,所有事物都是对象
- 对象是由属性和方法组成
- 属性:事务的特征,在对象中用==属性==来表示(常用名词)
- 方法:事务的行为,在对象中用==方法==来表示(常用动词)
类
- 在ES6中类没有变量提升,所以必须先定义类,才能实例化对象
- 类里面共有的属性和方法一定要加this使用
- constructor里面的this指向实例对象,方法里面的this指向这个方法的调用者
类的定义
- ES6之前通过 构造函数 实现面向对象编程
-
构造函数有prototype属性指向原型对象
-
构造函数的prototype里面有 constructor 属性指向构造函数本身
-
构造函数可以通过原型对象添加方法
-
构造函数的实例对象有_____proto_____ 指向构造函数的原型对象
class Star{ constructor(){ } say(){ console.log('say'); } } let curry = new Star(); // 1. 类的本质还是一个函数 ,我们可以认为类就是构造函数的另一种写法 console.log(typeof Star); //function // 2. 类也有 prototype 指向原型对象 console.log(Star.prototype); // 3. 类的 prototype 里面也有 constructor 属性 指向 (构造函数本身)类 console.log(Star.prototype.constructor); // 4. 类 也可以通过原型对象添加方法 Star.prototype.sing = function () { console.log(123); } console.log(Star.prototype); // 5. 类的实例对象也会有__proto__ 指向类的原型对象 console.log(curry.__proto__);
-
创建类
// 创建一个明星类 class
class Star{
// 类里面有构造函数constructor 实例调用时执行
constructor(name){
// this 指的是创建的对象
this.name = name
}
sing(song){
console.log(this.name +'---'+ song);
}
}
// 利用类创建一个对象 new
// 对象传入'kebo’ 被 constructor接收 -> 去执行constructor
// kebo 就有了name属性
let kobe = new Star('kebo');
console.log(kobe);
kobe.sing('baskball');
- 通过 class 关键字创建类,类名首字母大写
- 类里面有 constructor 函数,可以接受传过来的参数,同时返回给实例对象
- constructor 函数 只要new实例生成时,就会自动调用这个函数,如果我们不写,如果我们不写,类也会生成这个函数
- 生成对象要用 new
- 语法规范 :
- 生成类后面直接大括号
- 生成实例对象后面小括号
- 构造函数不用加 function
- 类里面创建方法都不不要 function 直接方法名+小括号+大括号
- 方法与方法之间不需要 逗号
类的继承
// 类的继承
class Grandfather{
constructor(){
}
money(){
console.log(100);
}
}
// father 就 继承了Grandfather
class Father extends Grandfather{
}
// curry的实例就继承了爷爷 并且继承了爷爷的钱
let curry = new Father();
curry.money(); //100
-
继承中,如果调用一个方法,先查找子类自己有没有这个方法,如果有就执行子类的(就近原则),如果没有就一层一层向上找
#####super关键字
-
super关键字用于访问和调用对象父类上的函数,可以调用父类的构造函数,也可以调用父类的普通函数。
-
super 还起初始化的作用
class Person{ constructor(a,b){ this.a = a; this.b = b; } sum(a,b){ return a+b } add(who){ return who } } class Justin extends Person{ constructor(a,b){ super(a,b);//调用父类的构造函数 } say(name){ // 调用父类的普通方法 return super.add('我是') +'-----' + name } } let justen = new Justin(); console.log(justen.sum(10,20)); console.log(justen.say('justin')); console.log(justen.add('aaa')); -
想使用自己的方法 并扩展使用父类的方法 super关键字在写在前面
-
子类在构造函数中使用super,必须放在this前面(先调用父类的构造方法,在子类)
// super 关键字 // 定义一个计算的大类 class Count{ constructor(x,y){ this.x = x; this.y = y; } sum(x,y){ return x+y } } // 子类有一个减法方法 ,我想同时使用我的减法并且使用父类的加法 class SmallCount extends Count{ constructor(x,y){ // 把参数给到父类的构造函数 (存在super和自己的属性时 super要写在上面) // 这里注意 :并不是写了super才可以调用父类的方法,既然写了 extends 关键字就可以使用父类的方法 // 只是把参数给到父类的构造函数 super(x,y); this.x = x; this.y = y; } sub(x,y){ return x-y } } let numA = new SmallCount(); console.log(numA.sub(20,10)); //10 console.log(numA.sum(20,10));//30
类的公共方法
类里面的公有属性和方法一定要加this
class Person{
<!--constructor里面的this指向的是创建的实例对象-->
constructor(name,age){
this.name = name,
this.age = age,
//记得一定要加this
this.sing()
}
<!--谁调用这个方法 this指向谁-->
sing(){
console.log('我会唱歌');
}
}
let curry = new Person('curry',18);
console.log(curry);
会默认调用sing() 这里就会直接打印 ‘我会唱歌’ 一定要加this
构造函数和原型
构造函数
-
在ES6之前 javascript 没有类的概念 所以用构造函数来定义类的概念
-
构造函数是一种特殊的函数,主要用来初始化对象,即为对象成员变量赋初始值,他总是与new 一起使用,我们可以把对象中的一些公共属性和方法抽取出来,封装到这个幻函数中
function Person(a,b) { this.a = a; this.b = b; Person.prototype.add=function(x,y){ return x+y } this.sub = function(x,y){ return x-y } } new Person()
new在执行中会做的四件事
- 再内存中创建一个新的空对象
- this指向这个新对象
- 执行构造函数里的代码,给这个新对象添加属性和方法
- 返回新对象
####构造函数中的属性和方法我们称为成员 成员分为:静态成员和实例成员 实例成员:构造函数内部通过 ==this== 添加的成员 ——> 实例成员只能通过实例化的对象来访问 静态成员:在构造函数本身上添加的成员 ——>静态成员只能通过构造函数来访问
function Person(a, b) {
this.a = a;
this.b = b;
this.sub = function (x, y) {
return x - y;
};
}
let curry = new Person('z','y');
console.log(curry.a); // 'z' 实例成员
Person.sex = '男'
console.log(Person.sex) //男 静态成员
构造函数的问题
-
构造函数存在内存浪费的问题,每次创建一个新对象,像属性之类的可以直接赋值给新对象,但是像函数这种复杂数据类型又会开辟一份新的内存空间,100个实例对象 就会开辟100份内存
function Person(a, b) { this.a = a; this.b = b; this.sub = function (x, y) { return x - y; }; } let curry = new Person(123,456); let kobe = new Person(111,222); console.log(curry.sub===kobe.sub); //false console.log(curry.a===kobe.a); //false解决方案:
-
我们希望所有的实例对象使用同一个函数,已达到节省内存的目的,所以我们使用构造函数原型: ==prototype== 构造函数里通过原型分配的函数是所有对象 ==共享的== -
javascript 规定 ,==每一个构造函数都有一个 prototype 属性== ,指向另一个对象,注意这个prototype 就是就个对象 prototype 翻译过来就是原型 所以说 ,==每一个构造函数都有一个原型对象==,这个对象中的属性和方法,都会被构造函数所拥有 -
一般情况下,我们把简单数据类型直接放到构造函数上就可以,一些不变的方法,直接定义在prototype对象上,这样所有对象的实例就可以共享这些方法
-
function Person(a, b) {
this.a = a;
this.b = b;
Person.prototype.say = function () {
console.log('我们是原型上的方法');
};
}
let curry = new Person(123, 456);
let kobe = new Person(111, 222);
curry.say(); //我们是原型上的方法
kobe.say(); //我们是原型上的方法
console.log(curry.say===kobe.say); //true
对象原型 proto
- 疑问:为什么我们实例对象可以访问到构造函数的原型对象上的方法?
- 回答:就是因为我们实例对象都会有一个属性____proto_____ ,而这个属性就指向构造函数的原型对象
_____proto_____ === 构造函数的原型对象
补充:方法的查找规则:先看实例对象自己身上是否有那个方法,有的话执行自己的方法,没有的话,因为有 _____proto_____的存在,就去构造函数的原型对象上找
constructor 构造函数
- 主要作用是实例对象是通过那个构造函数创建的
- 实例对象原型(proto) 和 构造函数的原型对象(prototype) 里面都有一个constructor 属性,因为他指向构造函数本身,所以我们称为构造函数
- constructor 主要记录该实例对象主要引用哪个构造函数
function Person(a, b) {
this.a = a;
this.b = b;
Person.prototype.say = function () {
console.log('我们是原型上的方法');
};
Person.prototype.sum = function(){
console.log('sum');
};
}
let curry = new Person(123, 456);
let kobe = new Person(111, 222);
curry.__proto === Person.prototype
console.log(curry.__proto__);
console.log(Person.prototype);
// 输出:
// {say: ƒ, sum: ƒ, constructor: ƒ}
构造函数为Person
//constructor: ƒ Person(a, b)
-
很多时候我们需要手动的利用 constructor 这个属性指向原来的构造函数
function Person(a, b) { this.a = a; this.b = b; Person.prototype = { constructor:Person, <!--会强制指向--> say(){ console.log('say'); }, sum(){ console.log('sum'); } } } let curry = new Person(123, 456); let kobe = new Person(111, 222); console.log(curry.__proto__); console.log(Person.prototype); <!-- --------------------------------------------------------> function Person(a, b) { this.a = a; this.b = b; Person.prototype = { constructor:Person, say(){ console.log('say'); }, sum(){ console.log('sum'); } } } let curry = new Person(123, 456); let kobe = new Person(111, 222); console.log(curry.__proto__.constructor); console.log(Person.prototype.constructor); console.log(curry.__proto__.constructor===Person.prototype.constructor); //ture
原型链
对象成员查找机制
- 当访问一个对象的属性或方法时,首先查找这个对象自身有没有该属性
- 如果没有就查找它的原型(也就是_____proto_____指向的prototype原型对象)
- 如果还是没有就查找原型对象的原型
- 一直找到Object为止(null)
- _____proto_____的意义是为对象成员提供一个查找线路
原型对象上的this
- 在构造函数中,里面的this指向的是实例对象
- 原型对象函数里面的this 也指向实例对象
利用原型对象(prototype)添加新内置对象方法
<!--查看数组内置方法-->
console.log(Array.prototype);
<!--利用原型对象添加-->
Array.prototype.sum=function(){
let sum = 0
for (let i = 0; i < this.length; i++) {
sum+=this[i]
}
return sum
}
var arr = [3,2,4];// 9
console.log(arr.sum());
var arr2 = new Array(3,2,4);
console.log(arr2.sum()); //9
<!--错误示范,不可以使用这种方式,因为这样是赋值 会覆盖原先的方法 这是不被允许的-->
Array.prototype={
// constructor:Array,
sum:function(){
let sum = 0
for (let i = 0; i < this.length; i++) {
sum+=this[i]
}
return sum
}
}
改变this指向
-
call 方法
// call 方法 1、调用这个函数 ,2、并且修改函数运行时this的指向 // fun.call(thisObj,arg1,arg2) // thisObj ->当前调用函数this的指向对象 // arg1,arg2 ->传递的其他参数 function fn(){ console.log('fn'); console.log(this); } let Obj = { name:'Obj' } // 1、调用函数 fn.call(); //fn && window // 2、改变this指向 fn.call(Obj) // fn && Obj // 3、还可以传参数
继承
-
使用call 方法改变子类的this 实现属性或属性方法的继承
-
利用子类的原型对象 = 父类的实例 实现父类原型对象上的方法的继承
-
特别注意:原型对象的
constructor是指向构造函数的,第二点虽然实现了继承 但是也会改变子类constructor的指向 导致自己子类的原型方法不能使用,所以我们必须手动让子类的原型对象指向子类的构造函数(Father.prototype.constructor = Father)function Person(name,age) { this.name = name; this.age = age Person.prototype.money=function(){ console.log('我是人类的钱'); } } let per = new Person('李四',18); console.log(per); function Father(identity) { this.identity = true Person.call(this,'张三',13); Father.prototype.say = function(){ console.log('say'); } Father.prototype.constructor = Father } Father.prototype = new Person(); // console.log(Person.prototype.constructor); // console.log(Father.prototype.constructor); let fa = new Father(); console.log(fa); fa.money(); // '我是人类的钱' fa.say(); //'say'``