作用域和闭包
一、作用域和自由变量
作用域
某个变量合法的使用范围,分为如下三种:
- 全局作用域
最高级对象的一个属性,也就是window
- 函数作用域
函数定义时,就已经确定了函数的作用域链
function fn () {
var a = 100;
console.log(a);
}
fn()
console.log(a); // 别的地方使用就会报错,a is not defined
function fn () {
var a = 100;
console.log(a);
}
fn()
console.log(a); // 别的地方使用就会报错,a is not defined
- 块级作用域(ES6)
if (true) {
// 块级作用域
let a = 100;
console.log(a)
}
console.log(a); // 别的地方使用就会报错,a is not defined
if (true) {
// 块级作用域
let a = 100;
console.log(a)
}
console.log(a); // 别的地方使用就会报错,a is not defined
作用域链
自由变量:一个变量在当前作用域未定义,但被使用了 作用域链:自由变量的查找过程,就是作用域链
作用域链上找,先找自己的作用域有没有该变量, 没有向上找最近的父级作用域有没有该变量, 没有继续往上找,直到最顶层,全局,也就是由内向外查找,把自己当成一个js引擎。
如果在全局作用域都没找到,则报错:xxx is not defined
注意:
(所有)自由变量的查找,是在函数定义的地方,向上级作用域查找,不是在执行的地方
变量提升
使用var
声明的变量为函数作用域,会在函数执行之前,进行变量提升,也就是变量的声明和定义,但是不会赋值,赋值是在执行的时候进行的。
var x = 1;
function f() {
// 等同于先在这里做了声明 var x;
console.log(x); // undefined
// 然后再赋值 x = 2
var x = 2;
}
console.log(x); // 1
f();
var x = 1;
function f() {
// 等同于先在这里做了声明 var x;
console.log(x); // undefined
// 然后再赋值 x = 2
var x = 2;
}
console.log(x); // 1
f();
通用函数的声明也是如此
function f() {
x();
function x() {
console.log(1);
}
}
f();
function f() {
x();
function x() {
console.log(1);
}
}
f();
var和不用var的区别
- var定义了一个局部变量,会有变量提升
- 不用var则生成一个全局变量【无变量提升】
如果在声明之前去访问是不存在的Uncaught ReferenceError: xxx is not defined
但是会在执行过后生成一个全局的变量,也就是window.xxx
function f() {
console.log(window.xxx);
console.log(xxx); // Uncaught ReferenceError: xxx is not defined
xxx = 1;
console.log(xxx);
}
f()
console.log(window.y);
var y = 2
console.log(y);
function f() {
console.log(window.xxx);
console.log(xxx); // Uncaught ReferenceError: xxx is not defined
xxx = 1;
console.log(xxx);
}
f()
console.log(window.y);
var y = 2
console.log(y);
二、闭包
定义
闭包是作用域使用的特殊情况, 我们把函数执行形成私有上下文,来保护和保存私有变量机制称为闭包。 1、是一个函数, 2、可以访问其他作用域中的变量;
有两种表现
- 返回一个函数
function create () {
let a = 200;
return function () {
console.log(a); // 200
}
}
let a = 100
let fn = create();
fn();
function create () {
let a = 200;
return function () {
console.log(a); // 200
}
}
let a = 100
let fn = create();
fn();
- 作为参数被传递
function create(fn) {
let a = 100;
fn();
}
let a = 200;
function fn() {
console.log(a); // 200,不是100
}
create(fn)
function create(fn) {
let a = 100;
fn();
}
let a = 200;
function fn() {
console.log(a); // 200,不是100
}
create(fn)
可以看到,(所有)自由变量的查找,是在函数定义的地方,向上级作用域查找,不是在执行的地方
应用
- 模仿块级作用域
- 保护外部函数的变量 能够访问函数定义时所在的词法作用域(阻止其被回收)
- 封装私有化变量
- 创建模块
- 设计模式:单例模式,函数节流,函数防抖
隐藏数据
let createCache = function () {
let data = {};
return {
get: function (key) {
return data[key];
},
set: function (key, value) {
data[key] = value;
return data;
}
}
}
let cache = createCache();
cache.set('token', 'token');
cache.get('token');
let createCache = function () {
let data = {};
return {
get: function (key) {
return data[key];
},
set: function (key, value) {
data[key] = value;
return data;
}
}
}
let cache = createCache();
cache.set('token', 'token');
cache.get('token');
节流、防抖
节流 throttle
稀释函数的执行频率
function throttle(fn, delay) {
let lastTime = 0
return function () {
let now = Date.now()
if (now - lastTime > delay) {
fn.apply(this, arguments)
lastTime = now
}
}
}
function throttle(fn, delay) {
let lastTime = 0
return function () {
let now = Date.now()
if (now - lastTime > delay) {
fn.apply(this, arguments)
lastTime = now
}
}
}
防抖 debounce
固定时间不可再次触发
function debounce(fn, delay) {
let timeout = null
return function () {
timeout && clearTimeout(timeout)
timeout = setTimeout(() => {
fn.apply(this, arguments)
}, delay)
}
}
function debounce(fn, delay) {
let timeout = null
return function () {
timeout && clearTimeout(timeout)
timeout = setTimeout(() => {
fn.apply(this, arguments)
}, delay)
}
}
实现bind函数
// 模拟bind的实现
Function.prototype.bind1 = function () {
// 将参数拆解为数组
const args = Array.prototype.slice.call(arguments);
// 获取this,并从数组剔除
const _this = args.shift();
// 原来的fn,fn.bind(...)的fn1
const self = this;
// 返回一个函数
return function () {
return self.apply(_this, args);
}
};
function fn () {
console.log(this);
console.log(arguments);
return 'this is a fn';
}
let fn1 = fn.bind1({
name: 'fn',
}, 10, 20);
fn1();
// 模拟bind的实现
Function.prototype.bind1 = function () {
// 将参数拆解为数组
const args = Array.prototype.slice.call(arguments);
// 获取this,并从数组剔除
const _this = args.shift();
// 原来的fn,fn.bind(...)的fn1
const self = this;
// 返回一个函数
return function () {
return self.apply(_this, args);
}
};
function fn () {
console.log(this);
console.log(arguments);
return 'this is a fn';
}
let fn1 = fn.bind1({
name: 'fn',
}, 10, 20);
fn1();
缺点
变量会常驻内存,可能造成内存泄露
因为内存得不到及时的释放,可以人为的去置为null
js垃圾回收机制
- 标记使用法
当这个函数结束,变量不再使用就得到了释放;
- 引用计数法
在一定时间内不再使用;
三、this
定义
函数执行所在的环境变量,并且是可以改变的。
this是执行上下文中很重要的一个组成部分,
同一个函数调用方式不同,得到的this值也不同,如下:
function showThis(){
console.log(this);
}
var o = {
showThis: showThis
}
showThis(); // global
o.showThis(); // o
function showThis(){
console.log(this);
}
var o = {
showThis: showThis
}
showThis(); // global
o.showThis(); // o
普通函数的this是由他调用他所使用的引用来决定的
注意
调用函数时使用的引用,决定了函数执行时刻的 this
值
场景
1、作为普通函数
const f1 = function() {
console.log(this);
};
f1(); // window
const f1 = function() {
console.log(this);
};
f1(); // window
2、作为对象方法被调用
function showThis() {
console.log(this);
}
var o = {
showThis: showThis
}
showThis(); // global
o.showThis(); // 对象本身
function showThis() {
console.log(this);
}
var o = {
showThis: showThis
}
showThis(); // global
o.showThis(); // 对象本身
3、class方法中
class Person {
constructor () {
this.duuty = 'person';
}
say () {
console.log(this); // person实例
}
}
let person = new Person();
person.say();
class Person {
constructor () {
this.duuty = 'person';
}
say () {
console.log(this); // person实例
}
}
let person = new Person();
person.say();
4、构造函数
let Person = function () {
this.duty = 'person';
}
Person.prototype.say = function () {
console.log(this); // person实例
}
let person = new Person();
person.say();
let Person = function () {
this.duty = 'person';
}
Person.prototype.say = function () {
console.log(this); // person实例
}
let person = new Person();
person.say();
5、箭头函数
箭头函数,this
的取值,是在函数定义的时候确定的,
是在定义时所在的对象,也就是上级作用域中的this
取值
const showThis = () => {
console.log(this);
}
var o = {
showThis: showThis
}
showThis(); // global
o.showThis(); // global
const showThis = () => {
console.log(this);
}
var o = {
showThis: showThis
}
showThis(); // global
o.showThis(); // global
我们看到,改为箭头函数后,不论用什么引用来调用它,都不影响它的 this 值。
操作this的内置函数
apply和call
作用是在特定的作用域中调用函数, 设置函数体内this
的对象,真正的目的其实是扩容函数的作用域。
function foo(a, b, c) {
console.log(this);
console.log(a, b, c);
}
foo.call({}, 1, 2, 3); // 参数依次传递
foo.apply({}, [1, 2, 3]); // 参数放在数组
function foo(a, b, c) {
console.log(this);
console.log(a, b, c);
}
foo.call({}, 1, 2, 3); // 参数依次传递
foo.apply({}, [1, 2, 3]); // 参数放在数组
这里 call 和 apply 作用是一样的,只是传参方式有区别。
bind
此外,还有 Function.prototype.bind
它可以生成一个绑定过的函数【返回一个副本】,这个函数的 this
值固定了参数:
function foo(a, b, c){
console.log(this);
console.log(a, b, c);
}
foo.bind({}, 1, 2, 3)();
function foo(a, b, c){
console.log(this);
console.log(a, b, c);
}
foo.bind({}, 1, 2, 3)();
手写bind函数
// 模拟bind的实现
Function.prototype.bind1 = function () {
// 将参数拆解为数组
const args = Array.prototype.slice.call(arguments);
// 获取this,并从数组剔除
const _this = args.shift();
// 原来的fn
const self = this;
// 返回一个函数
return function () {
return self.apply(_this, args);
}
};
function fn () {
console.log(this);
console.log(arguments);
return 'this is a fn';
}
let fn1 = fn.bind1({
name: 'fn',
}, 10, 20);
let res = fn1();
console.log(res);
// 模拟bind的实现
Function.prototype.bind1 = function () {
// 将参数拆解为数组
const args = Array.prototype.slice.call(arguments);
// 获取this,并从数组剔除
const _this = args.shift();
// 原来的fn
const self = this;
// 返回一个函数
return function () {
return self.apply(_this, args);
}
};
function fn () {
console.log(this);
console.log(arguments);
return 'this is a fn';
}
let fn1 = fn.bind1({
name: 'fn',
}, 10, 20);
let res = fn1();
console.log(res);
bind、call和apply的调用效果
let Fruit = function () {
console.log(this);
};
Fruit(); // window
Fruit.prototype = {
color: 'green',
say: function () {
console.log(`my color is ${this.color}`)
}
};
var apple = new Fruit();
apple.say();
let banana = {
color: 'yellow'
};
apple.say.bind(banana)(); // 返回一个副本
apple.say.call(banana); // yellow
apple.say.apply(banana); // yellow
let Fruit = function () {
console.log(this);
};
Fruit(); // window
Fruit.prototype = {
color: 'green',
say: function () {
console.log(`my color is ${this.color}`)
}
};
var apple = new Fruit();
apple.say();
let banana = {
color: 'yellow'
};
apple.say.bind(banana)(); // 返回一个副本
apple.say.call(banana); // yellow
apple.say.apply(banana); // yellow