这是我 JavaScript 学习过程中遇到的一些容易混淆的地方,趁着有时间,做了一个整理。
js 作用域内有变量,这个很好理解,但有一些细节需要注意。
console.log(foo); // 函数
function foo(){
console.log("函数声明");
}
console.log(foo); // 函数
var foo = "变量";
console.log(foo); // 变量
当变量名与函数名同名,且都提升上去了,那最终结果是哪个声明起作用呢?
有两个知识点:
1. var foo;并不会覆盖之前的变量
2. 函数提升优先级比变量提升要高,且不会被变量声明覆盖,但是会被变量赋值覆盖,所以上面的代码实际上是
function foo(){ // 优先级最高,提升到最前面
console.log("函数声明");
}
var foo; // 只提升声明,不提升赋值,且不能覆盖函数声明
console.log(foo);
console.log(foo);
foo = "变量"; // 可以覆盖函数声明
console.log(foo);
var num1 = 1;
function fn(num3){
console.log(num1); //output undefined
console.log(num3); //output 4
console.log(num4); //throw error “num4 is not defined”
console.log(num2); //throw error “num2 is not defined”
var num1 = num4 = 2; // js 连等赋值 num4 不会被提升
num2 = 3; // 没有 var 会挂载到全局作用域,但不会提升,所以之前会报错
var num3= 5;
}
fn(4);
if (true) {
function fn(){ return 1; }
}else {
if(false){
function fn(){ return 2; }
}
}
console.log(fn.toString());
console.log(fn())
以下是从找到这个例子的原文中摘抄的内容:
chrome和ie一均为function fn(){ return 2;},而firefox中依然报错。
可见三者处理并不相同。ff中会提前变量的声明,但不会提前块级作用域中的函数声明。而chrome和ie下就会提前块级作用域中的函数声明,而且后面的声明会覆盖前面的声明。
我写这篇文章时,测试的结果 IE10 及以下,返回2,IE11和谷歌,火狐新版,返回1
var fn;
console.log(fn); // undefined
if (true) {
function fn(){ return 1; }
}else {
if(false){
function fn(){ return 2; }
}
}
console.log(fn.toString()); // 函数 1
console.log(fn()) // 1
if判断内函数并没有提升
function test5() {
var fn = function fn1() {
log(fn === fn1); // true
log(fn == fn1); // true
}
fn();
log(fn === fn1); // fn1 is not defined
log(fn == fn1); // fn1 is not defined
}
test5();
在function内部,fn完全等于fn1
在function外面,fn1则是 not defined
!兼容
b();
var a = function b() {alert('this is b')};
在ie8及以下浏览器是可以执行b的. 说明不同浏览器在处理函数表达式细节上是有差别的.
函数 return 语句后的变量、函数声明提升
function text6() {
var a = 1;
function b() {
a = 10;
return;
function a() {}
}
b();
console.log(a); // 1
}
text6();
在js中,提到变量赋值,就要先说作用域,而作用域,在es6之前,只有函数才会形成独立的作用域,然后函数的嵌套形成了 js 的作用域链。子作用域内可以访问父级作用域内的元素。函数的作用域在函数确定的时候就已经确定,与调用无关。
// test1
var x = 1;
function foo(x) {
var x = 3;
var y = function() {
x = 2;
console.log(x)
}
y();
console.log(x);
return y
}
var z = foo() // 2 2
z() // 2
这段函数会输出三个 2 ,指向同一个 x,甚至,将 x 改为对象,就更明显了
// test2
var x = "abc";
function foo(x) {
var x = c;
var y = function() {
return x;
}
return y;
}
var c = {a:1}
var z = foo();
var b = z();
console.log(b === c); // true
上面例子中,foo 函数执行后,返回 y 函数并赋值给 z,z 指向 y 函数(函数体),此时,z 并不在 foo 函数的作用域内,在此作用域不能访问到 x,但 z 只是引用类型数据的一个指针,只是同 x 指向了同一个对象而已。而执行 z 函数,则会返回 x 的值,这个值是函数 y 作用域内访问到的 x 的值,是根据函数的书写位置确定的作用域,并不会因为调用位置不同,而改变变量的指向。
但是同时要注意,虽然函数作用域在函数写出来时就已经确定,但具体的值却跟调用的时机有关。
// test3
var x = "abc";
function foo(x) {
var x = c;
var y = function() {
x.a++;
return x;
}
return y
}
var c = {a:1}
var z = foo();
console.log(z()) // {a: 2}
console.log(z()) // {a: 3}
console.log(z()) // {a: 4}
这个例子中,输出的三次都是同一个对象,但输出的值不同,这是因为输出的时候的值不同,这就和调用时的实际值有关了。
同时,函数的另一个值也与函数的调用情况相关,就是 this。函数的 this 指向函数的调用者,与函数在哪里定义没有关系。
var x = "abc";
var b = {x:"def"}
function foo(x) {
var x = c;
var y = function() {
x.a++;
return this.x;
}
console.log(y()); // abc
return y
}
var c = {a:1}
var z = foo();
console.log(z()) // abc
b.a = z;
console.log(b.a()) // def
b.b = {
x: "hig",
z: z
}
console.log(b);
console.log(b.b.z()); // hig
上面的例子就说明了,函数内的 this 始终指向本次调用函数的对象,如果没有,就指向全局对象,而如果指向的对象本身是另一个对象的属性,这并不影响,甚至,将上个例子里的 b.b 的 x 属性删除,最后调用时 b.b.z() 的 this 依然指向 b.b 所指的对象,即使该对象对象没有 x 值,会返回 undefined,而不会向上级寻找。
function fn(){
var array=[];
var b = {a:1}
for(var i=0;i<10;i++){
array[i]=function(){
var abc = {def: "ghi"};
return b;
}
}
return array;
}
var a = fn();
console.log(a) //[ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ]
console.log(a[0].toString()) //f
console.log(a[1] === a[2]) // false
console.log(a[1] == a[2]) // false
console.log(a[1].toString() === a[2].toString()) // true
console.log(a[1]() === a[2]()) // true
两个函数相等的比较:两个函数的作用域相同并且函数内容相同,那么,比较结果为这两个函数相等,否则,不相等!不是我们直接用 == 去比较,而是按照这个规则去进行比较。
函数是对象,复合型的值。一般比较引用,同一个引用,就相等;否则不等。比较toString几乎没任何意义:因为对于函数调用来说,作用域只是一个不可见的透明的规则
function fn(a, b) {
var a = 1;
var b = 2;
console.log(arguments[0]);
console.log(arguments[1]);
arguments[0] = 3;
arguments[1] = 4;
console.log(a);
console.log(b);
}
fn(5,6) // 1 2 3 4 传入实参,实参和形参指向相同
fn() // unde unde 1 2 没有传入实参,arguments 指向 undefined,形参与实参不再相关
function fn(a, b) {
'use strict'
var a = 1;
var b = 2;
console.log(arguments[0]);
console.log(arguments[1]);
arguments[0] = 3;
arguments[1] = 4;
console.log(a);
console.log(b);
}
fn(5,6) // 5 6 1 2 严格模式下 arguments 指向实参,与形参不相关
fn() // unde unde 1 2
javascript中Arguments对象是函数的实际参数,arguments对象的长度是由实参个数而不是形参个数决定的。形参是函数内部重新开辟内存空间存储的变量,但是其与arguments对象内存空间并不重叠。
在es5规范下的非严格模式下,函数调用时传入实参,函数内部使用 形参 与 arguments 指向相同, arguments 的具体属性改变, 形参的值也会改变,但如果没有传入实参,arguments 与 形参的指向就没有关系了。
es5规范的严格模式下,形参与arguments 分别存放,不会相关
但自es6之后,非严格模式下效果也同es5 严格模式一样,即arguments和参数符号分别存放。
而es6的严格模式下,修改arguments会报错。
function aa(a,b,c) {
console.log(arguments); // {0: 函数a, 1: 2, 2: 3} 通过更改形参的值,可以更改 arguments
function a(){console.log(a)}
console.log(a); // 函数 a
console.log(aa); // undefined, var aa 被提升
console.log(arguments); // 同上
var a = "ee";
var aa = "444";
arguments = 6;
console.log(a); // ee
console.log(aa); // 444
console.log(arguments) // 6
}
aa(1,2,3)
function aa(a,b,c) {
'use strict' // 调用严格模式
console.log(arguments); // 指向arguments 对象,{0:1,1:2,2:3}
function a(){console.log(a)}
console.log(a); // 函数a
console.log(aa); // undefined
console.log(arguments); // 同上
var a = "ee";
var aa = "444";
arguments[0] = 6;
// arguments = 6; 严格模式 不能直接对 arguments 进行赋值
console.log(a); // ee
console.log(aa); // 444
console.log(arguments) // // 指向arguments 对象,{0:6,1:2,2:3}
}
aa(1,2,3)
从网上找到的一些解释
填充变量的顺序是: 函数的形参 -> 函数声明->变量声明, 当变量声明遇到VO中已经有同名的时候,不会影响已经存在的属性。
函数形参————由名称和对应值组成的一个变量对象的属性被创建;没有传递对应参数的话,那么由名称和undefined值组成的一种变置对象的属性性也将被创建
函数声明————由名称和对应值(函数对象(function-object))组成一个变量对象的属性被创建;如果变置对象已经存在相同名称的厲性,则完全替换这个属性
变量声明————由名称和对应值(undefined〉组成一个变量对象的属性被创建;如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的属性一般情况下,会按照四种方式依次解析
语言内置:
形式参数:
函数声明:
变量声明:
也有例外:
内置的名称arguments表现得很奇怪,看起来应该是声明在形参之后,但是却在声明之前。这是说,如果形参里面有arguments,它会比内置的那个优先级高。所以尽可能不要在形参里面使用arguments;
在任何地方定义this变量都会出语法错误
如果多个形式参数拥有相同的名称,最后的那个优先级高,即便是实际运行的时候它的值是undefined;
使用js的混淆加密,其目的是为了保护我们的前端代码逻辑,对于一些搞技术吃饭的公司来说,为了防止被竞争对手抓取或使用自己的代码,就会考虑如何加密,或者混淆js来达到代码保护。
ParentNode.children:该属性继承自ParentNode,返回值是一个HTMLCollection实例,成员是当前节点的所有元素子节点,该属性只读,且该属性是动态集合。Node.prototype.childNodes:该属性继承自Node,返回值是一个NodeList实例,成员是当前节点的所有子节点(包括但不限于元素子节点),该属性也是个动态集合。
像软件加密与解密一样,javascript的混淆与解混淆同属于同一个范畴。道高一尺,魔高一丈。没有永恒的黑,也没有永恒的白。一切都是资本市场驱动行为,现在都流行你能为人解决什么问题,这个概念。
在安全攻防战场中,前端代码都是公开的,那么对前端进行加密有意义吗?可能大部分人的回答是,毫无意义,不要自创加密算法,直接用HTTPS吧。但事实上,即使不了解密码学,也应知道是有意义的,因为加密前和解密后的环节
公司代码提供给第三方使用,为了不完全泄露源码,需要对给出的代码进行加密混淆,前端代码虽然无法做到完全加密混淆,但是通过使用 webpack-obfuscator 通过增加随机废代码段
在 JavaScript 中,function关键字可以完成一个简单的工作:创建一个函数。 但是,使用关键字定义函数的方式可以创建具有不同属性的函数。在本文中,我们来看一下,如何使用function关键字来定义函数声明和函数表达式,以及这两种函数之间的区别又是什么。
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!