javascript 作用域、this、闭包
1、作用域
- 全局作用域 window 全局声明的变量作用域为 window
var a = "javascript";
console.log(a);
在全局作用域下声明的变量和函数在页面的任何位置都可以访问到
var a = "javascript";
function log() {
console.log(a);
}
log(); // javascript
- 函数作用域
var b = "frontend";
function foo() {
var c = "course";
console.log(c);
console.log(b);
}
foo(); // course frontend
console.log(c); // c is not defined
// 在函数作用域中声明的变量,在外部无法访问,全局作用域下声明的变量 任何位置都可以访问
- 块级作用域 花括号包裹的部分形成块级作用域 只用 let const 关键字能形成块级作用域
if (true) {
let a = "block";
var b = "prop";
}
console.log(b); // prop
console.log(a); // ReferenceError: a is not defined
- 变量和函数声明提升 变量提升优先
// 1. 执行顺序
let a = "global";
console.log(a);
function course() {
let b = "js";
console.log(b);
session();
function session() {
let c = "this";
console.log(c);
teacher();
// 2. 函数提升 - 作用域之内
function teacher() {
// 2.1 let不支持提升
// 2.2 变量通过var支持提升,声明提升
// var e = undefined
console.log(e);
let d = "yunyin";
var e = "yy";
// e = 'yy';
console.log(d);
console.log("test1", b); // 3. 作用域向上查找,向下传递
}
}
}
course();
// 提升优先级
console.log("yunyin", yunyin); // f()
function yunyin() {
this.course = "js";
}
yunyin = "course";
// 变量优先 => 函数需要变量
2、this 上下文
this 是在执行时动态读取上下文决定的,而不是创建时
- 全局上执行的环境 => 函数表达式、匿名函数、嵌套函数
function foo() {
console.log(this); //
}
foo()// window
(function () {
console.log(this);
})();
function a() {
function b() {
console.log(this);
}
return b();
}
a();
- 隐式绑定 - this 指代调用堆栈的上一级 => 对象、数组等引用关系逻辑
function fn() {
console.log("隐式绑定", this);
}
var a = {
text: "a",
fn,
};
a.fn();
面试题
const foo = {
bar: 10,
fn: function () {
console.log(this.bar);
console.log(this);
},
};
// 取出
let fn1 = foo.fn;
// 独立执行
fn1();
// 追问1: 如何改变属性指向
const o1 = {
text: "o1",
fn: function () {
// 直接使用上下文 - 传统派活
console.log("o1fn", this);
return this.text;
},
};
const o2 = {
text: "o2",
fn: function () {
// 呼叫领导执行 —— 部门协作
return o1.fn();
},
};
const o3 = {
text: "o3",
fn: function () {
// 直接内部构造 —— 公共人
let fn = o1.fn;
return fn();
},
};
console.log("o1fn", o1.fn()); // this 为 o1
console.log("o2fn", o2.fn()); // this 为 o1
console.log("o3fn", o3.fn()); // this 为 window
改变this指向
// 1、人为干涉改变this - bind / call / apply
o1.fn.call(o2)
//2、不需要人为改变
const o1 = {
text: 'o1',
fn: function(){
// 直接使用上下文 - 传统派活
console.log('o1fn', this);
return this.text;
}
}
const o2 = {
text: 'o2',
fn: o1.fn
}
console.log('o2fn', o2.fn());
- 显示绑定 (bind | apply | call) 三者区别: 都可以改变this 第一个参数都是要更改的this,后面的参数中call 是逐个传入而apply 需要传一个数组,bind 传参和call 一致 但是bind 返回一个函数
面试题 手写apply bind
// apply
Function.prototype.myApply = function(contxt) {
// 获取要更改的this
context = context || window
// 获取执行函数 this 就是执行函数
context.fn = this
let result = arguments[1] ? context.fn(...arguments[1]) : context.fn()
delete context.fn
return result
}
function add(num1, num2) {
console.log(num1 + num2)
}
add.myApply(null, [1,2])
// bind
Function.prototype.myBind(context) {
context = context || window
var _this = this
let arg = Array.prototype.slice.call(arguments).slice(1)
return function() {
_this.myApply(context,arg)
}
}
//call
Function.prototype.myCall(context) {
context = context || window
context.fn = this
let args = [...arguments].slice(1)
let result = args.length > 0 ? context.fn(...args) : context.fn()
delete context.fn
return result
}
3、闭包
一个函数 A 中返回一个函数 B B 中引用 A 中声明的变量,从而导致函数 A 及变量无法释放 场景:
- 函数作为返回值
function main(){
let a = 'b'
return function() {
console.log(b)
}
}
const envelop = mail()
envelop()
- 函数作为参数的时候
function main(fn) {
let content
fn()
}
function fn1() {
content = 1
}
const envelop = main(fn1)
envelop()
- 函数嵌套
let counter = 0;
function outerFn() {
function innerFn() {
counter++;
console.log(counter);
}
return innerFn;
}
outerFn()();
- 立即执行函数 => js模块化的基石
let count = 0;
(function immediate(args) {
if (count === 0) {
let count = 1;
console.log(count);
}
})(args);
// 实现私有变量
function createStack() {
return {
items: [],
push(item) {
this.item.push(item);
}
}
}
const stack = {
items: [],
push: function() {}
}
function createStack() {
const items = [];
return {
push(item) {
items.push(item);
}
}
}