面向对象

​ 将所有的物体抽象为一个类,每一个具体的个体是类的实例化对象。构造函数是创建类的方法,可以无限制创建类,相当于工厂。

​ 数据类型中的类是一种数据类型,而面向对象是一种编程思想,更高级的还有函数式编程。面向对象里面也有属性和方法。有构造函数创建。为了避免资源的浪费,再构造函数里面创建实例化对象的属性,再构造函数的原型上创建对象的方法。

  • ​ 模板: ——> 构造函数是用来生成对象的,
  • ​ php python 是一种面向对象的编程语言 -> 以类为创建对象的模板
  • ​ JavaScript 是基于对象的编程语言 -> 以构造函数为模板 ->动态语言:没有定义数据类型,需要时才会去处理
1.构造函数

创建构造函数

  • 不能使用箭头函数
  • 首字符大写的函数
  • 建议不使用函数表达式,如果以函数表达式创建箭头函数,后面进行继承操作的时候,由于需要给子的构造函数还原,若没有名字,则还原之后他的constructor 不具名。
1
2
const Animal = function(){}
const Cat = function(){}
  • 在构造函数中,一般只写实例化对象的属性,将实例化对象的方法写在构造函数的原型上,形如

  • function Animal(name,sex){
        this.name = name;
        this.sex = sex;
    }
    
    Animal.prototype.play = function(){
        console.log('play');
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16



    new 关键字

    - ​ 原理:

    - 创建一个空对象
    - 空的原型
    - this 的指向变为空对象

    - ```js
    const cat = new Cat('实际参数')
    //若不需要传递参数,则后面的()可以省略
    //形如
    const cat = new Car
    在构造函数中使用this 关键字,this 指向未来即将生成的实例化对象
    1
    2
    3
    4
    //为了保证实例化对象的时候,使用new 关键字,可以在构造函数内部使用,严格模式
    //如果在实例化对象的时候,没有使用new 关键字系统就会报错
    'use strict' ....
    若实例化时没有加new,实例化对象为未定义,而对象的属性直接报错
    1
    2
    3
    4
    5
    6
    7
    8
    function Car(){
    this.name = 'nem';
    this.sex = 'cute';
    }

    const car =Car();
    console.log(car);
    // underfinded
  • 构造函数返回值

    • 构造函数的返回值如果时基础数据类型,对实例化对象没有影响。
    • 如果构造函数的返回值是引用数据类型,则会覆盖掉构造函数内部的属性。
      • 故构造函数不能有返回值

new.target命令

函数内部可以使用new.target属性。如果当前函数是new命令调用,new.target指向当前函数,否则为undefined

1
2
3
4
5
function f() {
console.log(new.target === f);
}
f() // false
new f() // true

使用这个属性,可以判断函数调用的时候,是否使用new命令。

1
2
3
4
5
6
7
function f() {
if (!new.target) {
throw new Error('请使用 new 命令调用!');
}
// ...
}
f() // Uncaught Error: 请使用 new 命令调用!

上面代码中,构造函数f调用时,没有使用new命令,就抛出一个错误。

Object.create( ) 创建实例对象

通过 Object.create 创建的对象原型为空。

1
2
//用于继承
Fn1.prototype = Oject.create( Fn2.prototype )
  • 如果需要一个构造函数的内容的部分包含另一个构造函数的内容,并且又有自己的

    属性,该怎么写

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function Animal(name,sex){
    this.name = name;
    this.sex = sex;
    }

    function Cat(name,sex,scorlls){
    Animal.call(this,name,sex);
    this.scorlls = scorlls;
    }

    //此处的call也可替换为apply,但后面参数要写成数组的形式
    Animal.apply( this,[name,sex])
2.原型和原型链

一、prototype

在JavaScript中,每个函数都有一个prototype属性,这个属性指向函数的原型对象。并且这个属性是一个对象数据类型的值.

除了underfinded 和 null 以外,所有的数据类型的原型都是Oject

Person ------ prototype --- Perosn.prototype

(构造函数) ---> (实现原理)

二、proto

这是每个对象(除null外)都会有的属性,叫做__proto__,这个属性会指向该对象的原型。

三、constructor

每个原型都有一个constructor属性,指向该关联的构造函数。

1
2
3
4
5
6
7
8
function Person() {

}
console.log(Person===Person.prototype.constructor) //true

//现在基本已不被使用
obj.__proto__ = obj.construcror.prototype
被右边的写法替代

image-20211222192440398

四、实例与原型

当读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层为止。

五、原型的原型

在前面,我们已经讲了原型也是一个对象,既然是对象,我们就可以用最原始的方式创建它,那就是:

1
2
3
var obj = new Object();
obj.name = 'Kevin'
console.log(obj.name) // Kevin

其实原型对象就是通过 Object 构造函数生成的,结合之前所讲,实例的 proto 指向构造函数的 prototype ,所以我们再更新下关系图:

1
2
//原型的最上面是object
//object 的上面是 null

六、原型链

简单的回顾一下构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。那么假如我们让原型对象等于另一个类型的实例,结果会怎样?显然,此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立。如此层层递进,就构成了实例与原型的链条。这就是所谓的原型链的基本概念。——摘自《javascript高级程序设计》

1
//而原型链中就是实例对象和原型对象之间的链接。每个函数都有一个prototype属性,这个prototype属性就是我们的原型对象,我们拿这个函数通过new构造函数创建出来的实例对象,这个实例对象自己会有一个指针(_proto_)指向他的构造函数的原型对象!这样构造函数和实例对象之间就通过( _proto_ )连接在一起形成了一条链子。

在JavaScript中 实例化对象与原型之间的链接,叫做原型链,其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法,然后层层递进,就构成了实例与原型的链条,这就是所谓的原型链

1).为什么要使用原型链呢?

1.为了实现继承,简化代码,实现代码重用!
2.只要是这个链条上的内容,都可以被访问和使用到!

2).使用原型链有什么作用?

继承
prototype用来实现基于原型的继承与属性的共享
避免了代码冗余,公用的属性和方法,可以放到原型对象中,这样,通过该构造函数实例化的所有对象都可以使用该对象的构造函数中的属性和方法!
减少了内存占用

3).原型链的特点

就近原则,当我们要使用一个值时,程序会优先查找离自己最近的,也就是本身有没有,如果自己没有,他就会沿着原型链向上查找,如果还没有找到,它还会沿着原型链继续向上查找,找到到达Object
引用类型,当我们使用或者修改原型链上的值时,其实使用的是同一个值!
JS中每个函数都存在原型对象属性prototype。并且所有函数的默认原型都是Object的实例。
每个继承父函数的实例对象都包含一个内部属性_proto_。该属性包含一个指针,指向父函数的prototype。若父函数的原型对象的_proto_属性为再上一层函数。在此过程中就形成了原型链。

4).ES5继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//Es5 继承时,函数尽量不使用函数声明语法
function Animal(name,sex){
this.name = name;
this.sex = sex;
}

Animal.prototype.play = function(){
console.log('play');
}

function Cat(name,sex,scorlls){
Animal.call(this,name,sex);
this.scorlls = scorlls;
}

Cat.prototype = Object.create(Animal.prototype);
// Cat.prototype.constructor = Cat;
Cat.prototype.constructor = Cat;

const cat = new Cat('cat','like','ball');
const ani = new Animal('any','dislike')


// Cat.prototype.constructor = Cat;
cat.play();
// Animal.play()
ani.play()
console.log(cat,ani.constructor.prototype)

//如果子对象存在自己的方法,则应该写在
Cat.prototype = Object.create(Animal.prototype)
//写在前面的话一旦赋值给空白对象,方法就会丢失
5).ES6继承(语法糖)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Animal{
constructor(name,sex){
this.name = name;
this.sex = sex;
}
eat(){
console.log('吃东西');
}
}

class cat extends Animal {
constructor(pz,name,sex){
super(name,sex);
this.pz = pz;
}
miao(){
console.log('喵...');
}
}

constructor(pz,name,sex){
super(name,sex);
// 里面的参数不需要对应,可以是任意顺序

能被继承

extends的主要用于子类继承父类,继承之后子类拥有父类的的所有方法包括,静态方法和属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class A {
// 静态属性
static info = '见过你的美,还能爱上谁?';
// 静态方法
static love() {
console.log('小姐姐,看见你就犯困,为情所困,为你所困!');
}
// 普通方法,调用静态属性
say() {
console.log('小姐姐,' + A.info);
}
}
class B extends A {
run() {
console.log('类B的方法..');
}
}
//使用类名 父类中普通方法调用
B.love();
6).原型链覆盖
1
2
fn.prototype = { ...}
//这样写原来对象上的方法和属性都被覆盖掉
3.proto,prototype 和constructor之间的关系
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Animal{
constructor(name,sex){
this.name = name;
this.sex = sex;
}
eat(){
console.log('吃东西');
}
}
Animal.prototype.say = function (){
console,log('this is a animal')
}

const cat = new Animal

//1.在这里Animal 就是一个构建工程,相当于construcor
//2.new 关键字声明的cat,就是构造函数的实例化对象
//3.say 就是定义在构造函数上的原型上的方法,通过Animal这个构造工厂构造出来的实例化对象都具有say这个方法

//关系
cat.constructor.prototype = cat.proto__ = Object.getPrototypeOf(cat)

//所有拿到实例化对象的原型有三种方法
//1.cat.__proto__ 不推荐使用
//2.cat.constructor.prototype 不推荐
//3.Object.getPrototypeOf(cat) 官方推荐
4.call bind apply
1
2
3
4
//第一个参数 都是this 的指向,后面的参数则都是传递的参数
// call 和 bind 传递参数都是: a,b,c...的形式
//而apply后面传递的参数应该为数组形式,就算是单个数据也要写成数组的形式 :['a']
//bind和call存在的区别在于,call和apply都会立即执行函数,而bind则不会。因此可以用bind写事件处理函数
1
2
3
4
5
6
7
8
9
// Array.prototype ---》数组的实例化对象的原型 ---》this 指向数组的实例化对象
function fn1(a,b,c) {
// arguments foreach
// es5中非常重要的写法
Array.prototype.forEach.call(arguments,function(item) {
console.log(item);
})
}
fn1('哈哈哈', '嘿嘿嘿', '呵呵呵');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const btn = document.querySelector('#btn');
// call apply bind 改变this指向
// call(this,值1,值2....) apply(this, [值1, 值2...]) bind(this, 值1, 值2....)
// 从表面上看bind方法和call方法完全一致,但是
// bind方法可以再调用之后不执行函数,而call和apply只要调用就立刻执行
const obj = {
a: 100,
}
function fn1(user) {
console.log('你好', user);
console.log(this.a); // window.a
}
// 让fn1的this指向obj 使用call方法,只要运行代码,程序就会立刻执行
// fn1.call(obj, '张三');
// fn1.apply(obj, ['张三']);
// 我的需要: fn1作为btn的事件处理函数,点击按钮之后再执行
// fn1需要改变this指向,同时需要向fn1中传值
btn.onclick = fn1.bind(obj, '张三');

image-20211223194857541

1
2
3
4
5
6
7
8
9
10
11
12
function Person(name,age){
//this == obj
this.name = name;
this.age = age;
}
ver person = new Person('deng',100)
var obj = {}
Person.call(obj,'cheng',300)
//test ---> test.call()

//默认函数运行,相当于隐式运行.call( )
//把Person时传入obj,相当于this == obj this.name = name ,,this.age = age
1
2
3
4
5
6
7
function Person(name,age){
//this == obj
this.name = name;
this.age = age;
}
function Student(){
}