面向对象的概念,创建,实例,call与apply,继承(js)

1、面向对象的概念

面向对象基本概念:

:类是对象的类型模板,例如,定义Student类来表示学生,类本身是一种类型,Student表示学生类型,但不表示任何具体的某个学生

实例:实例是根据类创建的对象,例如,根据Student类可以创建出小明、小花等多个实例,每个实例表示一个具体的学生,他们全都属于Student类型。

面向对象编程,就是编写类(模具),让类产生实例(月饼)

 

对象格式:无序的名值对

{
    key1: value1,
    key2: value2,
    ...
}

 

 

对象组成:

  • 属性:对象的特征描述,静态,名词(不是函数的就是属性)

  • 方法:对象的行为,动态(就是函数)

var obj = {
    name: '小邓',
    age: 3,
    fn: function () {
        console.log(this);
        console.log(this.name);
    }
};
​
// ------------------------------------
//
console.log(obj.name);
console.log(obj['name']); // 中括号比点的形式更强大,它还可以接收变量
​
obj.fn(); // 函数这样调用,fn中的this就是obj
// --------------
var v = obj.fn;
v(); // window  函数中的this不是在定义的时候确定的,而是在调用的时候确定的
//
obj.name = '隔壁老王'; // 如果原来已有这个属性,就会覆盖这个属性
obj.sex = '男'; // 如果原来没有这个属性,则会增加这个属性
console.log(obj);

var obj = {
    name: '小王',
    age: 3,
    aa: null,
    bb: undefined,
    fn: function () {
        console.log(this.name);
    }
};
​
// 当我们试图访问一个不存在的属性时,会返回undefined。
console.log(obj.sex); // undefined
// 我们也可以使用in操作符来判断属性是否存在。注意,只是判断是否存在,不是问值是多少
console.log('sex' in obj); // false
console.log('age' in obj); // true
console.log('aa' in obj); // true
console.log('bb' in obj); // true
// 我们也可以通过delete操作来删除某个属性。
delete obj.aa;
console.log(obj);

 

 

遍历:

  • for-in

for (var attr in obj) {
    console.log(attr, obj[attr]);
}

 

2、面向对象的创建

1、字面量创建

var obj1 = {
    name: '小美',
    age: 3,
    fn: function () {
        console.log('妹妹');
    }
}

 

这种创建方式,适用于单个对象的创建,如果要创建多个对象,会代码冗余。

2、实例创建

var obj = new Object();
obj.name = '小张';
obj.age = 3;
obj.fn = function () {
    console.log(this.name);
}
console.log(obj);
​
// 调用方式1
obj.fn();
​
// 调用方式2
var v = obj.fn;
v();
 

如果要创建多个对象,会代码冗余。

3、工厂模式创建

// 工厂模式,本质上是封装函数
function person(name, age) {
    // 1、准备原料
    var obj = new Object();
​
    // 2、加工:给对象添加各种属性和方法
    obj.name = name;
    obj.age = age;
    obj.fn = function () {
        console.log('前端开发');
    }
​
    // 3、出厂
    return obj;
}
var p1 = person('小邓', 3);
console.log(p1);
​
var p2 = person('小马', 1);
console.log(p2);

 

instanceof

// 实例 instanceof 类;如果这个实例是由这个类创建的,则返回true
console.log([] instanceof Array); // true
console.log([] instanceof Object); // true
// 工厂模式的不足,不能确定实例是由谁创建的
console.log(p1 instanceof person); // false
console.log(p1 instanceof Object); // true

 

4、构造函数创建对象

构造函数的特点:

  • 构造函数名首字母大写(为了区分普通函数,不是必须,是约定)

  • 构造函数方法没有显示的创建对象(new Object())

  • 直接将属性和方法赋值给this对象

  • 没有return语句,不需要返回对象

  • 通过构造函数创建对象,必须使用new运算符(直接调用跟普通函数一样)

function Person(name, age) {
    this.name = name;
    this.age = age;
    this.fn = function () {
        console.log('前端开发');
    }
}

// 用new Person()去调用时,会经历以下四个步骤
// (1) 创建一个新对象;
// (2) 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象);
// (3) 执行构造函数中的代码(为这个新对象添加属性);
// (4) 返回新对象。

var p1 = new Person('小邓', 3);
console.log(p1);

var p2 = new Person('小马', 1);
console.log(p2);

// 构造函数解决了识别问题  instanceof
console.log(p1 instanceof Person); // true
console.log(p1 instanceof Object); // true

// ------------------------------
// 构造函数的问题:同一个函数,会创建多次,占内存
alert(p1.fn);
alert(p2.fn);
alert(p1.fn == p2.fn); // false 引用类型比较的是地址,它的地址不一样

 

5、原型创建对象

原型的概念:

// 每个对象都会有自己的原型对象,是这类对象共有的方法和属性的集合。如Array对象的原型对象中定义了很多的方法和属性,所有的数组都能使用这些方法和属性。
var arr = []; // 实例
console.log(arr.__proto__); // 数组的原型

console.log(Array.prototype); // 数组的原型
console.log(arr.__proto__ == Array.prototype); // true

 

原型创建对象

function Person() { }

// 每创建一个函数,这个函数就有一个prototype属性,这个属性就是原型对象
// 这个原型对象是js默认给我们加的,原型对象有一个constructor属性,它又指向构造函数
// console.log(Person.prototype); // {constructor: prototype}
// console.log(Person.prototype.constructor); // Person 

// 给原型添加属性
Person.prototype.name = '小张';
Person.prototype.age = 3;
Person.prototype.fn = function () {
    console.log('前端开发');
}

// 创建对象
var p1 = new Person();
console.log(p1);
console.log(p1.__proto__);
console.log(p1.name); // 小张
console.log(p1.age); // 3
console.log(p1.fn);

var p2 = new Person();
console.log(p2.name);
console.log(p2.age);
console.log(p2.fn);

// 原型创建对象,解决了构造函数创建对象时重复创建方法的不足
console.log(p1.fn == p2.fn); // true

// -----------------
// 原型创建对象的不足:不能传参

 

  • 原型:js每声明一个function,都有prototype原型,prototype原型是函数的一个默认属性,在函数的创建过程中由js编译器自动添加。也就是说:当生产一个function对象的时候,就有一个原型prototype。原型中存储对象共享的属性和方法。

  • 原型链: 当你定义一个函数对象的时候,其内部就有这样一个链表关系。声明p1对象,自带了proto的属性,而这个属性指向了prototype,从而实现对象的扩展(例如继承等操作)

6、混合模式创建 (构造+原型)

是创建对象的标准模式

// 混合模式创建 (构造+原型)   (标准模式)
// 公共的写在原型上面,只有自己独有的写在构造函数里面
function Person(name, age) {
    this.name = name;
    this.age = age;
}
Person.prototype.fn = function () {
    console.log('前端开发');
}

var p1 = new Person('小王', 3);
console.log(p1);

var p2 = new Person('小李', 2);
console.log(p2);

 

7、动态混合模式

只是同混合模式的写法稍稍有点区别

function Person(name, age) {
    this.name = name;
    this.age = age;
    if (typeof (Person.prototype.fn) !== 'function') {
        Person.prototype.fn = function () {
            console.log('前端开发');
        }
        Person.prototype.showName = function () {
            console.log(this.name);
        }
    }
}

var p1 = new Person('小王', 3);
console.log(p1);

var p2 = new Person('小李', 2);
console.log(p2);

 

3、面向对象的实例

1、面向对象创建飞机

function Plane(parent, src, x, y) {
    this.parent = parent; // 父级,即图片放的盒子
    this.imgNode = null; // 图片
    this.src = src; // 图片的地址
    this.x = x; // x位置
    this.y = y; // y位置
}
Plane.prototype.create = function () {
    this.imgNode = document.createElement('img');
    this.imgNode.src = this.src;
    this.imgNode.style.left = this.x + 'px';
    this.imgNode.style.top = this.y + 'px';
    this.parent.appendChild(this.imgNode);
}
Plane.prototype.del = function () {
    this.imgNode.onclick = function () {
        this.parentNode.removeChild(this);
    }
}

var box = document.getElementById('box');
setInterval(function () {
    var p1 = new Plane(box, 'img/plane.gif', getRandom(0, 434), getRandom(0, 520));
    p1.create();
    p1.del();
}, 1000);

function getRandom(min, max) {
    return Math.floor(Math.random() * (max - min + 1) + min);
}

 

2、面向对象的选项卡

原则:先写出普通的写法,然后改成面向对象写法

1、普通方法变型

尽量不要出现函数嵌套函数

可以有全局变量

把onload中不是赋值的语句放到单独函数中(init)

2、改成面向对象()

先写构造函数

onload中创建对象,并init调用

全局变量就是属性

函数就是方法

(属性和方法前面,都要加this)

改this指向问题(尽量让this指向对象)

 

注意:this指向是这里的重点

演示时先把所有的this指向改了,然后再看报错,一步一步改。要一步一步分析this都指向谁了。

在事件或者定时器调用时,this指向特别容易出错。一定要注意(复习this的指向)

另外,还要注意event对象,它只能出现在事件函数里面。

阻止默认事件,也要出现在事件函数中。

 

结论:我们改成面向对象了之后,感觉是不是更复杂了?确实是这样,确实是更复杂了,但是我们这个面向对象特别适合复杂的开发,对于简单的,不太推荐使用面向对象。面对复杂开发时,它特别容易扩展,同时,复用性特别强。上面的例子,多添加几个,就可以发现特别方便复用和扩展。

 

4、命名空间

// 使用场景:需要全局变量,但是你又不能直接声明全局变量。就用为你分配的命名空间来使用

var U = {}; // 全网站唯一的一个全局变量

// 开发张三
U.zs = {};
U.zs.a = 5;
U.zs.fn = function () {
    console.log('前端开发');
}

// 开发李四
U.ls = {};
U.ls.a = 10;
U.ls.fn = function () {
    console.log('约不');
}

console.log(U.zs.a); // 5
console.log(U.ls.a); // 10

 

5、call、apply

  • 函数.call(新的this指向, 参数1, 参数2, ...)

  • 函数.apply(新的this指向, [参数1, 参数2, ...])

作用:调用函数,改变函数中的this指向

区别:后续的参数传入不同,call的参数是一个一个传入的,而apply的参数是以一个数组的形式传入的

function fn(a, b) {
    console.log(a, b);
    console.log(this);
}

var obj = {
    name: '张三',
    age: 3
};

// 普通调用
fn(3, 5); // window

// call调用
fn.call(obj, 10, 20);

// apply调用
fn.apply(obj, [4, 5]);

// -------------------------------
// 求数组中的最大值
var arr = [32, 4, 2, 433, 2, 3, 3, 5, 4, 44];

console.log(Math.max(3, 5, 2, 67, 34));
console.log(Math.max.apply(Math, arr));

 

6、面向对象的继承

1、原型链继承

// 父类
function Box() {
    this.name = 'Lee';
    this.arr = [1, 2, 3];
}

// 子类
function Desk() {
    this.age = 10;
}

// 如何用一句话实现继承
Desk.prototype = new Box(); // 原型链继承:将父的实例赋给子的原型

var d1 = new Desk();
console.log(d1);
console.log(d1.age);
console.log(d1.name);
console.log(d1.arr);
d1.arr.push(4);

// 问题:如果继承了引用类型,会一改全改,一个实例对象改变了引用类型的数据,后面再创建出来的实例对象就已经是改过之后的数据。

var d2 = new Desk();
console.log(d2.arr);
d2.arr.push(5);

console.log(d1.arr);

 

问题:如果继承了引用类型,会一改全改,一个实例对象改变了引用类型的数据,后面再创建出来的实例对象就已经是改过之后的数据。

 

2、对象冒充继承

// 父类
function Box() {
    this.name = 'Lee';
    this.arr = [1, 2, 3];
}
Box.prototype.showName = function () {
    console.log(this.name);
}

// 子类
// 对象冒充继承:在子的构造函数中,调用父的构造函数,并用call改this指向
function Desk() {
    Box.call(this);
    this.age = 10;
}

var d1 = new Desk();
console.log(d1);
console.log(d1.name);
console.log(d1.age);
d1.arr.push(4);

var d2 = new Desk();
console.log(d2.arr);
console.log(d2.showName); // 不足,不能继承父类原型上的东西

 

不足,不能继承父类原型上的东西

 

3、组合继承

// 组合继承:对象冒充继承 + 原型链继承
// 父类
function Box() {
    this.name = 'Lee';
    this.arr = [1, 2, 3];
}
Box.prototype.showName = function () {
    console.log(this.name);
}

// 子类
function Desk() {
    Box.call(this); // 对象冒充继承
    this.age = 10;
}
Desk.prototype = new Box(); // 原型链继承

var d1 = new Desk();
console.log(d1);

console.log(d1.name);
d1.arr.push(4);
d1.showName();

// -------------------
// 问题:
// 1、同样的属性,在原型链上出现了两次
// 2、会两次调用父的构造函数

 

问题: 1、同样的属性,在原型链上出现了两次 2、会两次调用父的构造函数

 

4、寄生组合继承

完美:继承的标准模式

// 父类
function Box() {
    this.name = 'Lee';
    this.arr = [1, 2, 3];
}
Box.prototype.showName = function () {
    console.log(this.name);
}

// 子类
function Desk() {
    Box.call(this); // 对象冒充继承
    this.age = 10;
}
inherits(Desk, Box);
Desk.prototype.showAge = function () {
    console.log(this.age);
}
// var F = function () { };
// F.prototype = Box.prototype;
// Desk.prototype = new F();
// Desk.prototype.constructor = Desk;

var d1 = new Desk();
console.log(d1);

// 寄生组合继承:(继承的标准模式)
// 1、利用对象冒充继承父的属性
// 2、原型链继承要修改一下,利用空函数转一下(4句话,一般封装成一个方法)
function inherits(Child, Parent) {
    var F = function () { };
    F.prototype = Parent.prototype;
    Child.prototype = new F();
    Child.prototype.constructor = Child;
}

 

 

 



posted @ 2020-09-20 22:43  正经的流刺源  阅读(186)  评论(0编辑  收藏  举报