作用域、变量提升、自由变量、this、闭包
作用域
作用域是某个变量合法使用的范围,分为:
全局作用域
函数作用域
块级作用域
变量提升
在一段JS脚本(即一个<script>
标签中,或一个function中)执行前,要先解析代码(所以说JS是解释执行的脚本语言),解析的时候会先创建一个全局执行上下文。先把代码中即将执行的函数声明都拿出来,再把变量声明拿出来。
函数声明优先级高于变量声明。
自由变量
一个变量在当前作用域没有被定义,但是被使用了
就会一层层向上级寻找,直到找到为止
如果找到全局作用域都没有找到,就报错 xxx is not defined
这种一层层的关系,就叫做作用域链。
所有的自由变量都应该在函数定义的地方,向上级作用域寻找,不是在执行的地方。
this
this
表示的是谁执行的你
this
是执行上下文的一部分,this
的取值是在函数执行时确定的,而不是函数定义时确定的。
var cyName = 'window-cy' var obj = { cyName: 'obj-cy', printName: function(from){ console.log(from + ': ' + this.cyName) } } obj.printName('obj') // obj-cy obj.printName.call({cyName: 'call-cy'},'call') // call-cy var p = obj.printName p('window') // window-cy复制代码
var cyName = 'window-cy'
换成let cyName = 'window-cy'
的话,p('window')
则打印undefined
。因为p('window')
此时的this
是window
,var
声明的变量会自动挂载到window
上,而let
不会。
this
的使用场景
作为普通函数执行
作为对象属性执行
作为构造函数执行
箭头函数中执行
使用
call
apply
bind
调用
闭包
闭包closure指的是那些引用了另一个函数作用域中变量的函数。
闭包的使用场景
函数作为返回值返回
函数作为参数传递
function F1() { var a = 100 return function () { console.log(a) // 100 } } var f1 = F1() var a = 200 f1()复制代码
function F1() { var a = 100 return function () { console.log(a) // 100 } } function F2(f1) { var a = 200 f1() } var f1 = F1() F2(f1)复制代码
实际开发中闭包的应用场景
隐藏数据,对外只提供API
function createCache(){ const prefix = 'portal-' const data = {} return { set: function(key,value){ data[prefix + key] = value }, get: function(key){ return data[prefix + key] } } } const c = createCache() c.set('name','cy') cy.get('name')复制代码
相关题目
var a = 10; function b() { a = 100; } b(); console.log(a); // 100复制代码
// 变量提升 var a = 10; function b() { a = 100; return; function a() {}//执行函数b之前,会先解析代码,把函数声明先拿出来,此时在b函数中,a就是funciton a。后续修改的就是函数b局部的变量a,与外部作用域中的a无关。 } b(); console.log(a);// 10复制代码
var a = 10; if(true) { var a = 100; // var声明的变量没有块级作用域 } console.log(a); // 100复制代码
var name = 'map'; function func() { console.log(this.name); // map } var object = { name: 'object', getNameFunc: function(fn) { fn && fn(); // 调用fn的是window,所以此时的this是window return () => { return this.name; } // 箭头函数的this指向父级作用域,所以此时的this是object } }; console.log(object.getNameFunc(func)()); // object // 结果输出 // map // object复制代码
手写call
apply
bind
函数
利用函数作为对象属性调用的特性实现
var cyName = 'window-cy' var obj = { cyName: 'obj-cy', printName: function (from) { console.log(from + ': ' + this.cyName) } } obj.printName.call({ cyName: 'call-cy' }, 'call') Function.prototype.cyCall = function (o) { const symbol = Symbol() o[symbol] = this // 谁调用的cyCall,谁就是this // 取第一个参数以外的参数 let args = Array.prototype.slice.call(arguments, 1) // arguments是类数组(含有length属性的对象),typeof arguments为object。Array.prototype.slice.call可以把类数组转化为数组 o[symbol](...args) delete o[symbol] } obj.printName.cyCall({ cyName: 'cyCall-cy' }, 'cyCall') Function.prototype.cyApply = function (o) { const symbol = Symbol() o[symbol] = this const args = Array.prototype.slice.call(arguments, 1) o[symbol](...args) delete o[symbol] } obj.printName.apply({ cyName: 'cyApply-cy' }, ['cyApply']) Function.prototype.cyBind = function (o, ...args) { return function (...innerArgs) { const symbol = Symbol() o[symbol] = this o[symbol](...args, innerArgs.slice(args.length > 0 ? args.length - 1 : 0)) delete o[symbol] } } obj.printName.bind({ cyName: 'cyBind-cy' }, 'cyBind')()
作者:iceychan
链接:https://juejin.cn/post/7015149221409996830