文章导航
一、面向过程
就是分析出解决问题所需要的步骤,然后用变量,函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。
经典案例 :
(1) 购物车 (1. 获取元素 2. 加号事件 3.减号事件 4.全选/反选 5.单删和全删 6.合计)
(2) 元素拖拽(1. 获取元素 2. 当鼠标按下,确定按下的位置 3.鼠标在文档移动时,盒子跟随移动 4.鼠标抬起,停止移动)
二、面向对象
思想上提升 , 就是将你的需求抽象成一个对象,然后针对这个对象分析其特征(属性)与行为(方法)–这个对象就称之为类(面向对象)
也就是说 把构成问题的事务(事务:一般是指要做的或所做的事情。在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit) ),分解成各个对象,建立对象的目的是为了描述某个事物在整个解决问题的步骤中的行为 (大量的分工,紧密的协作)
举例说明
把大象放进冰箱中需要几步
(1) 面向过程 (分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现)
1.1 需要的事物 大象(elephant) 冰箱(icebox)
1.2 打开冰箱 open()
1.3 把大象放进冰箱中 put()
1.4 关上冰箱 close()
(2) 面向对象 (把大象放进冰箱)
冰箱的对象 = {
target:"大象", // 猪肉,苹果,脆脆冰
打开:方法
放东西:方法 // 放target
关闭:方法
}
三、面向对象的封装
案例
需求 描述一只猫的名称,颜色,叫声,技能
- 面向过程 分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现
面向过程
var name = "Tom";
var color = "black";
function say() {
console.log("喵喵");
}
function skill() {
console.log("抓老鼠");
}
- 将你的需求抽象成一个对象,然后针对这个对象分析其特征(属性)与行为(方法)–这个对象就称之为类
字面量创建对象 (单个)
var cat = {
name: "Tom",
color: "black",
say: function () {
console.log(this.name, this.color)
},
skill: function () {
console.log("抓老鼠");
}
}
但是以字面量创建的对象存在一个明显的缺点,每次声明只能创建一个,无法实现代码的复用
var cat = {
name: "Kitty",
color: "pink",
say: function () {
console.log(this.name, this.color)
},
skill: function () {
console.log("抓老鼠");
}
}
解决方式 工厂模式 => 封装函数(提取参数 返回对象)
工厂模式 批量创建对象
function cat(name, color) {
var obj = {
name: name,
color: color,
say: function () {
console.log(this.name, this.color)
},
skill: function () {
console.log("抓老鼠");
}
}
return obj;
}
改写形式
function cat(name, color) {
var obj = new Object();
obj.name = name;
obj.color = color;
obj.say = function () {
console.log(this.name, this.color)
}
obj.skill = function () {
console.log("抓老鼠");
}
return obj;
}
var tom = cat("Tom", "black");
console.log(tom);
var Kitty = cat("Kitty", "pink");
console.log(Kitty)
工厂模式 批量创建对象
- 优点 快速得到想要的对象
- 缺点
- (1) 通过工厂模式创建的对象跟函数没有任何关联
- 对比Array 非常明显
- var arr = new Array(); //构造函数
- console.log(arr instanceof Array); // true
(2)公用的属性和方法重复声明 需要占据多余内存
{say,skill} 两个方法重复声明 4次 占内存
解决方式 构造函数
function Cat(name, color) {
this.name = name;
this.color = color;
}
构造函数创建了一个原型对象(prototype) 所有的公用属性和方法 都存储在原型对象上, 每一个通过构造函数创建的对象都可以使用该原型对象的方法
Cat.prototype.species = "猫";
Cat.prototype.skill = function () {
console.log("捉老鼠");
}
Cat.prototype.say = function () {
console.log(this.name, this.color);
}
函数调用 通过构造函数new 创建实例化对象
var Tom = new Cat("Tom", "black");
console.log(Tom);
console.log(Tom.name);
console.log(Tom.color);
Tom.say(); // 函数在调用的时候属于谁 就指向谁 this -> Tom
1. 通过构造函数创建的对象和 构造函数的关联性 Tom instanceof Cat => true;
2. 共有的属性和方法重复声明的问题 => 怎么解决 构造函数创建了一个原型对象(prototype) 所有的公用属性和方法 都存储在原型对象上, 每一个通过构造函数创建的对象都可以使用该原型对象的方法
注意:
prototype是一个指针,指向一个对象。这个对象的用途是,包含所有实例共享的属性和方法。
所有通过同一个构造函数创建的实例对象,都会共享同一个 prototype。
原型诞生的意义就是可以实现代码复用。
JS的new关键词做了什么操作?
new操作符新建了一个空对象,这个对象原型指向构造函数的prototype,执行构造函数后返回这个对象
1、创建一个空的对象
2、链接到原型
3、绑定this指向,执行构造函数
4、确保返回的是对象
以刚刚的方法为例,new共经过了4个阶段
1、创建一个空对象
var obj = new Object();
2、设置原型链(当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象)
obj.__proto__= Cat.prototype;
3、让Cat中的this指向obj,并执行Cat的函数体。(创建新的对象之后,将构造函数的作用域赋给新对象(因此this就指向了这个新对象))
var result = Cat.call(obj);
4、判断构造函数的返回值类型:
如果是值类型,返回obj。如果是引用类型,就返回这个引用类型的对象
默认情况下函数返回值为undefined,即没有显示定义返回值的话,但构造函数例外,new构造函数在没有return的情况下默认返回新创建的对象。
四、原型和原型链
每个继承父函数的子函数的对象都包含一个内部属性proto。该属性包含一个指针,指向父函数的prototype。若父函数的原型对象的proto属性为再上一层函数。在此过程中就形成了原型链 .
注: 平时我们查找一个是实例化对象的属性和方法时 就是顺着原型链查找的.
拓展方法
instanceof 判断的一个对象(实例对象)的原型链上是否包含构造函数的原型对象(prototype)
// Tom -> Cat.prototype -> Object.prototype ->null
Tom instanceof Cat;
isPrototyeOf 判断的是构造函数的原型对象是否存在于实例对象的原型链之上
Cat.prototype.isPrototypeOf(Tom)
in 判断的一个对象(实例对象)的原型链上是否存在某个属性
"name" in Cat
hasOwnProperty 在实例中是否包含该属性,不在原型中查找该属性
Tom.hasOwnProperty('name')
propertyIsEnumerable 方法返回一个布尔值,表示指定的属性是否可枚举(遍历)。
Tom.propertyIsEnumerable("name")
五、ES6 构造函数(类)
JavaScript 语言中,生成实例对象的传统方法是通过构造函数。
function Cat(name, color) {
this.name = name;
this.color = color;
}
构造函数创建了一个原型对象(prototype) 所有的公用属性和方法 都存储在原型对象上, 每一个通过构造函数创建的对象都可以使用该原型对象的方法
Cat.prototype.species = "猫";
Cat.prototype.skill = function () {
console.log("捉老鼠");
}
Cat.prototype.say = function () {
console.log(this.name, this.color);
}
函数调用 通过构造函数new 创建实例化对象
var Tom = new Cat("Tom", "black");
console.log(Tom);
上面这种写法跟传统的面向对象语言(比如 Java)差异很大。
ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class
关键字,可以定义类。
基本上,ES6 的class
可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class
写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已.
// ES6 class
class Cat {
// constructor等同于 原本的构造函数
constructor(name, color) {
this.name = name;
this.color = color;
}
// 写在此位置的方法 就行于写在 Cat.prototype 上
// 函数简写
call() {
console.log("喵喵");
}
skill() {
console.log("捉老鼠");
}
say() {
console.log(this.name,this.age);
}
}
console.dir(Cat);
var tom = new Cat("tom", "black");
console.log(tom);
ES6 此种写法类似于
(方便理解)
Cat.prototype={
constructor(){},
call(){},
skill(){},
say(){}
}
六、面向对象的继承
让 一个类继承(构造函数) 另一个类(构造函数) 的 属性和方法(实例化对象的属性和方法 和 原型对象prototype上的属性和方法) 或者重新定义、追加属性和方法等。
继承可以使得子类具有父类的属性和方法或者重新定义、追加属性和方法等
继承的类 叫做 子类 , 被继承的类 叫做 父类 。
继承主要解决的是代码重复使用的问题。
// 人
function Person(name, age) {
this.name = name;
this.age = age;
this.species = "human";
}
Person.prototype.skill = function () {
consoel.log("think");
}
Person.prototype.say = function () {
consoel.log(this.name, this.age);
}
// 黄种人 function YellowPerson(hobby) {
this.hobby = hobby;
}
YellowPerson.prototype.skin = "yellow";
YellowPerson.prototype.speak = function () {
console.log("chinese");
};
上述代码有两个类 Person 和 YellowPerson , 如何让YellowPerson 继承 Person的name,age,species属性和 skill,say方法?
七、构造函数继承
// 人
function Person(name, age) { // 构造函数中 实例的属性和方法
// this->构造函数Person创建的实例化对象
this.name = name;
this.age = age;
this.species = "human";
// this.__proto__ = Person.prototype
// return this;
}
// 原型对象上的属性和方法
Person.prototype.say = function () {
console.log(this.name, this.age);
}
Person.prototype.skill = function () {
console.log("thinking");
}
// YellowPerson
function YellowPerson(name, age, hobby) {
// this->构造函数YellowPerson创建的实例化对象
// Person(); // 普通函数来调用 this -> window
// 我们的想法是 调用Person()函数 将Person()函数中的this指向 当前YellowPerson创建的实例化对象(this)
// 1. 执行Person()方法
// 2. 在Person()执行过程中 将this 只想为 当前YellowPerson创建的实例化对象(this)
Person.call(this, name, age);
this.Person = Person; // 作为普通函数
this.Person( name, age); //Person函数中的this-> 当前实例化对象
delete this.Person
this.hobby = hobby;
}
var zhangSan = new YellowPerson("张三", 18, "唱");
console.log(zhangSan)
缺点 只能继承 父类实例的属性(name,age,species)和方法 并不能继承 父类原型对象(prototype)上的属性和方法
关于call和apply
call和apply都会调 用函数 , 在函数执行过程中改变this的指向
call(obj,arg1,arg2...argN) 1. 调用原函数
2. 在函数执行过程中,将该函数的this 强制指向call的第一个参数(obj)
3. 如果原函数需要接收参数的话 那么在call()的第二个参数起依次向后排
function fn(a, b) {
console.log(this, a, b); // document 10,20
}
fn.call(document, 10, 20);
apply(obj,[arg1,arg2,...,argN])
1. 调用原函数
2. 在函数执行过程中, 将该函数的this 强制指向apply的第一个参数(对象)
3. 如果原函数需要接收参数的话 那么在apply() 需要将参数先依次放到数组中,在将数组作为第二参数传到apply()中
function fn(a, b) {
console.log(this, a, b);
}
fn.apply(document.body, [10, 20]);
运用 1. 伪数组转真数组
var liList = document.querySelectorAll(".list li");
list = Array.prototype.slice(lilist);
console.log(list);
运用2. 判断一个数据是否是对象(纯对象类型的对象)
var obj = {};
Object.prototype.toString.call(obj); // "[object Object]"
八、原型继承
// 人
function Person(name, age) { // 构造函数中 实例的属性和方法
// var obj = new Object(); this->构造函数Person创建的实例化对象
this.name = name;
this.age = age;
this.species = "human";
// this.__proto__ = Person.prototype
// return this;
}
// 原型对象上的属性和方法
Person.prototype.say = function () {
console.log(this.name, this.age);
}
Person.prototype.skill = function () {
console.log("thinking");
}
// YellowPerson
function YellowPerson(name, age, hobby) {
this.hobby = hobby;
}
YellowPerson.prototype = new Person("张三", 18);
YellowPerson.prototype.sayHello = function () {
console.log("hello");
}
var zhangSan = new YellowPerson("张三", 18, "唱");
console.log(zhangSan)
// 拆分
// 1. new Person("张三", 18);
// 得到一个对象
// {
// name: "张三",
// age: 18,
// species: "human",
// [[Prototype]]: Person.prototype
// }
YellowPerson.prototype = Person.prototype;
// 2. YellowPerson.prototype = new Person("张三", 18);
// YellowPerson.prototype = {
// name: "张三",
// age: 18,
// species: "human",
// [[Prototype]]: Person.prototype
// }
原型继承 通过拓宽原型链实现子类继承父类的原型对象的属性和方法
缺点 只能继承 父类原型对象上的属性和方法 ,并不能继承 父类实例的属性和方法
原型继承优化 中间件继承
// 黄种人 function YellowPerson(name, age, hobby) { // this -> new YellowPerson() 创建的实例化对象
this.hobby = hobby;
}
// 创建一个中间函数
var Fn = function () { } // var obj this->obj return this
Fn.prototype = Person.prototype;
// new Fn(); // {__proto__:Person.prototype}
YellowPerson.prototype = new Fn();
// 方便理解
// YellowPerson.prototype = {
// __proto__: Person.prototype,
// }
九、组合继承
构造函数继承 + 原型继承
// 人
function Person(name, age) { // 构造函数中 实例的属性和方法
// var obj = new Object(); this->构造函数Person创建的实例化对象
this.name = name;
this.age = age;
this.species = "human";
// this.__proto__ = Person.prototype
// return this;
}
// 原型对象上的属性和方法
Person.prototype.say = function () {
console.log(this.name, this.age);
}
Person.prototype.skill = function () {
console.log("thinking");
}
// 黄种人 function YellowPerson(name, age, hobby) { // this -> new YellowPerson() 创建的实例化对象
Person.call(this, name, age); // 构造函数的继承
this.hobby = hobby;
}
// 创建一个中间函数
var Fn = function () { } // var obj this->obj return this
Fn.prototype = Person.prototype;
// new Fn(); // {__proto__:Person.prototype}
YellowPerson.prototype = new Fn();
// 方便理解
// YellowPerson.prototype = {
// __proto__: Person.prototype,
// }
十、ES6的继承
Class 可以通过extends
关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。
//Person
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
this.species = "human";
}
skill() {
consoel.log("think");
}
say() {
console.log(this.name, this.age);
}
}
//YellowPerson
class YellowPerson extends Person {
constructor(name, age, hobby) {
super(name, age); // super 作为一个函数使用代表调用父类构造函数 相当于 ES5 Person.call(this,name,age)
this.hobby = hobby; // 拓展自己的实例属性 / 方法
}
// 拓展自己的YellowPerson 的动态属性和方法
// skin = "yellow";
speak() {
console.log("chinese");
}
// 改写父类
say() {
console.log(this.name, this.age, this.hobby);
}
}
YellowPerson.prototype.skin = "yellow";
super的用法归纳
1. super 作为一个函数使用 代表 调用父类构造函数 (作为函数时,super()只能用在子类的构造函数之中,用在其他地方就会报错。)
2. super 作为一个对象使用
2.1 super作为一个对象 取值的时候 super 等价于 父类的原型对象 (此案例就相当于 Person.prototype)
2.2 super作为一个对象 赋值的时候 super 等价于 this
class YellowPerson extends Person {
constructor(name, age, hobby) {
// 1. super 作为一个函数使用
super(name, age); // 调用父类构造函数 ES5 Person.call(this,name,age)
this.hobby = hobby;
console.log(super.skill);
console.log(super.skill === Person.prototype.skill); //
this.x = "hello";
super.x = 123123;
}
// 拓展自己的动态方法
speak() {
console.log("chinese");
}
// 改写父类
say() {
console.log(this.name, this.age, this.hobby);
}
}
var zhang = new YellowPerson("张三", 18, "跳");
console.log(zhang);
zhang.say();
十一、面向对象之多态
多态 不同类(构造函数)的对象 在调用同一个方法 会显示不同的结果
JS 无态,天生就支持多态
class Cat {
constructor(name, color) {
this.name = name;
this.color = color;
}
call() {
console.log("喵喵");
}
}
class Dog {
constructor(name, color) {
this.name = name;
this.color = color;
}
call() {
console.log("旺旺");
}
}
class Pig {
constructor(name, color) {
this.name = name;
this.color = color;
}
call() {
console.log("哼哼");
}
}
var kitty = new Cat("Kitty", "pink");
var wang = new Dog("旺财", "black");
var pei = new Pig("佩奇", "pink");
function AnimallCall(animal) {
animal.call();
}
AnimallCall(kitty);
AnimallCall(wang);
AnimallCall(pei);
免费试用
更多热门智能应用