JavaScript扎马步——作用域、闭包、this指向
笔者(吃饺子不吃馅)在平时做项目当中以及日常工作中发现js基础不够牢固,痛定思痛,决定好好复习一下js基础,作为一名前端开发人员,在js上花费再多时间我认为都是值得的
一、作用域
函数作用域
函数作用域的含义是指,属于这个函数的全部变量都可以在整个函数的范围内使用及复用。 简而言之就是函数内部声明的变量只能在函数内部访问,在外部就访问不到了。
function demo() {
var a = 1;
console.log(a);
}
demo(); // 1
console.log(a); // 报错
很容易理解不再详细介绍,这么设计的目的应该就是为了安全性以及变量之间避免同名冲突。
由函数作用域派生出两点关于函数的点:
1、立即执行函数
// 立即执行
(function demo() {
var a = 1;
console.log(a);
})()
(function demo() {
var a = 1;
console.log(a);
}())
2、函数表达式与函数声明
// 函数声明
function demo() {}
// 函数表达式
let demo = function () {}
函数声明方式创建的函数会进行提示,在声明之前也可以使用
demo() // 1
function demo() { console.log(1) }
函数表达式在函数表达式赋值之前就是一个简单的变量定义,用let声明则执行demo的时候根本没有变量,用var声明则var demo;但是不是函数也会报错
demo()
var demo = function () { console.log(1) }
VM2198:1 Uncaught TypeError: demo is not a function
demo()
let demo = function () { console.log(1) }
VM2276:1 Uncaught ReferenceError: demo is not defined
函数声明和变量声明都会被提升。但是函数声明会比变量声明先一步。重复的 var 声明会被忽略掉,但出现在后面的函数声明还是可以覆盖前面的
demo(); // 1
var demo;
function demo() { console.log( 1 ); }
demo = function() { console.log( 2 ); };
块级作用域
说白了就是tmd花括号
{
let a =1
}
console.log(a)
VM2884:1 Uncaught ReferenceError: a is not defined
// 执行完花括号 a就被销毁了
全局作用域
简单理解最外层的就是全局作用域,在最外层声明变量后就会赋值给全局作用域
var a = 1;
// a就是全局作用域的打印window.a与a一样
词法作用域
不用整那么多那一理解的概念,就是在定义的时候的作用域,规则就是一级一级向上匹配
二、闭包有那么复杂?
闭包的概念以及解释有很多,还记得最开始学习前端的时候觉得闭包很迷,其实仔细理解一下,这东西按照一定思路来理解就行,没必要非得纠结于闭包的概念。
看下面的代码这tm经常使用吧!
function demo() {
var a = 1;
function dayin() {
console.log(a)
}
return dayin;
}
let res = demo()
res()
本来在demo函数作用域中的变量a只能在demo中访问,但是res在外面也可以访问了!这就是闭包。
demo执行后demo作用域中的数据应该被销毁但是由于res的引用没有销毁。
闭包:本来应该销毁但是没有销毁
三、this
this是动态的,指代当前运行的上下文。this指向调用它的上下文,this会按照一定规则进行绑定。
默认绑定
例子:
function demo() {
var a = 2;
this.demo1();
}
function demo1() {
console.log(this.a);
}
demo();// a is not define
为啥报错? 这么写应该会好理解一些
function demo() {
var a = 2;
this.demo1();
}
function demo1() {
console.log(this.a);
}
window.demo();
执行this.demo1()的时候相当于执行window.demo1(),自然就找不到a。如果使用严格模式(strict mode),那么全局对象将无法使用默认绑定。
隐式绑定
例子:
function demo() {
console.log(this.a);
}
var obj2 = {
a: '2',
demo: demo,
};
var obj1 = {
a: '1',
obj2: obj2,
};
obj1.obj2.demo(); // '2'
好吧,其实简单理解为obj2最后调用的,所以this指向obj2就行。管他什么隐式默认显示的绑定。 但是隐式绑定很容易出现隐式丢失的情况: (传参、赋值等)
function demo() {
console.log(this.a);
}
var obj = {
a: 520,
demo: demo,
};
var res = obj.demo;
res(); // undefined
相当于window.res()而不是window.obj.res()
-------------------------------------------
function demo() {
console.log(this.a);
}
function runFun(fn) {
fn();
}
var obj = {
a: 520,
demo: demo,
};
runFun(obj.demo);// undefined
参数赋值的时候会导致隐式丢失
相当于runFun(window.demo)
显示绑定
call、apply、bind
call( thisValue , arg1, arg2, ... )
var color = "red";
var obj = {color: "blue"};
function sayColor(){
console.log(this.color);
}
sayColor() // red
sayColor.call(obj) // blue
apply( thisValue , [arg1, arg2, ...] )
var color = "red";
var obj = {color: "blue"};
function sayColor(){
console.log(this.color);
}
sayColor() // red
sayColor.apply(obj) // blue
bind( thisValue , arg1, arg2, ... )()
var color = "red";
var obj = {color: "blue"};
function sayColor(){
console.log(this.color);
}
sayColor() // red
sayColor.bind(obj)() // blue
把 null 或者 undefined 作为 this 的绑定对象传入 call、apply 或者 bind,这些值 在调用时会被忽略,实际应用的是默认绑定规则,但是可以用来进行展开柯里化等操作,不会产生什么副作用。
function demo(arg1, arg2, arg3) {
console.log(arg1, arg2, arg3);
}
// 把数组“展开”成参数
demo.apply(null, [1, 2, 3]); // 1,2,3
// 使用 bind(..) 进行柯里化
var bar = demo.bind(null, 1);
bar(2)(3); // 1,2,undefined
new绑定
this绑定的是新创建的对象 new执行了四步操作从中可以看出this指向新创建的对象
// new所执行的操作
// 1、创建一个对象
// 2、该对象的原型指向构造函数的原型对象
// 3、把构造函数的this指向obj
// 4、构造函数执行结果如果是引用类型则返回执行结果,不然就返回创建的对象
function _new (fn, ...arg) {
//Object.create()创建的对象的原型指向传入的对象
const obj = Object.create(fn.prototype);// obj.__proto__ === fn.prototype
const res = fn.apply(obj, arg);
return res instanceof Object ? res : obj;
}
箭头函数的this
箭头函数的this是根据当前的词法作用域来决定,也就是说在箭头函数被定义的时候已经确定了this的指向。
箭头函数会继承外层函数调用的this绑定。
箭头函数的绑定无法被修改。call apply bind没用
// demo在定义的时候的词法作用域的this就是window
const demo = () => { console.log(this) }
demo(); // window
const obj = { name: 'dc }
// call也不会改变this指向
demo.call(obj); // window
---------------------------------------------------------
// 箭头函数的 `this` 值是在创建函数时继承自外部作用域,而不是在函数被调用时动态确定的
// 箭头函数被声明的时候的作用域是window所以this指向window
// 对象字面量本身并没有自己的作用域
const obj = {
name: 'dc',
fun: () => { return this.name; }
};
obj.fun(); // undefined
箭头函数的核心理解可以为: this为它爹的this,它爹的this动态改变后自然它的this就会改变,但是一旦它爹确定了this那么儿子也就确定了this。
此文自动发布于:github issues