一、闭包技术详解
1.1 什么是闭包
闭包就是大函数内部的小函数中,调用小函数外部(大函数内部)的参数
闭包实际上是一种函数,所以闭包技术也是函数技术的一种;闭包能做的事情函数几乎都能做。
闭包技术花式比较多,用法也比较灵活,一般开发人员在学习闭包的时候都会遇到瓶颈,主要是因为闭包技术的分界线并不明显。几乎无法用一个特点去区分。
当一个内部函数被其外部函数之外的变量引用时,就形成了一个闭包。
function A(){function B(){console.log("Hello YJH!");} return B; } var b = A(); b();//Hello YJH!
闭包的最大用处有两个:一个是可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。
使用闭包返回函数,间接调用局部变量。
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body><script>function test1() {var age = 19; //局部变量}console.log(age);</script></body></html>
运行结果:
function test1() {var age = 19; //局部变量return function () {console.log(age);//返回一个函数}}var func = test1();func();
运行结果:
1.2 常见的闭包使用形式
将函数作为另一个函数的返回值
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body><script>function fn1() {var num = 10;function fn2() {num++;console.log(num)}return fn2;}var f = fn1();f();</script></body></html>
可以在这里查看闭包
使用闭包最重要的意义:一个函数中的部分子函数在函数调用完成后仍希望保存,可以使用闭包将小函数作为大函数的返回值进行传输保存。
当引用的变量无用时,需要及时删除闭包,不然会造成资源浪费。令保存闭包的全局变量=null,即可让闭包变成垃圾对象删除。
将函数的形参作为实参传递给另一个函数调用
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body><script type="text/html">// 1. 将函数作为另一个函数的返回值function fn1() {var num = 10;function fn2() {num++;console.log(num)}return fn2;}var f = fn1();f();</script><script>// 2.将函数的形参作为实参传递给另一个函数调用function logMsgDelay(msg, time) {setTimeout(function () {debugger;console.log(msg);}, time);}logMsgDelay('Joker',1000);</script></body></html>
1.3 封闭作用域
JavaScript的GC机制
在javascript中,如果一个对象不再被引用,那么这个对象就会被GC回收,否则这个对象一直会保存在内存中。
封闭作用域
封闭作用域又称值为封闭空间,还有一个昵称叫小闭包,以及匿名函数自调。
基本结构:
(function(){})();;(function(){})();+(function(){})();-(function(){})();?(function(){})();
技法最大目的:全局变量私有化
技术优点:
不污染全局空间!内部所有的临时变量执行完毕都会释放不占内存。可以保存全局数据。更新复杂变量。
1.4 作用域链
嵌套之间的函数会形成作用域链,每次对变量的访问实际上都是对整条作用域链的遍历查找。先查找最近的作用域,最后再查找全局作用域。如果在某个作用域找到了对量就会结束本次查找过程。思考?🐷对于作用域全局作用域查找快,还是局部作用域查找快?
局部作用域查找要远远大于全局作用域查找的速度。所以高级的程序设计一般是尽量避免全局查找。
每次访问都是对作用域链的一次遍历查找其中全局作用域是最耗费时间的。解决方案:
当前目标使用完以后,在退出作用域之前储存这个目标,就可以在下次取到上一次的目标。补充
变量的生命周期 任何一个变量在内存中都是一个引用,这个变量是有自己的生命周期。周期结束意味着被销毁。 一个变量在它当前的作用域内被声明那一刻相当于变量出生,整个当前作用域执行完毕并退出作用域相当于变量的寿命终止。
1.5 保存作用域
保存作用域是一种更高级的闭包技术,如果函数嵌套函数,那么内部的那个函数将形成作用域闭包。简单的说,这种闭包能够达到的好处就是让指令能够绑定一些全局数据去运行;
基本结构
var A=function(){return function(){};}
优点:
全局数据隐藏化可以让某个指令运行时候绑定一些隐藏的全局数据在身上。一句话: 将数据绑定在指令上运行,让指令不再依赖全局数据。
1.6 保存全局变量
通过第二个括号将全局变量传入到闭包中,使其变成局部变量,优化调用。
(function (document) {var btn = document.getElementById('btn');var btn1 = document.getElementById('btn');var btn2 = document.getElementById('btn');var btn3 = document.getElementById('btn');})(document);
1.7 高级排他
只记录上一次改变的,不再像之前一样两次遍历。
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title><style>li{background-color: #ccc;border: 1px solid #000;cursor: pointer;}.current{background-color: red;}</style></head><body><ul><li class="current"></li><li></li><li></li><li></li><li></li><li></li><li></li><li></li><li></li><li></li></ul><script>var allLis = document.getElementsByTagName('li');var lastOne = 0;for (var i = 0; i < allLis.length; i++) {(function (index) {allLis[index].onmouseover = function (ev) {// 清除allLis[lastOne].className = '';// 设置this.className = 'current';// 赋值lastOne = index;}})(i);}</script></body></html>
1.8 参数传递
function test(x) {return function (y) {console.log(x);console.log(y);}}test(1)(2);
运行结果:
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title></title><style>body{text-align: center;}</style></head><body><img id="l" src="images/left.png" alt=""><br><img id="r" src="images/right.png" alt=""><script>function move(speed) {var num = 0;return function () {num += speed;this.style.marginLeft = num + 'px';}}var lImg = document.getElementById("l");var rImg = document.getElementById("r");lImg.onmousedown = move(-50);rImg.onmousedown = move(50);</script></body></html>
运行结果:点击左右移动
1.9 函数节流
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title></title></head><body><script>/* 传统节流方式var timer = null;window.onresize = function () {clearTimeout(timer);timer = setTimeout(function () {console.log(document.documentElement.clientWidth);}, 400);}*//*** 闭包节流方式* @param fn 要做的事情* @param delay 延时时间* @returns {Function} 定时器函数*/function throttle(fn, delay) {var timer = null;return function () {clearTimeout(timer);timer = setTimeout(fn, delay);}}window.onresize = throttle(function () {console.log(document.documentElement.clientWidth);}, 400);</script></body></html>
1.10 闭包的缺点及解决
缺点
函数执行完成后,函数内部的局部变量没有释放,占用内存时间会边长,容易造成内存泄露。
解决
及时释放
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body><script>function fn1() {var arr = new Array[999999999999];function fn2() {console.log(arr);}return fn2;}var f = fn1();f();// 释放f = null;</script></body></html>
二、面向对象
对象是什么?
对象就是带有属性和方法的数据类型!
任何一门高级语言都要面向对象,JavaScript则是基于原型的面向对象语言,因此,我们的思维要由面向过程转向面向对象:
面向对象里面:类和对象
类是对象的抽象,而对象是类的具体实例
一切事物皆对象 JavaScript 一切皆对象
面向对象的特征
抽象抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面。
把一些比较散的,单一的值,有结构的组装成为一个整体。
把一些值隐藏在内部,不暴露给外界。封装
封装是把过程和数据封闭起来,对数据的访问只能通过开放的接口。继承
子类对象继承使用父类的属性和方法。多态
多态是指两个或多个属于不同类的对象,对于同一个消息(方法调用)作出不同响应的方式。
三、构造函数
所有的构造函数有一个特点:首字母大写;
在js中我们可以理解为只要执行以后能够返回新的对象的函数就是构造函数。
构造函数技巧的最大目的:创造完全独立的对象,互相之间不影响。
3.1 关键词new
将一个函数变成对象并返回在这个函数的内部将this指向函数本身。new这个关键词实际上能够将任何函数直接变成一个对象。它只有在和构造函数配合的时候才有用,它相当于可以化简构造函数自己创造对象和返回对象的步骤。3.2 构造器(constructor) 和 原型属性 (prototype)
在任何一个对象中都有构造器和原型属性,包括原生的对象,比如: Date, Array等;
constructor 返回对创建此对象的 构造函数的引用prototype 让我们有能力向对象添加属性和方法
原型共享库是谁使用的?prototype它的作用就是构造函数的一个共享库;在这个共享库里面存储的所有数据将来都会被所有的新对象公用。 这样大大降低了创建方法的成本。
构造函数使用原型库,所有将来的对象共享这个原型库。
如果把方法都写在构造函数的原型库里面,将来还可以通过原型继续拓展。原型的工作原理?
在网页发布以后,原型的工作会自动做以下两件事情:
第一:自动将原型库中的所有内容都放在将来的对象身上;
第二:如果共享库中的内容发生变化会自动更新所有对象上的数据。注意:
在面向对象的写法当中,原型的共享库里面所有的方法中的this默认情况都会指向将来的对象。
只有在两个情况会发生变化,那么这两个情况一定要检查作用域:
第一:如果在事件的作用域中,this的指向会变成事件源。
第二:如果在定时器的作用域中,this的指向会变成window。解决方案:备份指针
3.3 构造函数使用-注意事项
3.3.1 构造函数设置属性和方法
通过this设置
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body><script>function Person(name, age, doFunc){// var this = new Object();this.name = name;this.age = age;this.doFunc = doFunc;// return this;}var p1 = new Person('张三',18,function () {console.log('张三在上课');})var p2 = new Person('李四',18,function () {console.log('李四在跑步');})p1.doFunc();console.log(p2.name);</script></body></html>
区分:实例属性/实例方法 & 静态属性/静态方法
实例属性/实例方法:都是绑定在使用构造函数创建出来的对象p上;最终使用的时候也是使用对象p来进行访问。
比如上面中的name、age、doFunc()静态属性/静态方法:绑定在函数身上的属性和方法
注意:函数本质上也是一个对象,既然是个对象,那么就可以动态的添加属性和方法。只要函数存在,那么绑定在它身上的属性和方法,也会一直存在
应用场景:记录总共创建了多少个人对象
方案1:全局变量
<script>// 设置全局变量var personCount = 0;function Person(name, age, doFunc){this.name = name;this.age = age;this.doFunc = doFunc;personCount++;}var p1 = new Person('张三',18,function () {console.log('张三在上课');});var p2 = new Person('李四',18,function () {console.log('张三在跑步');})console.log('总共创建了' + personCount + '个人');</script>
方案2:实例属性(只能作用在对象上,不能作用在函数上)
<script>function Person(name, age, doFunc){this.name = name;this.age = age;this.doFunc = doFunc;if (!this.personCount){this.personCount = 0;}this.personCount++;}var p1 = new Person('张三',18,function () {console.log('张三在上课');});var p2 = new Person('李四',18,function () {console.log('张三在跑步');})console.log('总共创建了' + p1.personCount + '个人');console.log('总共创建了' + p2.personCount + '个人');</script>
方案3:静态属性/静态方法
<script>function Person(name, age, doFunc){this.name = name;this.age = age;this.doFunc = doFunc;// 创建静态属性if (!Person.personCount){Person.personCount = 0;}Person.personCount++;}// 创建静态方法Person.printPersonCount = function () {console.log('总共创建了' + Person.personCount + '个人');};var p1 = new Person('张三',18,function () {console.log('张三在上课');});var p2 = new Person('李四',18,function () {console.log('张三在跑步');});var p3 = new Person('王五',18,function () {console.log('王五在打球');});// 输出Person.printPersonCount();</script>
3.3.2 关于创建出来的对象类型获取
获得内置对象的类型① typeof 判断是不是对象
② toSting()
③ constructor.name 获取创建该对象的构造函数
④ Object.prototype.toString.call(arr); 看该对象祖宗是不是Object
<script>var obj = {'name':'张三'};var arr = [1,2,3];console.log(typeof arr);console.log(arr.toString());console.log(arr.constructor);console.log(arr.constructor.name);console.log(Object.prototype.toString.call(arr));</script>
运行结果:
获取根据自己声明的构造函数创建的对象
① typeof p
② p.toString()
③ Object.prototype.toString.call§
④ p.constructor.name 💎💎
⑤ p.constructor 💎💎
注意:因为在自己声明的构造函数中,this是new Object创建的,所以③中结果为object
<script>function Person(name, age) {// var this = new Object();this.name = name;this.age = age;}// 1. 实例化var p = new Person('张三',18);console.log(typeof p);console.log(p.toString());console.log(Object.prototype.toString.call(p));;console.log(p.constructor.name);console.log(p.constructor);</script>
运行结果:
因此,获得对象最真实的类型,使用对象.constructor.name最好
3.3.3 关于创建出来的对象类型验证
判断一个对象是否为基于其原型链上的构造函数。
instanceof
<script>function Person(name, age) {this.name = name;this.age = age;}var p = new Person('张三',18);console.log(p instanceof Person);console.log(p instanceof Object)</script>
运行结果:
3.4 访问函数原型对象的方式
<script>function Person(name,age) {this.name = name;this.age = age;}Person.prototype.run = function(){console.log(跑);};// 方式一console.log(Person.prototype);// 方式二var p = new Person();console.log(p.__proto__);</script>
通过构造函数方式。函数名.prototype💎💎通过实例化出来的对象中的__proto__属性访问
注意点说明:__proto__是一个非标准属性,即ECMAScript并不包含该属性,这只是某些浏览器为了方便开发人员开发和调试而提供的一个属性,不具备通用性 建议:在调试的时候可以使用该属性,但不能出现在正式的代码中
3.5 hasOwnProperty和in属性操作
in判断一个对象,是否拥有某个属性(如果对象身上没有,会到原型对象里面查找)hasOwnProperty:只到对象自身查找思考:怎样判断一个属性仅仅是原型对象属性?
<script>function Person(name, age) {this.name = name;this.age = age;}Person.prototype.address = '山东';var p = new Person('hk',18);//console.log(name in p); //falseconsole.log('name' in p);console.log('address' in p);console.log(p.hasOwnProperty('name'));console.log(p.hasOwnProperty('address'));</script>
运行结果:
3.6 isPrototypeOf 和 instanceOf
isPrototypeOf:判断一个对象,是否是某个实例的原型对象instanceOf:判断一个对象,是否是某个构造函数的原型链上的<script>function Person(){}var p = new Person();console.log(Person.prototype.isPrototypeOf(p));console.log(p.__proto__.isPrototypeOf(p));var obj = {'name':'hk'};Person.prototype = obj;var pp = new Person();console.log(Person.prototype.isPrototypeOf(p));console.log(Person.prototype.isPrototypeOf(pp));</script>
运行结果:
声明好的函数的原型对象如果被修改,那么之前实例化的实例就不属于其新的原型对象。
<script>function Person(){}var p = new Person();console.log(p instanceof Person);var obj = {'name':'hk'};Person.prototype = obj;var pp = new Person();console.log(pp instanceof Person);</script>
运行结果:
3.7 原型完善—constructor
用于获取一个对象的真实类型
<script>function Person() {}var p = new Person();Person.prototype = {'name':'hk','age':18};var pp = new Person();console.log(p.constructor.name);console.log(pp.constructor.name);console.log(p);console.log(pp);</script>
运行结果:
在上面的代码中,我们修改了Person的原型对象。结果新的Person中没有了constructor。因此,如果我们直接修改构造函数的原型对象,需要加入constructor属性,并为其赋值构造函数。如果不设置,新的构造函数会指向Object