面向对象基本概念:
类:类是对象的类型模板,例如,定义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; }