# 作用域和闭包
- 全局作用域
- 函数作用域
- 块级作用域(ES6)
# 作用域
作用域就是一个独立的地盘,让变量不会外泄、暴露出去。也就是说作用域的用处就是隔离变量,不同作用域下同名变量不会有冲突。 作用域一般有两种 全局作用域和函数作用域。
let a = 0;
function fn1() {
let a1 = 10;
function fn2(){
let a1 = 9;
let a2 =200;
// a2 a1 a 在使用时会在作用域内,从离调用最近的地方到离调用远的作用域内找。
console.log(a2 + a1 + a)
}
fn2()
}
fn1();
- let定义的是块级作用域,let之前使用会出现暂时性死区。
- const用来定义常量,且必须要有初始值。
- var定义变量,会存在变量提升。
- let 在全局环境中定义的指,是不会被挂在window对象上的
//例题1
for (var i =0 ;i<10;i++){
console.log(i)// 0 1 2 3 4 5 6 7 8 9
setTimeout(function () {
console.log(i) // 10 10 10 10 10 10 10 10 10 10
})
}
//例题2
for (var i =0 ;i<10;i++){
}
console.log(i)// 10
//例题3
for (let i =0 ;i<=10;i++){
console.log(i) // 0 1 2 3 4 5 6 7 8 9 10
setTimeout(function () {
console.log(i) // 0 1 2 3 4 5 6 7 8 9 10
})
}
//例题4
for (let i =0 ;i<=10;i++){
}
console.log(i) // i is not defind
//例题5
//因为let在for循环体外定义,所以存在作用域问题
let i
for ( i = 1; i<=3; i++ ){
setTimeout(function () {
console.log(i) // 4 4 4
},0)
}
//例题6
let i
for ( i = 1; i<=3; i++ ){
console.log(i)// 1 2 3
}
console.log(i) //4
//例题7
let a = 1;
let obj = {
a:2,
add:()=>{
setTimeout(()=>{
console.log(this);//window
console.log(this.a);//undefind
},0)
}
}
obj.add();
# 闭包
利用闭包可以突破作用链域,将函数内部的变量和方法传递到外部。 两种表现(既闭包就是函数定义的地方和函数执行的地方不一致) 因为 js 是有作用域的,函数也拥有自己的作用域,如果我们要想访问到作用域内的变量,需要通过闭包。
- 函数作为参数被传递
- 函数作为返回值被返回
闭包内的自由变量寻找的时候,是在函数定义的地方向上级作用域去寻找。
//函数作为返回值被返回
function fn1(){
let a = 100;
return function(){
console.log(a)
}
}
let a = 200
let result = fn1();
console.log(result());// 打印结果是100
//函数作为参数被传递
function fn2(fn){
let a = 100
fn()//打印结果是200
}
let a = 200
function fn(){
console.log(a)
}
闭包的应用
- 闭包隐秘数据只提供 API,我们通过调用闭包内提供的 API 来访问函数函数内的变量
function fn(){
const data = {} //闭包内的数据,外部不可直接访问
return{
get(key){
return data[key]
},
set(key,val){
data[key] = val
}
}
}
const c = fn();
c.set('name','kira')
var x = 0;
var foo = {
x:1,
bar:function () {
console.log(this.x);
var that = this;
return function () {
console.log(this.x)
console.log(that.x)
}
}
}
foo.bar() // 1 foo调用bar,this指向foo
foo.bar()() // this: 0, that: 1 由于return一个匿名函数,形成了闭包,在匿名函数内 this指向window,匿名函数外this指向缓存的obj对象。
var x = 0;
var bar:function () {
var n = 999;
return function () {
return n;
}
}
var outer = bar();
outer() //n为999 函数在哪定义,自由变量在哪取值。
i 是全局作用域
let a,i
for(i=1,i<10,i++){
a = document.createElement('a')
a.innerHTML = i +'<br>'
a.addEventListener('click',function(){
alert(i) // 十个标签弹出的结果都是 10,因为遍历先执行,每个a标签被click时,i早就变成来10,因为i是全局作用域
})
}
document.body.appendChild(a)
改造
i改为块级作用域,这样每次循环都会产生一个块级作用域。
let a
for(let =1,i<10,i++){
a = document.createElement('a')
a.innerHTML = i +'<br>'
a.addEventListener('click',function(){
alert(i) // 十个标签弹出的结果都是 1,2,3,4.....
})
}
# this
this 取出的值是在函数执行的时候定义的 this 的使用场景
- 作为普通函数(this 指向 window)
- 使用 call apply bind(传入什么 this 就指向什么)
- 作为对象方法被调用(this 指对象本身)
- 在 class 方法中调用(this 指当前实例)
- 箭头函数(箭头函数的取值是取它上级作用域的值)
f.__proto__ == Function.prototype // true 所以 bind call apply 都在 Function.prototype 实现的。
function fn1(){
console.log(this);
}
fn1(); //打印结果window
//通过call改变了this的指向
fn1.call({x:100}) //打印结果 {x:100}
//通过bind改变了this的指向
const fn1 = fn1.bind({x;200})
fn2() //{x:200}
const xx = {
name:'zhangsan',
play(){
//箭头函数中的this是取它上级作用域的值
setTimeOut(()=>{
console.log(this)//this指向xx对象
})
}
play(){
setTimeOut(function(){
console.log(this)//this指向window
})
}
}
# bind实现
bind 的使用
function f() {
console.log('执行了')
}
Function.prototype.bind1 = function () {
const self = this; //指向f
//参数转化为数组
let args = Array.prototype.slice.call(arguments);
//把第一项,要执向的对象提取出来
const start = args.shift();
console.log(args);
//bind的执行是返回一个函数
return function () {
return self.apply(start,args)
}
};
const fn2 = f.bind1({},1,2,4);
//接收返回的函数,并执行
const res = fn2();
console.log(res);
# call实现
- 1将函数设为对象的属性
- 2执行该函数
- 3删除该函数
//使用call
let obj = {name:"kira"}
function fn (){ console.log(this.name)}
fn.(obj);//打印kira
//把fn挂载到obj上
let obj = {
name:"kira",
fn:function(){
console.log(this.name)
}
}
obj.fn();
Function.prototype.call1 = function(){
let args = Array.prototype.slice.call(arguments);
let context = args.shift();
context.fn = this;
const result = context.fn(...args)
delete context.fn;
return result;
};
function fn(content){
console.log(this.name)
}
# apply实现
Function.prototype.apply1 = function(context,args){
context.fn = this;
const result = context.fn(...args);
delete context.fn;
return result;
};
function fn(content){
console.log(this.name)
}
let obj = {name:'kira'}
fn.apply1(obj,[1,2,3]);
练习题
变换1
var name = 'global';
var obj = {
name:'obj',
method:function() {
this.name = 'local';
//console.log(此时this指obj,obj.name = 'local')
return function() {
return this.name;
}
}
}
console.log(obj.method());
变换2
var name = 'global';
var obj = {
name:'obj',
method:function() {
this.name = 'local';
console.log(此时this指obj,obj.name = 'local')
return function() {
//return function this指向window
return this.name;
}
}
}
console.log(obj.method()());//
var obj = {
name: 'obj',
hello() {
console.log(this.name)
},
}
var h = obj.hello
h()//此时this指向的window
console.log(h)//打印结果underfund
var obj = {
name:'obj',
hi(){
return () => {
console.log(this.name)
}
},
}
var h2 = obj.hi()
h2()//箭头函数没有自己的this指向
//变换3
function foo(){
var b = 2;
//打印结果124
console.log(b + this.a)
}
function foo1(){
var a = 4
// 2
//this指向{a:122}
console.log(a + this.a)//126
//this指向{a:122}
foo.call(this)
}
foo1.call({a:122})