面向对象笔记

365 阅读18分钟

1 -- 面向对象

对象

任何事物都可以看作是对象。

如何使用js语言描述对象

字面量形式创建对象

var p = {};
p.name = '中国人';
p.age = '500';

构造函数形式创建对象

function Person(name, age) {
	this.name = name;
	this.age = age;
}
var p = new Person('中国人', 500);
var p2 = new Person('中国人2', 500);

面向对象与面向过程的区别

  • 面向过程
    • 凡是自己亲力亲为,按部就班的解决现有问题。
  • 面向对象
    • 自己充当一个指挥者的角色,指挥更加专业的对象帮我解决问题。

面向过程的优缺点

  • 缺点
    • 代码可读性比较差
    • 可维护性和可扩展性比较差
    • 全局变量污染严重,变量管理混乱
  • 优点
    • 书写快速,因为通常是想到什么写什么,只要能解决当前问题即可
    • 通常来说比面向对象要节省内存

面向对象的优缺点

  • 缺点
    • 比面向过程要消耗一些内存
    • 开发会比过程要慢一些(但也不是绝对的,因为面向对象书写的代码复用性比较强,也就是第一天比较慢,日后可能就比较快了)
  • 优点
    • 代码可读性比较高
    • 可维护性和可扩展性比较高
    • 变量的管理比较清晰

面向对象代码的书写

  • 要根据需求想象我们需要那些对象帮我解决这个需求(比如我要逛街,需要一个导购,需要一个保镖,需要一个女朋友)
  • 编写对象对应的构造函数(比如function Person(){})
  • 抽取该对象所需的属性,编写到构造函数内(比如this.name = name)
  • 抽取该对象所需的方法,编写到构造函数原型中(比如Person.prototype.run = function(){})
  • 把类写完之后,就可以创建实例,解决实际需求了

面向对象的3大特征

  • 封装性
    • js中的对象本质上就是键值对的集合,是一个复杂数据类型,可以包含N多其他数据,这就是js中对象的封装性。
  • 继承性
    • js中可以通过某种方式,让一个对象无条件访问另一个对象的属性与方法,这就是继承性。
    • js中的所有对象,都具有一个原型对象,可以无条件的访问这个原型对象的属性与方法,这就是js中对象的继承性。
  • 多态性
    • 在js中,对象可以随时变化,对象继承的对象也可以被随意改变,这些动态的变化可以理解为是js的多态。

类和实例

如果把类看作是模子,实例可以看作是模子刻出来的东西。

  • 类就是对某一些具有相同特征与特性的抽象的描述
  • 实例相对比类,就是一个实实在在具体的事物。

什么是伪数组?

  1. 伪数组是一个非数组类型对象

  2. 有一个length属性,值为number类型

  3. 存在下标方式存储数据

    var obj = {
    	0:1,
    	1:'2sfd',
    	length:2
    }
    var obj2 = {
    	length:0
    }
    var obj3 = {
    	0:1,
    	1:'2sfd',
    	name:'abc',
    	length:3
    }
    

2 -- 继承

原型

  • 原型本身是一个对象,这个对象的属性与方法可供其他对象。
  • 任何对象都有成为原型的潜质,下面的代码就让obj成为了原型。

谁有原型

  • 默认所有的对象都有原型

谁有prototype

  • 默认所有的函数都有prototype

给对象手动添加prototype可以实现继承吗

  • 没什么乱用,因为对象不能配合new关键字创建实例。

函数的特殊性

  • 函数也是对象的一种,所以也有__proto__
  • 函数可以用来创建实例,又有prototype

如何访问一个对象的原型

  • 通过__proto__属性(但是它是非标准属性,不建议开发中使用)
  • 通过constructor属性得到对象的构造函数,再访问其prototype得到原型

prototype的作用

  • 用来引导构造函数创建的实例默认原型

__proto__的作用

  • 用来继承某个对象

prototype与__proto__联系

  • 通过构造函数创建的实例,实例的__proto__默认为构造函数的prototype,除此之外,没有任何联系。

构造函数创建对象的4个步骤

  • 创建一个新对象(本质上就是开辟了一块内存空间)
  • 设置新对象的原型
    • 本质上就是在这块内存空间中添加了一个__prot__属性
    • __proto__属性值与构造函数的prototype有关
    • 相当于是这样给__proto__赋值的:新对象.proto = 构造函数.prototype
  • 执行构造函数,执行时设置其this为新实例
  • 返回新实例的地址

对象的属性访问规则

优先从自身查找,找不到就去原型找,还找不到继续去原型的原型找, 直到终点,终点也没有返回undefined。

对象的属性赋值

自己没有该属性相当于新增,有则修改,并不会对其原型上的属性造成影响。

继承

  • 在js中,只要一个对象能够使用另一个对象的成员,这种特征就是继承。
  • 在主流的面向对象语言中,继承是类与类之间的关系,在js中继承是对象与对象之间的关系。

继承方式

1、默认的原型继承

function P() {}
P.prototype.fun = function(){};
var p = new P();

2、原型替换

function P() {}
P.prototype = {
	constructor: P,
	fun: function(){}
};
var p = new P();

3、Object.create

var proObj = {
	fun: function(){}
};
var p = Object.create(proObj);

4、原型组合式

function A() {}
function P() {}
P.prototype = Object.create(A.prototype);
P.prototype = new A();
var p = new P();

属性复制

在日常开发中,可能会存在实现多继承的需求,上面的原型组合式就可以完成这个需求。 但是原型组合式如何嵌套过多,对于属性的查找效率是有影响的,而且过长的原型,也不利于维护。 对于实现多继承,还有另外一种解决方案,这种解决方案有一个代表,就是jQuery库中提供的extend方法。

实现属性复制函数封装

function copy() {
	var target = arguments[0];
	for(var i = 1, len = arguments.length; i < len; i++) {
		for(var key in arguments[i]) {
			target[key] = arguments[i][key];
		}
	}
	return target;
}

关于for in遍历的补充

  • 使用for in的方式遍历对象的属性,是无法遍历出js内置属性的。

使用属性copy的方式给原型添加属性的优点

  • 不会覆写构造函数默认的prototype,那么对应的constructor属性就不会丢失
  • 可以替代原型组合式的写法
  • 使用灵活简单

3 -- 原型链

原型的规律

  • 原型链的终点统一是Object.prototype
  • 对象的原型和该对象的类型有关
    • 比如Person的实例,原型是Person.prototype
    • 比如Animal的实例,原型是Animal.prototype
    • []的原型链结构
      • [] ==> Array.prototype ==> Object.prototype ==> null
    • {}的原型链结构
      • {} ==> Object.prototype ==> null
    • /abc/的原型链结构
      • /abc/ ==> RegExp.prototype ==> Object.prototype ==> null
    • Person的原型链结构
      • Person ==> Function.prototype ==> Object.prototype ==> null
    • Function的原型链结构
      • Function ==> Function.prototype ==> Object.prototype ==> null
    • Object的原型链结构
      • Object ==> Function.prototype ==> Object.prototype ==> null
  • 构造函数默认的prototype,它统一都继承Object.prototype
    • 比如Person.prototype,原型是Object.prototype
    • 比如Animal.prototype,原型是Object.prototype
  • 通过这个规则,可以自由猜想出任意一个实例所有的原型
    • 比如Book的实例,其原型结构为: Book实例 ==> Book.protoype ==> Object.prototype ==> null

原型链

  • 一个对象,所有由__proto__联系在一起的原型,称之为这个对象的原型链。

如何研究一个对象的原型链结构

  • 先通过__proto__得到对象的原型
  • 然后访问这个原型的constructor属性,确定该原型的身份
  • 然后继续按照上诉两个步骤,往上研究原型,最终就得到了对象的原型链。

instanceof -- 运算符

  • 作用:判断一个对象的原型链中是否含有某个构造函数的prototype
  • 语法:对象 instanceof 构造函数
  • 返回值:boolean

hasOwnProperty -- 方法

  • 作用:判断一个属性是不是自己的(不包含继承的属性)
  • 语法:对象.hasOwnProperty(属性名)
  • 返回值:boolean

in -- 运算符

  • 作用:判断能否使用某个属性(包含继承的属性)
  • 语法:属性名 in 对象
  • 返回值:boolean

delete -- 运算符

  • 作用:删除对象的属性
  • 语法:delete 对象.属性名 || delete 对象[属性名]
  • 返回值:boolean

Function -- 内置构造函数

  • 作用:创建函数实例
  • 语法:new Function(形参1,形参2,...,代码体)
  • 返回值:新创建的函数实例
  • 特点:能够把字符串当做js脚本执行

eval -- 内置的全局函数

  • 作用:执行字符串代码

4 -- 作用域

作用域

变量的有效范围。

如何检测变量的有效范围

  • 在指定的作用域下访问该变量,如果不报错,就证明这个变量的有效范围覆盖了这个作用域。

全局变量

  • 指的是在代码的任何地方都可以使用的变量

在js中如何定义全局变量

  • 在函数外定义
  • 或者不使用var定义的变量(这种方式不标准,尽量不要使用)

局部变量

  • 在变量定义的局部可以使用的变量

在js中如何定义局部变量

  • 在函数内定义

变量的生命周期

  • 全局变量的生命周期从定义开始,到页面关闭结束
  • 局部变量的生命周期通常是从定义开始(函数被调用),到函数执行完毕结束(但是局部变量的生命周期可能因为闭包的存在被延长)

块级作用域 ==> js未采纳

  • 凡是代码块就可以产生新的作用域,代码块内的变量外界无法使用。

函数作用域 ==> js采纳

  • 只有函数可以产生新的作用域,函数内的变量外界无法使用。
  • js中是没有块级作用域的,只有函数作用域。

词法作用域(静态作用域) ==> js采纳

  • 查找一个变量,优先找函数自己作用域内的变量,找不到就去定义该函数的作用域中去找, 按照这个规则直到全局都没有找到,就报错。

动态作用域 ==> js未采纳

  • 查找一个变量,优先找函数自己作用域内的变量,找不到就去调用该函数的作用域中去找, 按照这个规则直到全局都没有找到,就报错。

有一个容易搞混,又没有什么联系的知识点,这里强调一下

  • 函数内的this,与函数的定义无关,与调用有关。
var obj = {
	fn: function() { console.log(this) };
};
var fn = obj.fn;

// 同一个fn,三种调用方式,this分别不同
obj.fn(); // obj
fn();     // window
new fn(); // fn实例
  • 变量的查找,与函数的定义有关,与调用无关。
function fn() {
	console.log(a); // 报错,自己找不到,去定义fn的全局找,所以这里和fn的定义有关,与fn的调用无关。
}
(function() {
	var a = 10;
	fn();
})();

作用域的产生

  • 函数可以被多次重复调用,调用一次就会产生一个新的作用域,每一个新作用域内会有新的变量。

作用域链

  • 函数在定义的时候,将来它执行时的上级作用域就被确定好了,上级作用域可能还有上级,函数所有的上级作用域称之为作用域链。
  • 一个函数作用域可以访问的所有上级作用域,称为它的作用域链。

5 -- 预解析&闭包

预解析

  • 可以理解为js解析引擎在逐行执行代码前,对一些特殊代码的预先执行。
  • 也可以认识是在马拉松之前的热身运动。
  • 具体一点讲,是js在逐行执行代码前,会对js脚本进行一个整体检查。
    • 1、检测语法有没有错误
    • 2、变量声明提升:检测到变量声明那就率先进行声明(实际上是开辟一个内存空间,将来备用)
    • 3、函数声明提升:检测到函数声明也率先进行声明(实际上是开辟两个内存空间,一个是变量,一个是函数)
  • 预解析造成js一个特殊的象限,就是在变量声明和函数声明之前的代码,可以访问它们。
  • js预解析完毕之后,才会整体正式逐行执行,但是预解析过的变量声明和函数声明不会重复执行。
  • js预解析分为两种,全局预解析(全局代码执行的时候会先预解析)与局部预解析(函数在调用的时候内部的代码会先预解析)

变量声明

  • 使用通过var定义的变量,才属于变量声明
    • 例如:var a; 属于变量声明。
    • 例如:b = 10; 不属于变量声明。
  • var关键字可以通过逗号连续声明多个变量
    • 例如:var a, b, c = 20, d = 30;
    • a,b,c,d全部属于声明。
  • var关键字在声明变量的时候,可以给其赋值,如果赋值表达式中含有一些变量,这些变量不属于变量声明。
    • 例如:var a = b = 10;
    • 其中a属于变量声明,b不属于。

函数的定义方式

  • 字面量
    • 函数声明
      • function fn(){}
    • 函数表达式
      • var fn = function(){}
  • 构造函数
    • new Function()

函数声明

在js中,函数声明式写法比较单一,好区分。

  • 一定是以function关键字开头定义的函数
  • 一定具有函数名
  • 函数声明有两种,1种是全局函数声明,1中是局部函数声明
    • 函数声明要么在全局,要么直接嵌套在另一个函数内

函数表达式

在js中,函数表达式的编写形式,多种多样。 比如把函数当作数据赋值给变量,或者把函数作为返回值return,或者当做参数传递,或者运算符运算,或者自调函数。

  • 要么不是以function关键字开头来定义的函数,要么该函数定义在了语句当中
  • 函数名可有可无

预解析细节规则

  • 变量声明重名 -- 后面的忽略,没有必要定义重复的变量
  • 函数声明重名 -- 保留后面的,因为函数体可能不一样,后面的优先与前面的
  • 变量与函数重名 -- 保留函数
  • 写在代码块中的函数,名字会被预解析,函数体不会
    • 最终造成的现象是,在该函数定义的访问它,不会报错,得到一个undefined
console.log(fn)  // undefined
if(true) {
	function fn(){}
}
console.log(fn)  // 函数体
  • 函数表达式不会被预解析,但是函数表达式定义的函数执行时,其内部会对自己进行函数声明。
    • 最终造成的现象是,在该函数的外面无法通过其名称找到它,但是在内部可以。
console.log(fn);  // 报错
var a = function fn(){
	console.log(fn);  // 函数体,因为表达式定义的函数,会在自己内部被声明一次。
}
console.log(fn);  // 报错

函数执行时形参的赋值

  • 一个函数在执行时,会优先定义形参,然后赋值。
  • 预解析和逐行执行都慢与形参。
(function(a) {
	console.log(a);  // 100
	var a = 200;
	console.log(a);  // 200
}(100));

闭包

  • 有权访问非自身局部变量(非全局变量)的函数,称为闭包。
  • 有权访问自由变量(非全局变量)的函数,称为闭包。

自由变量

  • 一个函数可以访问的非自身内部变量,称为这个函数的自由变量。

引用了自由变量的闭包特点

  • 会延长自由变量的生命周期,只要闭包不死我就不死

闭包的应用

  • 可以利用闭包的结构去管理一些重要的变量,防止外界随意对其修改

6 -- 函数的四种调用模式

this的特点

  • 函数中的this,调用方式不同,指向不同
  • this与调用有关,与定义无关

函数调用模式

  • 函数名() || (function(){}()) ==> window

方法调用模式

  • 对象.方法名() || 对象方法名 || 祖对象.父对象.子对象.方法名() ==> 宿主对象

构造器调用模式

  • new 构造函数() || new 对象.构造函数() ==> new出来的新实例

间接调用模式(上下文调用模式)

  • call
    • 函数.call(指定的this,实参1,实参2,...)
    • 对象.方法.call(指定的this,实参1,实参2,...)
  • apply
    • 函数.apply(指定的this,[实参1,实参2,...])
    • 函数.apply(指定的this,{0: 实参1, 1:实参2, length: 2})
    • 对象.方法.apply(指定的this,[实参1,实参2,...])

call和apply的使用范例

// 方法借用 -- 给伪数组对象添加属性值
var obj = {};
Array.protype.push.call(obj, '要添加的第一个值', '要添加的第二个值')
// 方法借用 -- 获取对象类型
var arr = [];
Object.prototype.toString.call(new Date).slice(8, -1)
// 方法借用 -- 借用父类构造函数给子类实例添加属性
function Parent(name, age) {
	this.name = name;
	this.age = age;
}
function Son() {
	Parent.apply(this, arguments);
}
var p = new Son('火星人', 999);
// apply拆分数组或伪数组值依次传递给函数
var arr = [1, 10, 20, 40];
Math.max.apply(null, arr)

7 -- ES5新特性&设计模式&js高级补充

ES5数组新增的3个方法

forEach

  • 作用:帮我们遍历数组,每遍历到一个值,就会调用一次回调,把这个值与它的下标传递过去
  • 语法:数组.forEach(function(v, i){ console.log('使用forEach帮我们遍历好的值与下标') })
  • 返回值:undefined

map

  • 作用:可以用来代替forEach,但是map可以接收回调的返回值,最终通过一组数据映射为回调返回的另外一组数据
  • 语法:var mapArr = 数组.map(function(v, i){ return v * v })
  • 返回值:回调所有的返回值组成的新数组

filter

  • 作用:可以用来代替forEach,但是还可以过滤数组中的值
  • 语法:var filterArr = 数组.filter(function(v, i){ if(v % 2 ==0){ return true; } })
  • 返回值:所有返回回调返回true的对应值组成的新数组

call&apply的补充

  • 如果不传参 ==> this指向window
  • 传null ==> this指向window
  • 传undefined ==> this指向window
  • 传123 ==> this指向123的包装类型对象(Number对象)
  • 传'abc' ==> this指向'abc'的包装类型对象(String对象)
  • 传true ==> this指向true的包装类型对象(Boolean对象)
  • 传对象 ==> this指向传入的对象

构造函数的返回值

  • 如果构造函数没有return语句,那么new它,得到一个新实例
  • 如果构造函数return了一些基本类型数据,那么new它,得到一个新实例
  • 如果构造函数return了一个对象,那么new它,得到return的对象

严格模式

  • ES5新增的一个特性,使用该特性可以让js以一种新的模式运行js脚本。
  • 该模式下可以强制我们抛弃那些不推荐不友好的写法
  • 该模式下可以让js之前的一些设计不太合理的api表现的合理一些
  • 该模式下可以让js拥有一些新的特性,比如ES6/ES7规范中定义的某些语法,必须在严格模式下才有效

严格模式的分类

  • 全局模式
    • 在全局代码的最上面书写一句话'use strict';
    • 使用该模式,所有的代码都按照严格模式执行
  • 局部模式
    • 在函数内部的最上面书写一句话'use strict';
    • 使用该模式,只有该函数内的代码才会按照严格模式执行

需要记住的几条严格模式规则

  • 定义变量必须使用var
  • 函数调用模式this为undefined
  • 真正实现了call谁this就为谁
  • eval拥有了单独的作用域

设计模式

沙箱模式

  • 使用某种方式,防止一些代码对外界环境造成潜在影响,这类代码就可以认为是沙箱模式
  • 在js中,最简单的沙箱模式写法,就是使用一个自调函数把某块代码进行封装,可以防止全局变量污染

工厂模式

  • 凡是调用一个函数,函数返回一个对象,那么这个函数就可以认为是一个工厂。
  • 在js中,一般的工厂都是把new对象的过程进行了一个封装。

单例模式

  • 只要让某种类型的实例,只能存在一个,实现这种需求的代码就是单例模式
  • 在js中,JSON与Math对象就被设计为单例模式,我们不能够创建第二个这种类型的对象。

观察者模式 -- 发布订阅模式

  • 只要某事件发生后,会自动执行预设好的回调,那么实现这种需求的代码就是观察者模式
  • 观察者模式可以认为就是自定义事件
  • 观察者模式通常使用的场景是这样的:某对象做了某件事,其他对象做出相应的一个响应

bind

  • ES5提供了一个新的可以改变函数this指向的新函数
  • 作用:通过某函数得到一个绑定了固定this的新函数,这个新函数可以是旧函数的clone版本
  • 语法:var bindFn = 函数.bind(this)
  • 返回值:绑定了this的函数

类成员&实例成员

  • 类成员
    • 添加给类自己的属性与方法
  • 实例成员
    • 添加给实例自己的属性与方法
    • 原型上供实例使用的属性与方法

免费领取nodejs全套视频,nodejs websocket ,MySQL基础班全套视频,phthon视频,几十本编程电子书籍,PHP入门基础视频,Java入门基础视频等!(公众号回复 '奇点的世界')