上下文和作用域
新的声明方式,新的声明方式带来了什么?块级作用域
在let之前,js只有全局作用域和函数作用域,先上个栗子吧
if(false){
var aa = 111;
}
console.log(aa) // 111
for(var i=0;i<5;i++){
var bb = 222;
}
console.log(i) //5
console.log(bb) //222
其实从if这个单词开始到大括号结束,从for这个单词开始到大括号结束是有作用域的,叫做块级作用域块级作用域用逻辑判断跳出,return不能跳出块级作用域,只能跳出函数作用域,他能被外部访问是因为var的变量提升和函数提升,除掉一个习惯,函数的声明别放在if和for里,那就只剩变量提升了,如果把上面的代码改为let,const跟let一样的,不讲是因为没人会把const当变量
if(false){
let aa = 111;
}
console.log(aa) // 错误
for(let i=0;i<5;i++){
let bb = 222;
}
console.log(i) //错误
console.log(bb) //错误
let让上面的aa,i,bb只能在那个小小的作用域内使用,如果同个函数作用域或者全局作用域有一样的变量,会安全很多,如果觉得没什么意义的话,就接着用var,没区别
let/const跟var有什么区别
// 面试题来着
// 这里是全局作用域window
var aa = "aa"
let bb = "bb"
console.log(window.aa) //"aa"
console.log(window.bb) //undefined
console.log(window.cc) //undefined
console.log(aa) //"aa"
console.log(bb) //"bb"
// bb并不在window里,因为他在自己的作用域里,只能用变量名去取值
所以目前已知作用域为全局作用域,函数作用域,块级作用域,用代码模拟就是
// 当页面被打开,就有一个全局作用域window
window = {
// 还有页面里所有的dom和bom
var dom = {}
var bom = {}
// 下面的内容就是程序员自己的代码或者插件了
var name = "tom"
function aa(){
// 函数作用域
if(true){
// 块级作用域
let name = "jee"
}
console.log(name) //"tom"
}
}
上下文
回顾一个函数作用域里隐藏了什么东西
有个隐藏的arguments
参数伪数组,如果是dom时间,还有个event
事件对象,并且还有个肯定有的this
上下文,函数被执行肯定有个执行者,执行者就是这个函数的this
// 这个都理解不了就转行吧
var name = "我是window"
function init(){
console.log(this) // window
console.log(this.name) // "我是window"
}
init() //实际是window.init()
// 这个都理解不了就转行吧
var obj = {
name: "我是obj",
init(){
console.log(this) // obj
console.log(this.name) // "我是obj"
}
}
obj.init()
如果我不想把init写在obj里,因为window我也要用,重复写很不好怎么办
用function的方法apply,call,bind
这三兄弟是面试的经典
var name = "我是window"
var obj = {
name: "我是obj",
}
// 加两个
function init(x,y){
console.log(x,y)
console.log(this)
console.log(this.name)
}
// 直接执行init肯定就是window了
init(1,2)
// 修改this方式一,立即执行
init.call(obj,1,2)
// 修改this方式二,立即执行
init.apply(obj,[1,2])
// 修改this方式三,不立即执行,后续执行
init.bind(obj)(1,2)
说apply
因为apply可以把参数变成数组,所以这个强大的优点让人们忘了他实际是用来改变this的
Math.max.apply(null, [14, 3, 77]) //第一个参数是空因为Math本来就是window的子对象
arr1.push.apply(arr1,arr2) //第一个参数需要是this自己,用null就没了
// 还可以这么写
Array.prototype.push.apply(arr1, arr2);
...
//一切可以无限传参数的方法都可以用apply改成传数组
但是apply被拓展运算符代替了
Math.max(...[14, 3, 77])
arr1.push(...arr2);
arr1.push(...arguments);
保留this
在函数里执行函数肯定是window
function init(){
console.log(this)
}
document.querySelector("#id").onclick = function(){
console.log(this) //dom#id....
console.log(this.style.color)
init() // 我们总是以为这个是当前this执行的,但实际是window执行的
}
为了让init能拿到前一个函数的this,最常见的做法是var that = this
然后把that当做参数传过去,这个是没问题的,只是维护很难,很吐血,判断很多
function cb(that){
console.log(that)
}
var obj = {
name: "我是obj",
init(cb){
console.log(this)
var that = this;
cb(that)
}
}
obj.init(cb)
上面的call,apply,bind直接修改上下文this,而不是把上下文当做参数去传递
function cb(that){
console.log(that)
}
var obj = {
name: "我是obj",
init(cb){
console.log(this)
cb.call(this)
}
}
obj.init(cb)
箭头函数也可以保存this,但是必须声明在父函数内部
var obj = {
name: "我是obj",
init(){
console.log(this)
// 箭头函数执行是往上找执行者,直到找到一个执行者后停下来
// 如果如果上一级也是箭头函数就继续往上找
// 箭头函数没有三个改变this的方法
// 一般也不会在这里声明一个cb函数,cb函数一般都是作为参数传进来的
var cb = () => { console.log(this) };
cb() // obj
}
}
obj.init()
箭头函数在定时器效果最明显
var name = "name1";
function init(){
var name = "name2";
// 这个改的是window的name
setTimeout(function(){
this.name = "new"
},2000)
// 这个改的是上面的name
setTimeout(()=>{
this.name = "new"
},2000)
}
this的使用是很少的,也不会需要经常去修改this,自定义插件就用的挺多的
上班的时候学习java后自己做了个js的面向AOP开发的功能
function aop(funArr,beforeFun,afterFun,context){
var thatContext = context;
var that = this;
this.init = function(){
that.funArrEach(funArr,thatContext);
return that;
}
this.funArrEach = function(arr,context){
arr.forEach(function(fun){
that.addAop(fun,context)
})
}
this.addAop = function(fun,context){
var context = context || window;
context[fun.name] = function(){
beforeFun()
fun.call(context,...arguments)
afterFun()
}
}
this.push = function(opt,context){
var context = context || thatContext;
if(Object.prototype.toString.call(opt)=="[object Array]"){
that.funArrEach(opt,context);
}
if(Object.prototype.toString.call(opt)=="[object Function]"){
that.addAop(opt,context);
}
}
}
function aopBeforeFun() { ... }
function aopAfterFun() { ... }
new aop([a,b,c],aopBeforeFun,aopAfterFun).init()
改进版,有时候aopBefore需要判断是否执行
this.addAop = function(fun,context){
var context = context || window;
context[fun.name] = function(){
if(beforeFun(fun,context,arguments)){
}else{
fun.call(context,...arguments)
}
afterFun()
}
}
function aopBeforeFun(cb,context,arg) {
if(true){
// 自己执行
cb.call(context,...arg)
return true;
}else{
// 跳过不执行了
return true
}
}
aopAfter也可以按上面这么改