寒假的时候偶然发现阿里的开发牛冴羽在Github中发布了自己的Js深入系列,看了两章之后觉得写的挺透彻的,也认识到自己对于Js的理解仅停留的能看到一些代码片,遂借此文深化自己对部分未知概念的理解,顺带总结前端学习以来自认为有趣点,算是当作笔记来摘录。
函数参数传递包含两种方式:值传递和引用传递。
对于Javascript来说,基本类型是值传递
,(除了基本类型)例如:对象、方法,是引用传递
var aa = 1;
setNum(aa);
console.log(aa); //1
function setNum(aaa){
aaa = 2;
}
var obj = {
name:'obj',
age:12
}
function myfunc(objtemp){
objtemp.name='func';
alert(objtemp.age); //12
}
myfunc(obj);
alert(obj.name); //func
见之前的文章:
这里补充一个点:for
循环中使用var导致的变量提升问题(后文的所有专题我都会对变量提升的例子做说明,毕竟安全息息相关)
如下面的例子
for (var i = 0; i < 3; i++) {
console.log(i);
}
console.log(i); //3
变量i
被放入了全局变量中,此时全局上下文的VO为
globalContext = {
VO: {
i: 3
}
}
前几天听了一节Google开发团队讲了一节JS开发课程,其实ES6下对作用域提升的问题已经有很好的解决方案,比如使用let
for (let i = 0; i < 3; i++) {
console.log(i);
}
console.log(i); //3
可见拥有一个好的编程习惯对安全影响深远,例如若某个变量的值不会改变就直接用const
来定义。
this
用途最广的就是在面向对象了。和其他语言类似,在对象内用this.xxx
指代具体的属性or函数。看下面一个简单的例子,很容易就跟其他语言带入,其中这里的this
指向myobj。
var myobj = {
getbar : function(){
return this.bar;
},
bar: 1
};
console.log(myobj.getbar()); //1
这看起来和其他语言类似,不过JS在绑定this
的时候存在变量提升的问题,你可以直接下滑看最后一个例子。
但在这之前,我们先简单了解一下JS的内存结构:
在JavaScript中生成一个具体的obj都会把地址(reference)赋值给相应的obj
变量。对于上面的例子来说,变量myobj
是一个地址。后面如果要读取myobj.bar
,引擎先从内存拿到myobj
的地址,然后再从该地址读出原始的对象,返回它的bar属性。
如果bar定义为函数的话,会返回bar所指函数的地址。
所以对于obj中的bar
(无论bar是属性还是函数)来说,它的存储形式类似于下面的形式
{
bar: {
[[value]]: 1
//[[value]]: 若bar为函数,此值为函数的地址
[[writable]]: true
[[enumerable]]: true
[[configurable]]: true
}
}
那么我们再来看一个例子,进一步了解this
在获取值时的trick
var myobj = {
getbar : function(){
console.log(this.bar);
},
bar: 1
};
var bar = 2;
var getbar = myobj.getbar;
myobj.getbar()//1
getbar(); //2
myobjc.getbar
通过myobj
的地址找到getbar()
函数,此时的运行环境就在myobj
中,this
也指向myobj
。其后var getbar = myobj.getbar
将getbar
变量指向函数本身,所以执行getbar
时的this
指向全局变量。
上面的例子,我把它叫做this
变量的范围提升(很可能是错误的叫法)。this
的获取跟Reference有关,根据对Ref的操作来决定this
的值是具体的内存地址还是undefined
,具体的操作流程可以看mqyqingfeng的这篇文章:JavaScript深入之从ECMAScript规范解读this
回到上面的例子中执行getbar()
时获取到的this
实际上为undefined
。非严格模式下,this
的值为undefined
的时候,其值会被隐式转换为全局对象
已经有前辈对this
的绑定做了总结,写的很中肯,这里我贴出来:
JavaScript深入之从ECMAScript规范解读this
with是很古老的用法,很多JS开发者已经放弃这个用法了。打比赛的时候用到了,还是讲一下的好
with
的引用是方便对象的属性调用,我们看下面的例子
myobj = {
name : 'hpdoger',
sex : 'boy'
}
console.log(myobj.name) //hpdoger
console.log(myobj.sex) //boy
with(myobj){
console.log(name) //hpdoger
console.log(sex) //boy
}
with 被当做重复引用同一个对象中的多个属性的”快捷方式”,可以不需要重复引用对象本身。
其次,之所以放到this
的后面来讲,因为with
也存在变量提升的原因。看下面的例子,其中name
被提升至全局变量的范围
function foo(obj) {
with (obj) {
name = 'hpdoger';
}
}
aa = {
sex: 'boy'
}
foo(aa)
console.log(aa.name) //undefined
console.log(name) //hpdoger
我们再来对它进行一点拓展,我们知道在Javascript中对非基本数据类型的引用是引用传递
,那么也就是说我们可以通过with来返回一些意料之外的东西
比如返回Object.prototype
来污染原型链
function foo(obj) {
with (obj) {
return __proto__
}
}
aa = {
name: 'boy'
}
foo(aa).name='admin'
console.log("".name) //admin
又或者是返回一个Function的构造类来RCE,参照Confidence2020-Web题解
global.flag = "flag{aaaa}";
function anonymous(a,
/*``*/) {
with(par)return constructor
}
function par(a) {
console.log(123);
}
console.log(anonymous``)
console.log(anonymous`` `return flag` ``)
定义:函数与对其状态即词法环境(lexical environment)的引用共同构成闭包(closure)。也就是说,闭包可以让你从内部函数访问外部函数作用域。在JavaScript,函数在每次创建时生成闭包。
我们先来下面这个例子吧,探讨一下引入闭包到底要来干什么
var data = [];
for (var i = 0; i < 3; i++) {
data[i] = function () {
console.log(i);
};
}
data[0]();
data[1]();
data[2]();
在控制台打印的结果都是3
,这是因为var
声明的i
被提升为全局作用域(如果不理解可以看之前的#Scope Chain),此时调用data[0]函数时的作用域链为
data[0]Context = {
Scope: [AO, globalContext.VO]
}
data[0]Context 的 AO 并没有 i
值,所以会从 globalContext.VO 中查找,i
为 3,所以打印的结果就是 3。
同理调用data[1] 和 data[2]。
那我们用闭包的写法的来表示一下
var data = [];
for (var i = 0; i < 3; i++) {
data[i] = (function (i) {
return function(){
console.log(i);
}
})(i);
}
data[0]();
data[1]();
data[2]();
现在控制台打印的结果就分别为0、1、2
。因为我们在return
之前加了一层匿名函数function(i)
(当然你也可以加一层标准函数名来返回,不一定非要是匿名函数)。此时导致执行 data[0] 函数的时候,data[0] 函数的作用域链发生了改变如下
data[0]Context = {
Scope: [AO, 匿名函数Context.AO globalContext.VO]
}
此时匿名函数执行上下文的AO为:
匿名函数Context = {
AO: {
arguments: {
0: 0,
length: 1
},
i: 0
}
}
我们知道在 Javascript 中,如果一个对象不再被引用,那么这个对象就会被 GC 回收,否则这个对象一直会保存在内存中
如果我们以闭包的形式返回一个变量,那么封装它的上层函数中的变量就可以被记录在内存中,就像这个例子中的变量i
。
最后举一个我们日站中最典型的使用闭包的例子,加深对闭包的理解
(function(document){
var viewport;
var obj = {
init:function(id){
viewport = document.querySelector("#"+id);
},
addChild:function(child){
viewport.appendChild(child);
},
removeChild:function(child){
viewport.removeChild(child);
}
}
window.jView = obj;
})(document);
(function(document){})
就相当于创建了一个匿名函数,传入的参数为document
。
这时你可能会疑惑为什么这个匿名函数里并没有return function(xxx)
这种形式,似乎不符合闭包的结构?那就是走入了对闭包
这个抽象概念的误区。
我们看到在函数的最后window.jView = obj;
。意思是在 window 全局对象定义了一个变量 jView
,并将这个变量指向 obj 对象,即全局变量jView
引用了 obj
. 而 obj
对象中的函数又引用了函数 f 中的变量 viewport
,因此函数 f 中的 viewport 不会被 GC 回收,viewport 会一直保存到内存中,所以这种写法满足了闭包的条件。
当我们需要在模块中定义一些变量,并希望这些变量一直保存在内存中但又不会“污染”全局的变量时,就可以用闭包来定义这个模块