js程序设计02——变量、作用域问题
首先,ECMAScript中的数据类型分为基本类型、引用类型,基本类型的访问操作是按值的。引用类型的值是保存在内存中的对象,操作对象时,实际上操作的是对象的引用,而非对象自身。“javascript高级程序设计”中的描述是“当复制保存着对象的某个变量时,操作的是对象的引用。但在为对象添加属性时,操作的是实际的对象”,下面从数据复制来看下:
var num1 = 12;
var num2 = num1;
num2 = 13;
console.log(num1);//
console.log(num2);// var obj = new Object({
"name":"admin1"
});
var obj2 = obj;
obj2.name = "test";
console.log(obj.name);
基本类型的值进行复制时,复制的仅仅是内存中的值,变量的地址是在内存中重新开辟的空间,所以这里对num2重新赋值后num1并没有改变;
引用类型进行复制时,变的是将新值的引用指向原有的值,这样一来,新值和旧值便指向了同一内存区域,两个变量实际上将引用同一个对象,因此这里对obj2的操作会影响到obj对象。
来看如下一个参数传递的实例:
function say(num){
++num;
return num;
}
var count = 1;
var num2 = say(count);
console.log(count); //1
console.log(num2); //2 var o = {"name":"修改前的值"};
function say2(obj){
obj.name = "修改后的值";
}
say2(o);
console.log(o.name); //"修改后的值"
参数传递时:基本类型传递的是值,引用类型也是按值传递的,从上述例子并不能看出来obj传递时是按值传递还是引用传递,因为即使是按值传递,obj也会按引用来访问同一个对象。还有一个demo:
function setName(obj){
obj.name = "admin";
obj = new Object();
obj.name = "test";
}
var obj = new Object({
"name":"hahaha"
}); setName(obj);
console.log(obj.name); //"admin"
如果Object是按引用传递的,那么Object就会自动被修改为指向其name 属性值为"test"的新对象。但是,当接下来再访问object.name 时,显示的值仍然是"admin"。这说明即使在函数内部修改了参数(对象)的值,但原始的引用仍然保持未变。实际上,当在函数内部重写obj 时,这个变量引用的就是一个局部对象了。而这个局部对象会在函数执行完毕后立即被销毁。
关于instanceof:
var obj = new Object(); console.log(obj instanceof Object); //true
console.log(12 instanceof Object); //false
instanceof相比于typeof,前者是检测引用类型是否是某类型的实例,仅适用于引用类型,对所有的基本类型调用instanceof直接返回false,所有的引用类型均是Object的实例。引用类型包括:Object、Array、Date、RegExp、Function、基本包装类型等。
关于函数环境及作用域问题:
先看一个例子:
function buildUri(){
var qs = "?debug=true";
with(location){
var url = href + qs;
}
return url;
} console.log(buildUri()); //"http://null.jsbin.com/runner?debug=true"
首先js没有块级作用域概念,使用with语句后,在其内部定义的url变量便成了外部函数作用域内的变量,内部的href会在location作用域中查找,即location.href,所以可以直接作为返回值返回。同样的还有if语句、for循环中声明的变量:
if (true){
var url = "http://www.baidu.com";
} console.log(url); //"http://www.baidu.com"
for(var i = 0; i < 10; i++){ } console.log(i); //"10"
再来看下js Array的toString()、toLocaleString()的区别:
var p1 = {
toLocaleString:function(){
return "p1_1";
},
toString:function(){
return "p1_2";
}
} var p2 = {
toLocaleString:function(){
return "p2_1";
},
toString:function(){
return "p2_2";
}
} var ps = [p1,p2];
console.log(ps); //"p1_2,p2_2"
console.log(ps.toString()); //"p1_2,p2_2"
console.log(ps.toLocaleString()); //"p1_1,p2_1"
js的Array操作的基本方法(var colors = new Array();):
- 添加:colors.push("xxx")
- 移除:colors.pop() // colors.length = colors.length - 1
- 取index为0的数据:colors.shift("xxx")
- 从开始依次插入:colors.unshift("xxx")
- 排序:sort()、reverse()
- 拼接:concat()
- 截取:splice()
- 位置查找:indexOf()、lastIndexOf()
- 迭代:filter(返回该函数会返回true 的项组成的数组)、forEach(没有返回值)、map(返回每次函数调用的结果组成的数组)
- 归并:reduce()、reduceRight()
首先是几个例子:
var arr = new Array();
arr.unshift("1","2");
console.log(arr); //["1", "2"]
arr.unshift("4");
console.log(arr); //["4", "1", "2"]
其次是sort函数,sort默认会将数组的每一项直接先转换为string类型,然后再以string类型比较每一项内容再进行排序,所以如下数据正确:
var values = [0, 1, 5, 10, 15];
values.sort(); //默认是转换为string比较
console.log(values); //[0, 1, 10, 15, 5]
sort接收一个比较函数作为参数来自定义比较方式:
//自定义的比较函数
function compare(a,b){
if (a > b){
return 1;
}else if(a < b){
return -1;
}else{
return 0;
}
} values.sort(compare);
console.log(values); //[0, 1, 5, 10, 15]
使用splice实现数组的添加、删除、替换:
var arr1 = ["1","2","3","4"];
arr1.splice(0,1); //删除0位置开始的1个元素
console.log(arr1); //["2", "3", "4"] arr1.splice(0,0,"我是插入的");
console.log(arr1); //["我是插入的", "2", "3", "4"] arr1.splice(0,1,"我是替换的");
console.log(arr1); //["我是替换的", "2", "3", "4"]
使用concat实现数组拼接:
var arr1 = new Array(1,2,3,4,5);
var arr2 = arr1.concat(7,8);
console.log(arr2); //[1, 2, 3, 4, 5, 7, 8]
var arr3 = arr1.concat(9,["admin","test"]);
console.log(arr3); //[1, 2, 3, 4, 5, 9, "admin", "test"]
迭代的基本使用:
var arr1 = new Array(1,2,3,4,5);
var arr2 = arr1.filter(function(item,index,array){
return item > 3;
});
console.log(arr2); //对每一项进行>3判断,返回[4, 5] var arr3 = arr1.map(function(item,index,array){
return item * 2;
});
console.log(arr3); //对每一项进行*2运算,返回结果:[2, 4, 6, 8, 10] var arr4 = arr1.forEach(function(item,index,array){
console.log(item * 2);
});
数组归并的使用:
var arr1 = new Array(1,2,3,4,5);
var arr2 = arr1.reduce(function(prev,cur,index,array){
console.log("prev:"+prev);
console.log("cur:"+cur);
return prev + cur;
});
console.log("arr2:"+arr2); //
可以看出,reduce执行时,cur默认是从数组第二项开始遍历,这时prev为index=0的值,每一次操作后return的值作为下一次遍历的prev值,知道遍历结束整个数组,reduceRight则以相反顺序进行遍历。
这里有一个很奇怪的地方,如下:
如果array长度为1时,使用第一种方式直接报错,第二种方式确是ok的,甚是不解啊,坑爹的js。按理说直接采用对象字面量的方式创建数组内存消耗小点,速度优于new Array方式,一般情况下,建议使用字面量形式。
关于函数声明
可以将函数名想像为一个指针,这样一来,后声明的同名函数会覆盖之前的函数,这也是js中函数没有重载的原因。js运行时,会优先将所有的函数声明提取出来放入当前的执行环境当中,函数被调用时再从执行环境中抽取调用,所以如下调用是正常的:
console.log(say()); //"hello merry" function say(){
return "hello merry";
}
但是如下写法是错误的:
console.log(s()); //"TypeError: s is not a function" var s = function say(){
return "hello merry";
}
函数中返回一个函数
函数可以作为参数进行传递,也可以作为返回值直接返回,前面有记录数组的sort方法,该方法可以自定义一个排序规则,直接传入一个比较函数即可,比较函数包含数组中的两个值,下面改写一下该方法,以实现针对对象数组某个属性进行排序:
function compareFromAttr(attr){
return function(obj1,obj2){
var value1 = obj1[attr];
var value2 = obj2[attr];
if(value1 > value2){
return 1;
}else if(value1 < value2){
return -1;
}else {
return 0;
}
}
} var arrs = [{"name":"admin","age":23},{"name":"test","age":21}];
arrs.sort(compareFromAttr("age"));
console.log(arrs);
arrs.sort(compareFromAttr("name"));
console.log(arrs);
关于函数内部属性
函数内有2个特殊对象,一个是arguments,一个是this。arguments用来保存参数数组,this指的是当前函数执行绑定的执行环境。arguments有一个特殊属性叫做callee,该属性是一个指针,指向拥有该arguments对象的函数,也就是说arguments属于哪个函数,那么arguments.callee()就代表哪个函数,即可以使用arguments.callee()替代该函数执行,在递归函数里面可以这样使用,这样一来修改函数名称后不需再修改内部函数名:
function factorial(num){
if(num <= 1){
return num;
}else{
return num * arguments.callee(num - 1);
}
} console.log(factorial(3)); //
ECMAScript 3后添加了一个属性:caller,caller属性保存了调用当前函数的函数的引用,如果在全局函数中调用该函数,返回null:
function outer(){
inner();
}
function inner(){
alert(arguments.callee);
alert(arguments.callee.caller);
}
outer();
因为outer调用了inner,所以在inner中使用caller属性,直接返回的是outer函数的引用,全局的话直接返回null:
function inner(){
console.log(arguments.callee.caller);
}
inner(); //"null"
关于函数属性及方法(call、apply、length等)
函数属性length表示函数的命名参数个数,call、apply用于指定函数中this的值:
function call(name){
console.log("call is called:"+name);
} function call1(){
call.apply(this,["hahah"]); //传入对象数组
} function call2(name){
call.apply(this,arguments); //传入arguments对象
} call1(); //"call is called:hahah"
call2("hahah"); //"call is called:hahah" console.log(call.length); //
console.log(call1.length); //
console.log(call2.length); //
call和apply的区别在于:两个方法第一个参数均是指定this作用域,不同的是参数部分:apply可以使用arguments或者参数数组形式,call使用的是挨个罗列的方式。
使用call or apply来改变函数作用域:
var color = "red";
var o = {"color":"blue"}; function sayColor(){
console.log(this.color);
} sayColor(); //"red"
sayColor.call(this); //"red"
sayColor.call(window); //"red"
sayColor.call(o); //"blue"
ECMAScript 5中新增了一个bind方法:bind创建一个函数实例,然后将其内部的this值绑定到bind的参数上:
var color = "red";
var o = {"color":"blue"}; function sayColor(){
console.log(this.color);
}
var say = sayColor.bind(o);
say(); //"blue"
每个函数继承的toLocaleString()和toString()方法始终都返回函数的代码:
function sayColor(){
console.log(this.color);
} /*"function sayColor(){
window.runnerWindow.proxyConsole.log(this.color);
}"*/
console.log(sayColor.toString());
console.log(sayColor.toLocaleString());
关于基本包装类型
Boolean、String、Number类型,既具有基本类型的行为又具有引用类型的属性:
var s = new String("adminteatawmdnaw");
var s1 = "adminteatawmdnaw";
console.log(s instanceof String); //true
console.log(s1 instanceof String); //false
console.log(typeof s); //"object"
console.log(typeof s1); //"string" var n = new Number(12);
var n1 = 12;
console.log(n instanceof Number); //true
console.log(n1 instanceof Number); //false
console.log(typeof n); //"object"
console.log(typeof n1); //"number" var b = new Boolean(true);
var b1 = false;
console.log(b instanceof Boolean); //true
console.log(b1 instanceof Boolean); //false
console.log(typeof b); //"object"
console.log(typeof b1); //"boolean"
关于encodeURI、encodeURIComponent区别
作用范围:encodeURI适用整个uri,而encodeURIComponent适用于uri中的某一段
编码范围:encodeURI不会对本身属于uri的特殊字符进行编码,例如冒号、正斜杠、井号、问号等,而encodeURIComponent会对任何非标准字符进行编码。
var uri = "http://www.wrox.com/illegal value.htm#start";
console.log(encodeURI(uri));//"http://www.wrox.com/illegal%20value.htm#start"
console.log(encodeURIComponent(uri));//"http%3A%2F%2Fwww.wrox.com%2Fillegal%20value.htm%23start"
实际使用时,后者使用居多,因为一般都是给uri尾部附上的请求参数进行编码,对应地,解码使用decodeURI、decodeURIComponent。
关于eval
eval()是js一个很变态的函数,属于Global对象,eval()执行时会将eval()中的参数当做实际ECMAScript语句来执行,所以一下写法是正确的(严格模式下会报错):
eval("function say(){console.log('hello Mr.Chao')}");
say(); //"hello Mr.Chao"
eval("var msg = 'how do you known my name?'");
console.log(msg); //"how do you known my name?"
js程序设计02——变量、作用域问题的更多相关文章
- JS函数、变量作用域
函数参数 函数的()中指定一个或多个形参(形式参数),多个形参之间用,号隔开,声明形参相当于在函数内部声明了对应的变量,但不赋值.在调用时在()中指定实参 调用时解析器不会检查实参类型.数量,实参可 ...
- js中的变量作用域问题
变量既可以是全局的,也可以是局部的. 全局变量可以在脚本的任何位置被引用.一旦你在脚本里声明了一个全局变量,就可以从这个脚本中的任何位置——包括函数内部引用它.全局变量的作用域是整个脚本. 局部变量只 ...
- js基础之--变量 作用域和内存问题
基本类型:Undefind Null Boolean Number String 引用类型: 对象 在操作对象时,实际上实在操作对象的引用而不是实际的对象.为此,引用类型的值是按引用访问的. 从一个变 ...
- javascript的变量作用域--对比js、php和c的for循环
为什么要写这篇文章呢?主要是给自己提个醒,js的水很深,需要小心点儿才能趟过去,更何况自己不是专业人士,那就得更加小心了. 看下面的js代码: <!DOCTYPE html> <ht ...
- js学习之变量、作用域和内存问题
js学习之变量.作用域和内存问题 标签(空格分隔): javascript 变量 1.基本类型和引用类型: 基本类型值:Undefined, Null, Boolean, Number, String ...
- js的变量作用域 ,变量提升
(function(){ a = 5; alert(window.a); var a = 10; alert(a); })(); 结果: undefined 10 代码等同于下面 var a = un ...
- js的变量作用域
js不支持块级变量作用域,而是包含它们的函数的作用域, 例如: function query() { ; ; i < ; i++) { var b = i; } return b + a; } ...
- [js]js代码执行顺序/全局&私有变量/作用域链/闭包
js代码执行顺序/全局&私有变量/作用域链 <script> /* 浏览器提供全局作用域(js执行环境)(栈内存) --> 1,预解释(仅带var的可以): 声明+定义 1. ...
- JS中for循环变量作用域--解决for循环异步执行的问题
被这个问题困惑了很久,终于在网上找到了答案,感谢~ 现在分享给大家~ js中如何让一个for循环走完之后,再去执行下面的语句? 这涉及for循环变量作用域的问题,js中作用域只有函数作用域和全局作用域 ...
随机推荐
- bzoj 2152聪聪可可
2152: 聪聪可可 Time Limit: 3 Sec Memory Limit: 259 MB Description 聪聪和可可是兄弟俩,他们俩经常为了一些琐事打起来,例如家中只剩下最后一根冰 ...
- An unknown error occurred & “”的 iPhone is busy: Processing symbol files
An unknown error occurred & ""的 iPhone is busy: Processing symbol files An unknown err ...
- Android自动化预备(下)
上次说道:要具备的一些知识,还有多ADB得理解 本次继续ADB理解: AndroidDebugBridge debugBridge =AndroidDebugBridge.createBridge(& ...
- c语言检测文件是否存在int __cdecl access(const char *, int);
最近写代码,遇到很多地方需要判断文件是否存在的.网上的方法也是千奇百怪,“百家争鸣”. fopen方式打开的比较多见,也有其他各种方式判断文件是否存在的,由于其他方法与本文无关,所以不打算提及. 笔者 ...
- Phone Gap [error] cmd: Command failed with exit code 1
下投票 我不知道如何解决这个问题,但尝试了这一点,将解决肯定. 这是由于ANT工具找不到的tools.jar在JRE lib目录下.当我从复制的tools.jar JDK的lib目录下,以JRE li ...
- 第二章 ZAB协议介绍
ZAB ( ZooKeeper Atomic Broadcast , ZooKeeper 原子消息广播协议)是zookeeper数据一致性的核心算法. ZAB 协议并不像 Paxos 算法那样,是一种 ...
- POJ 2352Stars 树状数组
Stars Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 42898 Accepted: 18664 Descripti ...
- json+servlet+ajax
json-lib-2.3-jdk15.jar commons-beanutils-1.7.0.jar commons-httpclient-3.1.jar commons-lang-2.3.jar c ...
- 思维导图软件TheBrain 8全新发布 提供更强大的信息管理
TheBrain思维导图软件是全球唯一一款动态的网状结构的思维导图软件,广泛用于学习.演讲.项目管理.会议.需求调研与分析等.其独特的信息组织方式使得用户可以创建并连接到数以万计的数字想法,为此在全球 ...
- .Net中的Debug模式和Release模式
1.Debug模式和Release模式 在vs中,运行程序有两种模式:Debug和Release 在bin目录下也会生成对应的文件夹,用于存放生成的dll等文件,这两种模式的区别如下: Debug:用 ...