闭包作用域THISOOP
Posted smilestudio
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了闭包作用域THISOOP相关的知识,希望对你有一定的参考价值。
1.作用域
全局作用域
供代码执行的运行环境即全局作用域
在浏览器打开页面的同时,也会形成两个虚拟的内存;
一个是栈内存,一个堆内存;
栈内存:1.提供代码运行环境2.存储基本数据类型值
堆内存:存储引用数据类型值;
//在全局作用域形成以后,在这个全局作用域会默认提供最大的window对象;
//全局变量:在全局作用域下定义的变量叫全局变量;
私有作用域
私有变量:在私有作用域下定义的变量
- 1.当前作用域有没有被var;
- 2.有没有被function
- 3.是否是形参;
//var a=100;
//function fn(){
////在函数私有作用域中定义的变量,不会给window新增键值对;
////var b=10;
//var a=99;
//console.1og(a);//100函数里面可以访问函数外面的变量
//}
//fn();
//console.1og(b);
//函数执行会形成私有的作用域,会保护里面的私有变量不受外界的干扰;
let:let声明的变量不能给window新增键值对;
const声明的常量也不能新增键值对:
//const e=1;
//let d=1;
//console.log(d);
//1.私有作用域是在全局作用域下形成创建的;在全局作用域中包含私有的作用域;
//2.全局作用域不能访问私有作用域下的私有变量,但是私有作用域能够访问全局作用下变量
//3.如果私有作用域存在该私有变量,那么就不再向外获取
function fn(){
//私有作用域:是给函数体中的代码提供代码的运行环境的;
//和全局作用域,
///var a=1e;
a=11;
console.1og(10)
}
fn();
console.1og(a);
变量提升1
debugger;添加断点
过程:
//变量提升:在当前作用域下,代码执行之前,会首先把带var的和function进行提前声明,
带var的只声明,不赋值,
但是function不仅声明,而且还要赋值(定义);
那么这种机制就是变量提升的机制;先对function变量提升
//声明:通知了当前作用域有这么一个变量;
console.log(a);//undefined
console.log(b)
var a=100;b=99;
- 函数执行过程
1生成一个私有作用域
2形参赋值
3变量提升
变量提升2
变量提升:在当前作用域下,代码执行之前,会首先把带var的和function进行提前声明,
带var的只声明,不赋值,
function不仅声明,而且还要赋值(定义);
- (注意:当如果function在块级作用域内时,只声明,不赋值了)
- 等号的右边不进行变量提升,变量提升只发生在等号的左边;
那么这种机制就是变量提升的机制;先对function变量提升
- 3.return下面的代码要进行变量提升:return后面的代码时不进行变量提升的:
function fn(){
console.1og(f);
return function f(){}
console.log(f)
]
- 4 自执行函数
如果fn的属性值时一个自执行函数,那么当代码以键值对存储的时候(当代码执行到这一行时,自执行函数就会运行),并且把自执行函数的执行结果赋值给属性名fn
var obj={
fn:(function(){
console.1og(100);
//;
return function(){
})()
console.1og(200)
obj.fn()
- 5.如果变量名和函数名重名,不再进行重复声明,但是要重新赋值;
fn();//4
function fn(){
console.1og(1);
function fn(){
console.1og(2);
fn();//4
function fn(){
console.1og(3);
fn=100;
function fn(){
console.1og(4);
fn();报错
- 6.自执行函数
当代码执行到这一行时,先开辟一个空间地址,然后再执行:
var fn=function(){};
(function(){})();
- 7.let声明的变量不进行变量提升;
在代码执行之前,浏览器余先过滤当前作用域下的let声明的变量:
let声明的变量不允许重名:(在同一个作用域)
console.log(num);
1et num=18e;
console.1og(num);
上级作用域
- 全局作用域;私有作用域,块级作用域;
- 当获取变量所存储的值时,先找自己有没有,如果自己没有,那么向上一级作用域查找,如果上一级也没有,那么会继续向上查找,直到找到全局作用域,如果全局作用域也没有,会报错;这样一级一级向上查找形成一个作用域链;
- 函数的上一级作用域跟函数在哪定义有关系,跟函数在哪执行无关:
var total=10;
function sum(){
var total=100;
return function(){
console.log(total);//undefined 100
//var total=1000;
}
var f=sum();//f接受到了sum返回的小函数:
f();
堆栈内存回收
-
堆内存回收:
-
Gc垃圾回收机制;在浏览器内置了一个gc程序,这个程序会每隔一段时间执行一次;
1.标记法:浏览器每隔一段时间要对所有的空间地址类型进行检测,检查该地址是否被占用:如果没有占用,那么久立即回收掉
2.IE计数法:每当到间地址占用一次,该地址标识就会默认+1;如果不再占用,就会-1:如果标记计数为e:立即回收:
/栈内存回收==>作用域销毁; -
栈内存回收==>作用域销毁;
立即销毁【1】不销毁的作用域
1.返回一个引用的数据类型值
2.返回的值需要被外界接受
3 如果当前作用域有个空间地址,被函数体外的东西占用着,那么这个栈内存就不能销毁; olis占用了这个空间地址
function fn(){
olis[i]. onclick=function(){
}
}
fn()
2. THIS
1.在全局作用域,this指向window;this 和window是同一块空间地址:
console.log(this);//window window.
alert()
this.alert();
2.给元素的事件绑定的函数中的this,指向了当前被点击的那个元素:
box.onclick=function(){
//this:对象:
console.dir(this)}
3.普通函数执行:看函数执行前有没有“.”,如果要是没有,那么函数中的this指向window,
如果有“.”,那么点前面是谁,this就指向谁:跟在哪执行有关
var obj={
m:1,
fn:function(){
console.1og(this);
}//this就指向了obj;
var f=obj.fn;
console.1og(f==obj.fn);
obj.fn();
f();
4.自执行函数中this永远指向window:
(function(){
console.log(this);//window
})();
5.回调函数中的this一般都指向window;
6.构造函数中的this指向实例
7.calll,apply,bind 可以改变this指向:
calll,apply,bind 语法
基于call/apply/bind可以改变函数中this的指向(强行改变)
Function.prototype={
call
apply
bind
}
- call/apply
第一个参数就是改变的THIS指向,写谁就是谁(特殊:非严格模式下,传递null/undefined指向的也是window)
唯一区别:执行函数,传递的参数方式有区别,call是一个个的传递,apply是把需要传递的参数放到数组中整体传递
func.call([context],10,20)
func.apply([context],[10,20]) - bind
call/apply都是改变this的同时直接把函数执行了,而bind不是立即执行函数,属于预先改变this和传递一些内容 =>"柯理化"
call的练习题
function fn1() {
console.log(1);
}
function fn2() {
console.log(2);
}
/* function call(context = window, ...args) {
context.$fn = this;
let result = context.$fn(...args);
delete context.$fn;
return result;
} => AAAFFF000*/
fn1.call(fn2); //=>执行的是FN1 =>1
fn1.call.call(fn2); //=>执行的是Fn2 =>2
Function.prototype.call(fn1); //空
Function.prototype.call.call(fn1); //=>1
总结:当执行一个call,则执行call前面的函数,但是this指向括号中
当执行两个及两个以上call,则执行括号中的函数
- this练习题
var a=1; 3
var obj1={
a:0, 1 2
fn1:(function(a){
a=1 2 3 4
this.a=a;
a++; 2
return function(){
this //obj //Windows
this.a=a++; 3
console.1og(a) 3 4
}
})(a)
};
obj1.fn1();
var fnl=objl.fn1;
fn1();
console.1og(a); 3
console.1og(obj1.a); 2
var n=2: 4 8
var obj{
n:3, 6
fn:(function(n){
n=2 4
n*=2;
this.n+=2;
var n=5: 6 7
return function(m){
m=3
this.n*=2;
console.1og(m+(++n)); 9 10
])(n)
var fn=obj.fn:
fn(3);
obj.fn(3);
console.1og(n,obj.n): 8 6
var num=1; 2 4
var obj={
num:2, 4
fn:(function(){
this.num*=2;
num +=3; NAN 1 3
var num=1; 1
return function(){
num+=2; 3 4 6 7
this.num+=2
console.1og(++num); 4 7
})()
};
var f= obj.fn;
f();
obj. fn();
console.1og(window. num, obj. num); 4 4
数据类型监测
-
检测数据类型1:typeof
返回结果都是字符串
字符串中包含了对应的数据类型 "number"/"string"/"boolean"/"undefined"/"symbol"/"object"/"function"
【局限性】
* typeof null => "object" null不是对象,它是空对象指针
* 检测数据或者正则等特殊的对象,返回结果都是"object",所以无法基于typeof判断是数组还是正则 -
检测数据类型2/3:instanceof / constructor 类似的
检测某个实例是否属于这个类
他检测的底层机制:所有出现在其原型链上的类,检测结果都是TRUE
【局限性】
* 由于可以基于__proto__或者prototype改动原型链的动向,所以基于instanceof检测出来的结果并不一定是准确的
* 基本数据类型的值,连对象都不是,更没有__proto__,虽说也是所属类的实例,在JS中也可以调取所属类原型上的方法,但是instanceof是不认的
arr.instanceof == Array
arr.constructor==Array -
检测数据类型4:Object.prototype.toString.call([value]) /
({}).toString.call([value]) 常用方法
- 格式:"[object 所属类信息]"
[object Object/Array/RegExp/Date/Function/Null/Undefined/Number/String/Boolean/Symbol...]
* 这种方式基本上没有什么局限性,是检测数据类型最准确的方式
({}).toString.call("stknf")
3.OOP
面向对象编程
单例设计模式
1.表现形式
var obj={
XXx:XXX,
}
2.作用
=>把描述同一件事务的属性和特征进行“分组、归类”(存储在同一个堆内存空间中),因此避免了全局变量之间的冲突和污染
- 在单例设计模型中,OBJ不仅仅是对象名,它被称为”命名空间“[NameSpace]",把描述事务的属性存放到命名空间中,多个命名空间是独立分开的,互不冲突I
3.单例设计模式命名的由来
*=>每一个命名空间都是Js 中object这个内置基类的实例,而实例之间是相互独立互不干扰的,所以我们称它为单例:”
高级单例模式
【1】.在给命名空间赋值的时候,不是直接赋值一个对象,而是先执行匿名函数,形成一个私有作用域AA(不销毁的栈内存),在AA中创建一个堆内存,把堆内存地址赋值给命名空间
【2】这种模式的好处:我们完全可以在AA中创造很多内容(变量or函数,哪些需要供外面调取使用的,我们暴露到返回的对象中(模块化实现的一种思想)
var nameSpace=(function(){
function fn(){
}
return {
fn:fn
}
})()
单例设计模式应用
模块化开发
*模块化开发
*1.团风协作开发的时候,会把产品按照功能板块进行划分,每一个功能板块有专人负责开发
*2.把各个版块之间公用的部份进行提取封装,后期在想实现这些功能,直接的调。
取引用即可(模块封装)
var weatherRender=(function(){
var fn=function(){
};
return{
init:function(){
fn();//=>调取自己模块中的方法直接调取使用即可
skipRender.fn();//=>调取别人模块中的方法
})();
weatherRender.init();
工厂模式
工厂模式(Factory Pattern)
1.把实现相同功能的代码进行“封装”,以此来实现“批量生产”(后期要实现这个功能,我们只需要执行函数即可)
2.“低剩合高内聚”:减少页面中的冗余代码,提高代码的重复使用到
function createPerson(name, age){
var obj={};
obj. name=name;
obj. age=age;
return obj;
}
面向对象编程
面向对象编程,需要我们掌握:“对象、类、实例”的概念
对象:万物对象
类:对象的具体细分(按照功能特点进行分类:大类、小类)
实例:类中具体的一个事物(拿出类别中的具体一个实例进行研究,那当前类别下的其它实例也具备这些特点和特征)
js本身基于面向对象编程
构造函数
基于构造函数创建自定义类(constructor)
-
在普通函数执行的基础上"new xxx()”,这样就不是普通函数。执行了,而是构造函数执行,当前的函数名称之为“类名”,接收的返回结果是当前类的一个实例
-
自己创造类名,最好第一个单词大写。
function Fn(){
}
var f =new Fn()
实例之间是独立分开,互不干扰
-
JS创建值两种方式
1.字面量表达式
2.构造函数模式
var obj1={ }
var obj2=new Object()var obj={};//->字面量方式
var obi=new Object();//->构造函数模式
Fn() => 普通函数执行
=>不管是哪一种方式创造出来的都是object类的实例,而实例之间
是独立分开的,所以 var xxx={)这种模式就是Js中的单例模式 -
基本数据类型基于两种不同的模式创建出来的值是不一样的
基于字面量方式创建出来的值是 基本类型值
基于构造函数创建出来的值是 引用类型
NUM2是数字类的实例,NUM1也是数字类的实例,它只是JS表达数字的方式之一,都可以使用数字类提供的属性和方法
var numl=12;
var num2=new Number(12);
console. log(typeof numl);//=>"number"
console. log(typeof num2);//->"object"
-
普通函数执行
1.形成一个私有的作用域
2.形参赋值
3.变量提升
4.代码执行
5.栈内存释放问题 -
构造函数执行原理
构造函数执行的细节问题
- 自己写RETURN?
1.return是的一个基本值,返回的结果依然是类的实例,没有受到影响
2.如果返回的是引用值,则会把默认返回的实例覆盖,此时接收到的结果就不在是当前类的实例了
=>构造函数执行的时候,尽最减少RETURN的使用,防止覆盖实例 - 在构造是不用参数,可以省略小阔号,意思也是创建实例
- instanceof 检测某一个实例是否属于这个类
console. log(f instanceof Fn);//=>TRUE
console. log(f instanceof Array);//=>FALSE
- in
检测当前对象是否存在某个属性(不管当前这个属性是对象的私有属性还是公有属性, 只要有 结果就是true)
console. log(‘m‘ in f)://=>TRUE
console. log(‘n‘ in f);//=>FALSE
- hasOwnProperty
检测当前属性是否为对象的私有属性(不仅要有这个属性,而且必须还是私有的才可以)
console. log(f. hasOwnProperty(‘m‘));//=>true
console. log(f. hasOwnProperty(‘n‘));
原型链设计模式
原型 prototype
【函数]
普通函数、类(所有的类:内置类、自己创建的类)
【对象]
普通对象、数组、正则、Math、arguments..…
实例是对象类型的(除了基本类型的字面量创建的值)
prototype的值也是对象类型的
函数也是对象类型的
- 所有的函数数据类型都天生自带一个属性:prototype(原型),这个属性的值是一个对象,浏览器会默认给开辟一个堆内存
- .在浏览器给prototype开辟的堆内存中有一个天生自带的属性:constructor,这个属性存储的值是当前函数本身
- 每一个对象都有一个_proto_ 的属性,这个属性指向当前实例所属类的prototype(如果不能确定它是谁实例,都是Object的实例)
原型链查找方法原理
【原型链:】
它是一种基于_proto向上查找的机制。当我们操作实例的某个属性或者方法的时候,首先找自己空间中私有的属性或者方法
1.找到了,则结束查找,使用自己私有的即可
2.没有找到,则基于_proto找所属类的prototype,如果找到就用这个公有的,如果没找到,基于prototype上的_proto继续向上查找,一直找到Object.prototype的原型为止,如果在没有,操作的属性或者方法不存在 返回undefined
【举例】
### 类的继承和多态
-
类的多态:重载和重写 (无)
JAVA中重载:函数名相同,但是传参类型、数量不同或者返回值不一样,这相当与把一个函数重载了 (JS中没有类似于后台语言中的重载机制:JS中的重载只的是同一个方法,根据传参不同,实现不同的业务逻辑) 重写:子类重写父类上的方法
-
继承:子类继承父类中的属性和方法(JS中的继承机制和其它后台语言是不一样的,有自己的独特处理方式)
-
方案一:原型继承 (B子类 => A父类)
子类的原型指向父类的一个实例
function A() {
this.x = 100;
}
A.prototype.getX = function getX() {
console.log(this.x);
};
function B() {
this.y = 200;
}
B.prototype.sum=function(){}
B.prototype = new A();
B.prototype.getY = function getY() {
console.log(this.y);
};
let b = new B;
- CALL继承:把父类当做普通函数执行,让其执行的时候,方法中的this变为子类的实例即可
* 【特点】
* 1.只能继承父类中的私有属性(继承的私有属性赋值给子类实例的私有属性),而且是类似拷贝过来一份,而不是链式查找
* 2.因为只是把父类当做普通的方法执行,所以父类原型上的公有属性方法无法被继承过来
function A() {
this.x = 100;
}
A.prototype.getX = function getX() {
console.log(this.x);
};
function B() {
//CALL继承
A.call(this); //=>this.x = 100; b.x=100;
this.y = 200;
}
B.prototype.getY = function getY() {
console.log(this.y);
};
let b = new B;
console.log(b);
- 寄生组合继承:CALL继承+变异版的原型继承共同完成的
* CALL继承实现:私有到私有
* 原型继承实现:公有到公有
function A() {
this.x = 100;
}
A.prototype.getX = function getX() {
console.log(this.x);
};
function B() {
A.call(this);
this.y = 200;
}
//=>Object.create(OBJ) 创建一个空对象,让其__proto__指向OBJ(把OBJ作为空对象的原型)
B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;
B.prototype.getY = function getY() {
console.log(this.y);
};
let b = new B();
console.log(b);
* ES6创建类用class
class A {
constructor() {
this.x = 100;
}
getX() {
console.log(this.x);
}
}
//=>extends继承和寄生组合继承基本类似
class B extends A {
constructor() {
super(100); //=>一但使用extends实现继承,只要自己写了constructor,就必须写super <=> A.call(this,100)
this.y = 200;
}
getY() {
console.log(this.y);
}
}
以上是关于闭包作用域THISOOP的主要内容,如果未能解决你的问题,请参考以下文章