1.OOP
面向对象编程(Object Oriented Programming,缩写为OOP)是目前主流的编程范式。它的核心思想是将真实世界中各种复杂的关系,抽象为一个个对象,然后由对象之间的分工与合作,完成对真实世界的模拟
面向对象编程的基本特点是:封装,继承,多态
- 封装:封装的过程就是把一些属性和方法放到对象中“包裹”起来
- 继承:继承,简单的说就是可以从父级那里获取到共有的属性和方法
- 多态:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果,由于弱类型的特点,JS自带多态,或者说不需要这个概念
面向对象开发需要三个步骤:OOA面向对象分析,OOD面向对象设计,OOP面向对象编程
2.对象创建
对象是一种复杂的引用类型,拥有属性和方法
字面量创建
var obj = {a:1, b:2};
构造函数创建
var obj = new Object([value]); // 将value转换成对象
以某个对象作为原型创建
var obj1 = Object.create(obj); // obj1将继承obj的所有属性和方法
3.万物皆对像
在js中,万物皆对象,万物皆对象指的是:js中所有的数据类型的顶层,都是Object的原型,之所以说万物皆对象,是因为几乎所有的数据类型都有属于自己的属性或者方法(null 和 undefined除外)
数字是对象
普通的数字以阿拉伯数字的形式展示,但数字也有对象的形态,拥有toFixed等方法
字符串是对象
和数字一样,字符串也有对象的形态,拥有split,trim,substring,replace,match等的一系列方法以及length属性
布尔值是对象
布尔值拥有toString等方法
函数是对象
函数拥有apply,call,bind等方法,还拥有name(函数名),length(参数数量)等属性
元素是对象
元素拥有id,className,classList,innerHTML等属性
4.原型
原型,我们可以理解成创造一个产品的基本模型,该模型会定义这个产品的基本属性和所拥有的方法,创造出来的产品会拥有这些方法个属性,当然,我们也可以通过一个原型创造出另一个原型,创造的原型可以拥有原始原型的所有内容,并且可以重新定义新的内容,这就是我们所说的继承
举个栗子:1886年卡尔·奔驰制造出世界上首辆三轮汽车;1888年奔驰生产出世界上第一辆供出售的汽车。1893年杜里埃设计出美国第一辆汽油机汽车。1897年奥兹汽车公司成立,并出产第一辆奥兹汽车。1902年卡迪拉克汽车公司成立。1907年日本生产出第一辆汽车。旁蒂克公司(通用汽车)前身-奥克兰公司成立;到卡迪拉克汽车采用电起动发动机。哈德逊研制第一辆现代四门轿车。发明挡风玻璃刮水器。通用公司首先采用了车顶内灯。美国的克莱尔发明了倒车灯。这样成就了今天的汽车。在汽车历史中,每个具有独特属性的第一辆车,我们都可以看成是原型车,通过这个车,我们可以生产大批量的同类车,而原型之间,也是一个版本一个版本迭代而来,继承上一代车特点的同时,也新增了一些属性
5.原型链
任何一个对象都是原型和原型连接形成链式结构,这种链式结构叫做原型链,原型链的顶端都是Object的原型,原型链通过__proto__连接,以下是div元素的原型链
HTMLDivElement --> HTMLElement --> Element --> Node --> EventTarget --> Object
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HokyhcgX-1596372424197)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200727115827942.png)]
Object的原型是所有对象的原型,Object的原型定义了对象的基本属性和方法,所有的对象都继承于Object原型,都拥有Object原型上的方法和属性
6.对象属性的读取和设置
对象有两种属性,一种是自身的对象属性,另一种是自身原型的对象属性,自身的对象属性简称对象属性,自身原型的对象属性简称为原型属性。在对象获取属性是遵循以下原则:
先查看对象属性是否存在,如果存在直接读取该属性,如果对象属性不存在,就根据原型链依次从最近的原型属性逐级向上查找,直到找不到结果为undefined,在对象属性设置时,只能设置对象属性而不能设置原型属性
绑定在Object原型上的方法和属性会被所有的对象共享和使用
Object原型 是原型链的顶端
7.对象的属性
对象的每个属性都拥有描述对象,用来控制一个对象的属性是否可写,是否可枚举,是否可删除,描述对象包含以下四个属性
- value:属性的值
- writable:是否可写,默认为true,改成false则属性只可读,不可写
- enumerable:是否可枚举,默认为true,改成false则该属性不可被遍历
- configurable:是否可配置,可删除,默认为true,改为false则该属性不可被删除,不可再配置(排除将writable从true改为false,writable为true时修改value)
Object.defineProperty(obj, prop, config)
为对象定义单属性,使用描述对象,描述对象中属性不写则表示值为false
Object.defineProperty(obj, 'name', {
value: 'value',
configurable: false, // 不可再配置
writable: true, // 可写
enumerable: true // 可枚举
});
Object.defineProperties(obj, configObj)
为对象定义多属性,使用描述对象,描述对象中属性不写则表示值为false
Object.defineProperties(obj, {
a: {
value: 10
},
b: {
enumerable: true,
value: 20
},
c: {
configurable: true,
value: 30
}
})
Object.getOwnPropertyNames(obj)
获取对象的所有属性名,不管属性是否可枚举,组成数组并返回
Object.getOwnPropertyDescriptor(obj, prop)
获取对象指定属性名的描述对象并返回
Object.getOwnPropertyDescriptors(obj)
获取对象所有属性名的描述对象并返回
8.对象的静态方法
Object.assign
对象属性合并方法
// 对象复制的两种方法
var obj = {a:1, b:2};
// 循环复制
for(var prop in obj) {
o[prop] = obj[prop];
}
// 解构复制
var obj1 = {...obj};
Object.assign复制,将obj对象的所有属性复制到一个空对象上并返回这个空对象
var obj2 = Object.assign({}, obj);
当参数为多个对象时,将所有对象的所有属性复制到空对象,同名属性的值,以参数列表中靠后的对象属性的值决定,Object.assign不能复制原型链,不可枚举属性,以上的三种方法全是浅复制方法,只能复制到对象的第一层,JSON方法可以将对象进行深复制,不过同样无法复制不可枚举属性,同时不会复制方法
var obj = {a:1, b:2};
var obj1 = JSON.parse(JSON.stringify(obj)); // 深复制对象
Object.keys(obj)
将可枚举的属性组成数组并返回
Object.values(obj)
将可枚举的属性的值组成数组并返回
var obj = {
a: 10,
b: 20,
c: 30,
d: 40,
e: 50,
f: 60,
}
console.log(Object.keys(obj)) // ["a", "b", "c", "d", "e", "f"]
console.log(Object.values(obj)) // [10, 20, 30, 40, 50, 60]
Object.freeze(obj)
冻结对象,被冻结的对象不可修改属性,不可删除属性,也不可添加属性
Object.isFronzen(obj)
判断对象是否被冻结
Object.isExtensible(obj)
判断对象是否可以扩展,添加新属性(被冻结的对象不可添加新属性,值为 false)
Object.is(a, b)
判断两个值是否相同,和===类似,但不完全相同
console.log(-0 === +0); // true
console.log(Object.is(-0, +0)); // false
console.log(NaN === NaN); // false
console.log(Object.is(NaN, NaN)); // true
9.对象的原型方法
hasOwnProperty
判断属性是否为对象的对象属性而不是原型属性
true 是原型属性,false 不是原型属性(是自己的属性)
obj.hasOwnProperty('name'); // 判断name是否为obj的对象属性
isPrototypeOf
判断一个对象是否为另一个对象的原型
o.isPrototypeOf(obj); // 判断o是否为obj的原型对象
propertyIsEnumerable
判断对象上的某个属性是否可枚举
obj.propertyIsEnumerable('name'); // 判断obj的name属性是否可枚举
Object.keys(obj)、Object.values(obj)、hasOwnProperty 重点掌握
10.函数
函数的声明
声明命名函数
function fn(x, y){
return x + y;
}
声明匿名函数
var fn = function(x, y){
return x + y
}
实例化构造函数
var fn = new Function('x', 'y', 'return x + y');
函数的调用
函数调用的本质是函数本身后面接上小括号,函数名或者变量只是用来代表函数本身的,但不局限于这种形式
// 命名函数
function fn(x, y) {
console.log(x + y) // 3
return x + y
}
fn(1, 2)
console.log(fn)
// 匿名函数
var fn = function (x, y) {
console.log(x + y) // 3
console.log(arguments) // Arguments(2) [1, 2, callee: ƒ, Symbol(Symbol.iterator): ƒ]
return x + y
}
fn(1, 2)
console.log(fn)
// 构造函数
var fn = (x, y) => {
console.log(x + y) // 3
return x + y
}
fn(1, 2)
console.log(fn)
// 实例化构造函数
var fn = new Function('x', 'y', 'console.log(x + y)') // 3
fn(1, 2)
console.log(fn)
11.作用域链
作用域
在Javascript中,作用域分为全局作用域和局部(函数)作用域
- 全局作用域:代码在程序的任何地方都能被访问,window 对象的内置属性都拥有全局作用域
- 局部作用域:在固定的代码片段才能被访问
作用域链
一般情况下,变量取值到创建这个变量的函数的作用域中取值。但是如果在当前作用域中没有查到值,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链
12.内存的占用和释放
函数调用时才会执行内部的代码,并且占用内存,函数内部代码执行完毕后释放内存,由垃圾回收机制回收(GC),不需要手动释放内存
13.闭包
函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起构成闭包(closure)。也就是说,闭包可以让你从内部函数访问外部函数作用域。在JavaScript中,每当函数被创建,就会在函数生成时生成闭包
闭包的产生的关键
-
内部函数调用外部函数的变量
-
内部函数要被返回
-
返回的函数一定要被变量所接收(导致内部函数不能被释放)
function fn() {
var num = 10
function inner() {
console.log(num++)
}
return inner
}
// 这里的结果均为 10,是因为函数内部代码执行完毕后自动释放了内存
fn()() // 10
fn()() // 10
fn()() // 10
fn()() // 10
var f = fn() // 返回的函数被变量接收,导致内部函数不能被释放,形成闭包
f() // 10
f() // 11
f() // 12
f() // 13
词法作用域
请看下面的代码:
function init() {
var name = "Mozilla"; // name 是一个被 init 创建的局部变量
function displayName() { // displayName() 是内部函数,一个闭包
alert(name); // 使用了父函数中声明的变量
}
displayName();
}
init();
init()创建了一个局部变量name和一个名为displayName()的函数。displayName() 是定义在init()里的内部函数,并且仅在init()函数体内可用。请注意,displayName()没有自己的局部变量。然而,因为它可以访问到外部函数的变量,所以displayName()可以使用父函数init()中声明的变量name。运行该代码后发现,displayName()函数内的alert()语句成功显示出了变量name的值(该变量在其父函数中声明)。这个词法作用域的例子描述了分析器如何在函数嵌套的情况下解析变量名。词法(lexical)一词指的是:词法作用域根据源代码中声明变量的位置来确定该变量在何处可用。嵌套函数可访问声明于它们外部作用域的变量
闭包的产生
现在来考虑以下例子:
function makeFunc() {
var name = "Mozilla";
function displayName() {
alert(name);
}
return displayName;
}
var myFunc = makeFunc();
myFunc();
运行这段代码的效果和之前init()函数的示例完全一样。其中不同的地方(也是有意思的地方)在于内部函数displayName()在执行前,从外部函数返回。闭包是由函数以及声明该函数的词法环境组合而成的。该环境包含了这个闭包创建时作用域内的任何局部变量。在本例子中,myFunc是执行makeFunc时创建的displayName函数实例的引用。displayName的实例维持了一个对它的词法环境(变量name存在于其中)的引用。因此,当myFunc被调用时,变量name仍然可用,其值Mozilla就被传递到alert中
闭包计时器
用闭包写一个不受外界影响的计时器
(time => {
setInterval(() => {
console.log(++time);
}, 1000);
})(0);
加强版,可暂停,可重启,可设置计时起点
var timer = ((time, timer) => {
return {
start: function () {
clearInterval(timer);
timer = setInterval(() => {
console.log(++time);
}, 1000);
},
stop: function () {
clearInterval(timer);
},
set: function (num) {
time = num;
},
restart: function () {
clearInterval(timer);
time = 0;
timer = setInterval(() => {
console.log(++time);
}, 1000)
},
time: function () {
return time
}
}
})(0, null);
timer.start();
加强版,可暂停,可重启,可设置计时起点
var timer = (() => {
var time = 0
var timer = null
return {
// 开始
start: () => {
clearInterval(timer)
timer = setInterval(() => {
console.log(++time)
}, 1000);
},
// 暂停
stop: () => {
clearInterval(timer)
},
// 重置
reset: () => {
time = 0
clearInterval(timer)
timer = setInterval(() => {
console.log(++time)
}, 1000);
},
// 继续
set: (num) => {
time = num
}
}
})()
timer.start()
14.闭包的总结
- 定义:当一个函数的返回值是另外一个函数,而返回的那个函数如果调用了其父函数内部的变量,且返回的这个函数在外部被执行就产生了闭包.闭包是一个环境,具体指的就是外部函数–高阶函数。说白了就是一个环境,能够读取其他函数内部的变量。本质上,闭包是将函数内部和函数外部连接起来的桥梁
- 用处:
- 读取函数内部的变量
- 这些变量的值始终保持在内存中,不会在外层函数调用后被自动清除
- 优点:
- 变量长期驻扎在内存中
- 避免全局变量的污染
- 私有成员的存在
- 特性:
- 函数套函数
- 内部函数可以直接使用外部函数的局部变量或参数
- 变量或参数不会被垃圾回收机制GC回收
- 缺点:
变量常驻内存,会增大内存的使用量,使用不当会造成内存泄露(内存泄露:指占用内存过多)