一、闭包
1、函数的执行空间
function fn(){
console.log("fn函数")
}
fn();
函数执行的时候会开辟一个执行空间(我们暂且叫他 xxff00 )。console.log(我是fn函数’)这个代码就是在xxff00这个空间中执行。代码执行完毕以后,这个xxff00空间就销毁了。fn内部局部变量的生命周期:作用域开始的位置被声明,结束的位置被删除(感觉像朝生暮死)。
2、闭包的定义
闭包是JavaScript中,函数的一种高级应用方式 。是利用作用域的嵌套,触发了计算机的垃圾回收机制,将原本的局部变量进化成私有变化的环境。
3、闭包的形成环境(三要素,缺一不可)
(1)有一个函数A , 在函数A内部返回一个函数B
(2)在函数B 中访问函数A 的私有作用域变量
(3) 在函数A 外部,有变量引用函数B
简单的理解就是需要有:函数的嵌套;内部函数使用外部函数中的变量;将内部函数返回到外部函数的外部,接收返回值并执行(相当于执行了内部函数)。
4、闭包的特点 (既是优点,同时也是缺点 )
(1)作用域空间不销毁
(2)可以通过闭包语法,从外部访问函数内部变量
(3)保护私有变量
5、闭包的应用场景
(1)循环中的事件,事件处理函数中使用了循环的每次的计数器
<body>
<div id="box"></div>
<ul class="list">
<li>link1</li>
<li>link2</li>
<li>link3</li>
<li>link4</li>
<li>link5</li>
</ul>
</body>
<script>
var ali = document.querySelectorAll(".list li");
for(var i=0;i<ali.length;i++){
//第一种写法
(function(index){
ali[index].onclick = function(){
console.log(index);
}
})(i);
//第二种写法
// ali[index].onclick = (function(index){
// return function(){
// console.log(index);
// }
// })(i);
//第三种写法
//function box(index){
// ali[index].onclick = function(){
// console.log(index);
// }
// box(i);
//}
}
</script>
(2)给某些系统默认的回调函数,传参(如给计时器的回调函数,传参))
function fn(a){
return function(){
console.log(a);
};
}
setTimeout(fn("world"), 1000);
(3)处理掉全局变量(不造成全局污染),只要开启新文件,新功能,最好上手就是一个匿名函数,简易闭包。(模块化开发)
//如果当前功能只是一个封装,不只是在当前使用,将来在其他位置也会使用,或者给别人使用.
var f = (function(){
var a = "hello";
function fn(){
console.log(a + "world");
}
return fn;
})();
f();
(4)封装过的事件委托,利用到了闭包
二、原型
1、对象的__proto__是什么
js中万物皆对象,每个数据都会有一个__proto__的属性,这个属性叫隐式原型。
一个对象(obj)的隐式原型(proto)指向构造该对象(obj)的构造函数(Object())的原型属性(Object.prototype)。
这样做的原因是为了能够保证实例(obj)能够访问到在构造函数(Object())的原型属性(Object.prototype)中定义的属性和方法。
2、函数对象的prototype是什么
函数(Function)是一个特殊的对象,除了和其他对象一样有上述__proto__属性之外,还有自己特有的属性——原型(prototype),这个属性被描述成指针。
他指向一个对象类型的数据,这个对象的用途就是包含所有将来使用该函数构造出来的可被共享的属性和方法(我们把这个对象叫做原型对象)。
原型对象内也有一个属性,叫做constructor,这个属性包含了一个指针,指回原函数。类似于arguments.callee,但是arguments只能在函数内部获得,而函数原型对象内的constructor属性,可以在任何能访问到这个函数的位置使用。
3、构造函数、原型、实例之间的关系
(1)构造函数Fn身上有属性prototype为原型对象,原型对象内有constructor属性指向当前prototype所在的构造函数Fn
(2)在new执行构造函数Fn时,创造了一个实例对象f,实例对象f的__proto__指向构造函数Fn的原型prototype
(3)因为实例对象f的__proto__指向构造函数Fn的原型prototype,所以实例对象f可以间接访问到Fn原型prototype的方法
4、实例和原型关系检测
(1)isPrototypeOf()函数,用于检测两个对象之间似乎否存在原型关系,使用方法如下:
// 查看 Fn 的 prototype 对象,是否是 f 原型
Fn.prototype.isPrototypeOf(f)
(2)instanceof运算符,用于检测某实例是否来自于某构造函数,使用方法如下:
// 查看 f 对象是否是构造函数 Fn 的实例
console.log(f instanceof Fn)
// 查看 f 对象是否是构造函数 Fn 的实例
console.log(f instanceof Object)
(1)(2)两种使用,如果是返回ture,如果不是返回false
注意:instanceof运算符右侧为构造函数,并且js中所有原型都来自Object构造函数
5、js解析器访问属性顺序
解析器的解析顺序遵循:就近原则,当访问实例 f 的属性或方法时,会先在当前实例对象 f 中查找,如果没有,则沿着__proto__继续向上寻找,如果找到最顶头的Object还是找不到,则会抛出undefined。如果在实例中找到,或某层原型中找到,就会读取并使用,同时停止向上找寻。
三、继承
1、什么是继承
(1)继承是与 构造函数 相关的应用;
(2)是指,让一个构造函数去继承另一个构造函数的属性和方法
(3)继承是发生在两个构造函数之间的。
2、ES5 之继承的常见方法
(1)原型继承
原型继承——原型对象继承(拷贝prototype)
简单,方便,易操作。但是这种继承,只能继承原型身上的方法和属性,不能继承构造函数内的方法和属性。(注意属性的位置)。
function Parent(){
this.name = "admin";
}
Parent.prototype.show = function(){
console.log("hello");
}
function Child(){}
// 浅拷贝
// Child.prototype = Parent.prototype;
// 深拷贝
for(var i in Parent.prototype){
Child.prototype[i] = Parent.prototype[i];
}
var p = new Parent();
p.show(); //hello
console.log(p.name); //admin
var c = new Child();
c.show(); //hello
console.log(c.name); //undefined 原型对象继承,不能继承构造函数内的方法和属性
原型继承——原型链继承(将prototype设置成另一个构造函数的实例)
更加的简单,方便,易操作。不仅可以继承原型身上的方法和属性,而且还可以继承构造函数中的方法和属性。但是这种继承,不方便传参。(注意传参的位置)
function Parent(){
this.name = "hello world";
}
Parent.prototype.show = function(){
console.log("你好");
}
function Child(){};
// 这里对象访问属性或方法的过程:Child的实例c ---> __proto__ ---> Child.prototype ---> Parent的实例 ---> __proto__ ---> Parent.prototype
Child.prototype = new Parent();
var p = new Parent();
p.show(); //你好
console.log(p.name); //hello world
var c = new Child();
c.show(); //你好
console.log(c.name); //hello world
(2)借用构造函数继承 (通过改变this指向)
方便的传参,还可以实现多继承,但是只能继承构造函数内部的属性或方法,不能继承原型身上的属性或方法。
function Parent(s){
this.skill = s;
}
function Child(n){
// 利用this的改变
// 在Child中执行Parent的同时修改this指向,为Child的this
// 因为Child将来被new执行,Child中的this,指向将来Child的实例
// Parent.call(this,n);
// Parent.apply(this,[n]);
Parent.bind(this,n)();
}
var p = new Parent("大盒子");
console.log(p.skill); //大盒子
var c = new Child("小盒子");
console.log(c.skill); //小盒子
借助构造函数继承 – 多继承
function Mp3(){
this.music = "放音乐";
}
function Camera(){
this.photo = "拍照";
}
function Tel(){
this.call = "打电话";
}
function Email(){
this.message = "发信息";
}
function MobilePhone(n){
Mp3.call(this);
Camera.call(this);
Tel.call(this);
Email.call(this);
this.name = n;
this.game = "打游戏";
}
var mp = new MobilePhone("HUAWEI P30");
console.log(mp);
执行结果:
(3)组合继承 (原型继承+构造函数继承)
组合继承略复杂,既可以继承构造函数,又可以继承原型,方便传参,可以多继承构造函数。注意:原型链继承时,依然有参数隐患
function Parent(s){
this.skill = s;
// 测试原型链继承的参数隐患
// this.skill.split();
}
Parent.prototype.show = function(){
console.log(this.skill);
}
function Child(s){
Parent.call(this, s);
}
// Child.prototype = new Parent();
for(var i in Parent.prototype){
Child.prototype[i] = Parent.prototype[i];
}
var p = new Parent("大盒子");
p.show();
var c = new Child("小盒子");
c.show();
3、ES6 class继承的语法
ES6语法中,提供了非常简单的继承方式
ES6 class继承原理就是:构造函数方式继承 + 原型链继承
用ES6 class继承实现的拖拽效果,一个有边界限定,另一个没有边界限定。具体代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style type="text/css">
#box1{width: 100px;height: 100px;background:pink;position: absolute;left: 0;top: 0;}
#box2{width: 100px;height: 100px;background:green;position: absolute;left: 0;top: 130px;}
</style>
</head>
<body>
<div id="box1"></div>
<div id="box2"></div>
</body>
<script type="text/javascript">
class Drag{
constructor(ele){
this.ele = ele;
this.addEvent();
}
addEvent(){
var that = this;
this.ele.onmousedown = function(eve){
that.downE = eve || window.event;
document.onmousemove = function(eve){
that.moveE = eve || window.event;
that.move();
}
document.onmouseup = function(){
that.up();
}
}
}
move(){
this.ele.style.left=this.moveE.clientX-this.downE.offsetX+"px";
this.ele.style.top=this.moveE.clientY-this.downE.offsetY+"px";
}
up(){
document.onmousemove = null;
}
}
class SmallDrag extends Drag{
constructor(ele){
super(ele);
}
move(){
let l = this.moveE.clientX-this.downE.offsetX;
let t = this.moveE.clientY-this.downE.offsetY;
if(l<0) l=0;
if(t=0) t=0;
this.ele.style.left=l+"px";
this.ele.style.top=t+"px";
}
}
var obox1 = document.getElementById("box1");
var obox2 = document.getElementById("box2");
new Drag(obox1);
new SmallDrag(obox2);
// 当有多个相似程序或相似对象,具有一些类似功能时,可以将公共功能做成一个总的父级
// 所有细节部分,在继承公共对象之后,另做修改
</script>
</html>
实现效果如下: