什么是闭包
在函数外部能够读取其他函数内部变量的函数。
通俗理解的闭包: 一个内部函数引用了外部函数的变量,外部函数形成了一个闭包
闭包的特点
优点
希望一个变量长期存储在内存中。可以读取函数内部的变量,避免设置过多全局变量造成污染
缺点
1 常驻内存,增加内存使用量。
2 使用不当会很容易造成内存泄露,内存中能存的东西越来越少,像是其他部分被泄露了一样.
3 设置私有变量(内部函数调用外部函数的局部变量,此时,这个局部变量就会变成内部函数的私有变量)
闭包认识
初体验
例:在函数的外部实现函数内部变量的访问
functionfun1(){
varn=999;
functionfun2(){
console.log(n);
}
returnfun2;//fn2 就是一个闭包函数,因为他能够访问到outer函数的作用域
}
varr=fun1();
r();
原理
function fun1(){
function fun2(){
console.log(999);
}
return fun2; //fn2 就是一个闭包函数,因为他能够访问到outer函数的作用域
}
var r=fun1();
r();
js中的变量,函数在执行的时候都会被加载到栈中,执行完毕在弹出,当使用闭包的时候,函数执行完成后,不会弹出,因为其他的地方还要加载其内部变量
面试题 一
使用原生js给每个li绑定onclick事件,点击后输出当前li对应的索引值
一般方式
function test(){
var liObj = document.getElementsByTagName('li');
var len = liObj.length;
for(var i=0;i liObj[i].onclick = function(){ console.log(i); } } } test(); 闭包写法1: var liObj = document.getElementsByTagName('li'); function test(){ var len = liObj.length; for(var i=0;i addClick(i); } } function addClick(j){ liObj[j].onclick = function(){ console.log(j); } } test(); 闭包写法2: var liObj = document.getElementsByTagName('li'); function test(){ var len = liObj.length; for(var i=0;i (function(j){ liObj[j].onclick = function(){ console.log(j); } }(i)) } } test(); 多学一招:自定义匿名函数 (function(){})是一个标准的函数定义,但是没有复制给任何变量。所以是没有名字的函数,叫匿名函数。没有名字就无法像普通函数那样随时随地调用了,所以在他定义完成后就马上调用他,后面的括号()是运行这个函数的意思 面试题 二 不能使用this, 利用闭包,打印每一个li的文本
//不能使用this, 利用闭包,打印每一个li的文本
var lis = document.getElementsByTagName('li');
// 常规使用this的写法
for(var i = 0; i < lis.length; i++) {
lis[i].onclick = function(){
console.log(this.innerText);
console.log(lis[i].innerText);
}
}
for(var i = 0; i < lis.length; i++) {
(function(i){
lis[i].onclick = function(){
// console.log(this.innerText);
console.log(lis[i].innerText);
}
})(i);
总结:闭包的特点
1 闭包的作用:充当一个摄像头,函数外部可以访问函数内部的变量,减少变量的生命,避免造成污染.
2缺点:内存的占用比较大,浪费内存.
容易造成内存的泄露.
函数内部的私有变量就不存在了.
第二节:
1.掌握闭包的特点和原理
2.掌握闭包的应用场景
写出一个简单闭包,函数father里有一个局部变量a, 在函数外(全局状态)能调到,并每次累加
function father(){
var a = 10;
return function(){
a++;
return a;
};
}
var son = father();
console.log(son())
console.log(son())
console.log(son())
console.log(son())
第三节:
prototype原型继承
所欲函数只要创建出来,系统都会分配一个原型对象给整个函数,通过prototype找到原型对象.
我们创建的每个函数都有一个 prototype(原型)属性。使用原型的好处是可以让所有对象实例共享它所包含的属性和方法。
定义:
1 原型是function对象的一个属性,它定义了构造函数制造出的对象的公共祖先,通过该构造函数产生的对象,可以继承该原型的属性和方法.原型也是对象.
2 利用原型的概念和特点可以提取共有属性.
3 实例化对象可通过_ _proto__查看原型
原型与对象
概念1讲解
function Person(){
}
// Person.prototype 原型 祖先
Person.prototype.age = 18;
Person.prototype.say = function(){
console.log('吃饭了');
}
//Person.prototype.age = 22;
let p1 = new Person();
let p2 = new Person();
console.log(p1);
概念2讲解
第一步:
function Cart(ower,color){
this.name="BMW";
this.lang = '3米'
this.price = 30000;
this.ower = ower;
this.color = color;
}
let p1 = new Cart();
let p2 = new Cart();
console.log(p1);
console.log(p2);
console.log(p1===p2);
此时可以看出构造函数的多次创建会产生多个相同函数,造成冗余太多。
利用原型prototype解决。回顾prototype
console.log(Cart.prototype);
//constructor表示当前的函数属于谁
//__proto__ == [[prototype]],书面用语,表示原型指针
第二步:直接使用原型提取公共属性
// Cart.prototype.name = "BMW";
// Cart.prototype.lang = "3米";
// Cart.prototype.price = 30000;
Cart.prototype={
name:'BMW',
lang:'3m',
price:30000
}
function Cart(ower,color){
this.ower = ower;
this.color = color;
}
let p1 = new Cart();
let p2 = new Cart();
console.log(p1);
console.log(p2);
概念3讲解
原型链继承
JavaScript 中描述了原型链的概念,并将原型链作为实现继承的主要方法。其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。简单回顾一下构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指
原型链的连接点就是 __ proto __,原型是没有的!
从近的往远的访问.
_ proto _ 是实例对象,拥有对的指向原型的属性,而构造函数是没有的.
function Person(name){
this.name = name;
this.age = 18;
}
var zs = new Person('zs');
// console.log(Fn.prototype.constructor);
console.log(zs.__proto__); // {constructor: ƒ} Fn的原型
console.log(zs.__proto__.__proto__.constructor); //Object
console.log(zs.__proto__.__proto__.__proto__); //null
原型链的构造
Grand.prototype.lastName='cheng';
functionGrand(){
}
vargrondObj=newGrand();
Father.prototype=grondObj;
functionFather(){
this.name="runzhi"
}
varfatherObj=newFather();
Son.prototype=fatherObj;
functionSon(){
this.hobbit='smoke';
}
varsonObj=newSon();
当Son要访问一个属性不存在时,就会找到原型的指针__proto__,然后找到原型继续找
实例属性与原型属性
原型属性的操作
查,即获取数据
删,子孙不能删除,其本身可以删除
改,除非自己增,后辈无法增
增,自己增,后辈无法增
call/apply继承
作用:都是改变this的指向,只是传参的形式不同
call方法
作用: 调用该函数,并修改函数中this的指向
语法: 函数名. call(对象,[实参]);
参数详解:
第一个参数: 要让函数中this指向谁,就写谁
后面的参数: 被调用函数要传入的实参,以逗号分隔
functionPerson(name,age){
this.name=name;
this.age=age;
console.log(this);
}
varpersonObj=newPerson('cheng',88);
letobj={}
Person.call(obj,'feng',66);
例1:编写person和sudent两个函数,让student中的属性和person的一样,通过call改变调用方法
第一步:
function Person(name,age,sex){
this.name = name;
this.age = age;
this.sex = sex;
}
function Student(name,age,sex,tel,grade){
this.name = name;
this.age = age;
this.sex = sex;
this.tel = tel;
this.grade = grade;
}
var student = new Student();
第二步:在Student中调用call,传递this
function Person(name,age,sex){
this.name = name;
this.age = age;
this.sex = sex;
console.log(this);
}
function Student(name,age,sex,tel,grade){
Person.call(this,name,age,sex,);
this.tel = tel;
this.grade = grade;
}
var student = new Student('cheng',20,'男',18336629253,'A');
注意:this的指向
// student() = Student.call();
例2:企业中的模块化处理
function Wheel(WheelSize,style){
this.style = style;
this.WheelSize = WheelSize;
}
function Sit(c,sitColor){
this.c = c;
this.sitColor = sitColor;
}
function Model(height,width,len){
this.height = height;
this.width = width;
this.len = lenl
}
function Car(WheelSize,style,c,sitColor,height,width,len){
Wheel.call(this,WheelSize,style);
Sit.call(this,c,sitColor);
Model.call(this,height,width,len);
}
var car = new Car(1,23,45,66,7,8,89,56);
公司中的模块化处理,别人把一些常用的开发好了,直接去调用
apply 方法
作用: 调用该函数,并修改函数中this的指向
语法: 函数名. apply(对象,数组);
参数详解:
第一个参数: 要让函数中this指向谁,就写谁
第二个参数: 要去传入一个数组,里面存放被调用函数需要的实参
functionfn(x,y){
console.log(this);// {a : 1}
console.log(x+y);//8
}
fn.apply({a:1}, [3,5]);
bind方法
作用: 不调用函数,克隆一个新的函数,并修改新函数中this的指向,将新的函数返回
语法: 函数名. bind(对象[,实参]);
参数详解:
第一个参数: 要让函数中this指向谁,就写谁
后面的参数: 被调用函数要传入的实参,以逗号分隔
例1:实现fn中this指向的改变
functionfn(x,y){
//调用newFn时打印结果
console.log(this);//{b:2}
console.log(x+y);//8
}
varnewFn=fn.bind({b:2},3,5);
newFn();
例2:给每一个li注册一个点击事件,点击这个li之后,每隔一秒钟,打印一下当前li中的文本
// 1.1 获取元素
var lis = document.getElementsByTagName('li');
// 1.2 给每一个li注册点击事件
for(var i = 0 ; i < lis.length; i++){
lis[i].onclick = fn;
}
//每一个li的事件处理函数
function fn(){
// console.log(this);// --> 点击的那个li
// 1.3 在事件处理函数中,设置一个定时器
setInterval(function(){
// 1.4 在定时器的回调函数中,打印当前li的文本
console.log(this.innerText);
// console.log(this); //-->window
}.bind(this), 1000);
// setInterval(function(){
// // 1.4 在定时器的回调函数中,打印当前li的文本
// console.log(this.innerText);
// // console.log(this); //-->window
// }.call(this), 1000);
}
小结:
1 当我们需要自己调用函数,并且要修改函数中this的指向的时候, 用call / apply 2 当我们不需要自己调用函数,要浏览器帮我们调用(事件处理函数, 定时器的回调函数),并且要修改函数中this的指向,我们就可以用bind.
继承
构造函数继承
我们工作中要经常创建多个具有相同属性的对象,所以经常要写构造函数.
那么构造函数创建的出来的对象该如何实现继承呢?
例:使用call构造函数的继承
function Person(name, age){
this.name = name;
this.age = age;
this.sayHello = function(){
console.log('hello, ' + '我是' + this.name );
}
}
function Student(name, age, score){
this.score = score;
Person.call(this, name, age);//借用构造函数
}
var stu = new Student('zs', 18, 100);
console.log(stu); //{score : 100, name : zs, age : 18}
stu.sayHello(); //hello, 我是zs
原型继承
function Person(name, age){
this.name = name;
this.age = age;
this.sayHello = function(){
console.log('hello, ' + '我是' + this.name );
}
}
function Student(score){
this.score = score;
}
Student.prototype = new Person();
var stu = new Student(100);
console.log(stu); //{score : 100}
stu.sayHello(); //hello, 我是undefined
实例化的时候没有传值,所以才会出现这种情况
我们发现,方法继承下来了,但是属性却没有继承下来
混合继承
借用构造函数 + 原型继承
functionPerson(name,age){
this.name=name;
this.age=age;
this.sayHello=function(){
console.log('hello, '+'我是'+this.name);
}
}
functionStudent(name,age,score){
this.score=score;
Person.call(this,name,age);
}
Student.prototype=newPerson();
varstu=newStudent('zs',18,100);
console.log(stu);//{score : 100, name : zs, age : 18}
stu.sayHello();//hello, 我是zs