文档章节

js程序设计02——变量、作用域问题

c
 caiyezi
发布于 2016/11/08 20:24
字数 2742
阅读 7
收藏 0
点赞 0
评论 0

首先,ECMAScript中的数据类型分为基本类型、引用类型,基本类型的访问操作是按值的。引用类型的值是保存在内存中的对象,操作对象时,实际上操作的是对象的引用,而非对象自身。“javascript高级程序设计”中的描述是“当复制保存着对象的某个变量时,操作的是对象的引用。但在为对象添加属性时,操作的是实际的对象”,下面从数据复制来看下:

var num1 = 12;
var num2 = num1;
num2 = 13;
console.log(num1);//12
console.log(num2);//13

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);      //15

可以看出,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));  //6

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); //1
console.log(call1.length);  //0
console.log(call2.length);  //1

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?"

 

本文转载自:http://www.cnblogs.com/vipzhou/p/5908904.html

共有 人打赏支持
c
粉丝 1
博文 108
码字总数 0
作品 0
西安
程序员
解密 JavaScript 中的 this

 我想在本文解释JavaScript中的this,希望有助你理解this的工作机制。作为JavaScript程序员,学习this对于你的发 展有很大帮助,可以说利大于弊。这篇文章的灵感来自于我最近的工作——我即...

上赶的大老鼠
2014/01/03
0
0
翻译 - JavaScript中的作用域与变量声明提升

本文地址:http://blog.163.com/jinluhz/blog/static/113830152201131132035178/ 原文地址:http://www.adequatelygood.com/2010/2/JavaScript-Scoping-and-Hoisting 原文作者:ben cherry ......

蜗牛奔跑
2015/06/19
0
0
JS 底蕴之 变量、作用域和垃圾回收

基本类型和引用类型 在 JavaScript 中,数据类型可分为基本类型和引用类型, 基本类型有六种:Null,Undefined,String,Boolean,Number,Symbol; 而引用类型就是传说中的 Object 了。 其中...

Fly_001
05/23
0
0
从上下文,到作用域(彩蛋:理解闭包)

前言 近几天在编程群中的聊天,让我发现了很多人并不清楚什么是上下文(context)、什么是作用域(scope),而且纠结在其中。我当初对这两个概念也只有粗浅的理解,不过我从一开始就不怎么困...

天方夜
07/04
0
0
【旧文新读】解释“闭包”需要几行代码?

新读(2017年10月19日) 本文写于 ,今天做了一点修改,所谓修改,其实只是删去了几句不影响技术内容的话。关于闭包,我最近写了一篇新的文章,提到了静态作用域,相比本文,是对闭包的更深一...

天方夜
07/04
0
0
深入理解JavaScript作用域和作用域链

作用域是JavaScript最重要的概念之一,想要学好JavaScript就需要理解JavaScript作用域和作用域链的工作原理。今天这篇文章对JavaScript作用域和作用域链作简单的介绍,希望能帮助大家更好的学...

唐俊-
2014/01/03
0
0
JavaScript模块化开发一瞥

对于那些正在构建大型应用程序,而对JavaScript不甚了解的开发者而言,他们最初必须要面对的挑战之一就是如何着手组织代码。起初只要在标记之间嵌入几百行代码就能跑起来,不过很快代码就会变...

偶是小娃
2014/02/25
0
0
Shadow DOM系列5-JavaScript

英文链接:Shadow DOM: JavaScript, 02 SEPTEMBER 2013 on Web Components, Shadow DOM 我们目前已经对模板、HTML引入和 Shadow DOM(简介、基础、样式、样式续) 有了一定了解。所有这些技...

一配
2015/08/11
0
0
JavaScript中的this指针 理论化this指针的定义

JavaScript现在应用之广泛,远超其他任何语言,只要是一个合格的网站应用,基本上多多少少都会有JS的存在。在JavaScript中,this的指向被不少Coder所不解,但其实JS中的this理解起来也是相当...

superwebmaster
05/29
0
0
由浅入深JavaScript——变量和原始类型

JavaScript变量 JavaScript变量标识符 标识符 var + 变量名称来定义变量。 变量名称以字母,下划线,美元$符号开头,余下字符可以是字母,数字,下划线,美元符号。 eg: var name = 'hello'...

奇葩界张三
06/29
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

轻松搭建svn版本管理工具+svnmanager管理客户端

前面的文章有写过svn版本管理工具的安装是基于svn的安装包进行安装,对于svn与apache的结合还得下svn和apache的模块进行结合过程比较繁琐,今天来介绍下通过centos的yum来安装svn能够快速安装...

javazyw
14分钟前
0
0
keepalived配置高可用集群

Linux集群概述 根据功能划分为两大类:高可用和负载均衡 高可用集群通常为两台服务器,一台工作,另外一台作为冗余,当提供服务的机器宕机,冗余将接替继续提供服务 实现高可用的开源软件有:...

TaoXu
20分钟前
0
0
mysql联表批处理操作

1 概述 mysql中的单表增删改查操作,可以说是基本中的基本. 实际工作中,常常会遇到一些基本用法难以处理的数据操作,譬如遇到主从表甚至多级关联表的情况(如一些历史问题数据的批量处理),考虑到...

社哥
22分钟前
0
0
IntelliJ IDEA 详细图解最常用的配置,适合刚刚用的新人。

刚刚使用IntelliJ IDEA 编辑器的时候,会有很多设置,会方便以后的开发,磨刀不误砍柴工。 比如:设置文件字体大小,代码自动完成提示,版本管理,本地代码历史,自动导入包,修改注释,修改...

kim_o
37分钟前
0
0
Google Java编程风格指南

目录 前言 源文件基础 源文件结构 格式 命名约定 编程实践 Javadoc 后记 前言 这份文档是Google Java编程风格规范的完整定义。当且仅当一个Java源文件符合此文档中的规则, 我们才认为它符合...

niithub
39分钟前
0
0
java.net.MalformedURLException异常说明

1.异常片段 Java代码中,在进行URL url = new URL(urllink)操作时,提示以下异常信息,该类异常主要问题出在参数urllink上面。 异常片段1 java.net.MalformedURLException at java.ne...

lqlm
39分钟前
1
0
CentOS7修改mysql5.6字符集

解决办法:CentOS7下修改MySQL数据库字符编码为UTF-8,UTF-8包含全世界所有国家所需要的字符集,是国际编码。 具体操作如下: 1.进入MySQL [root@tianqi-01 ~]# mysql -uroot -p Enter passw...

河图再现
41分钟前
0
0
DevExpress v18.1新版亮点——WPF篇(一)

用户界面套包DevExpress v18.1日前终于正式发布,本站将以连载的形式为大家介绍各版本新增内容。本文将介绍了DevExpress WPF v18.1 的新功能,快来下载试用新版本!点击下载>> Accordion Co...

Miss_Hello_World
44分钟前
0
0
Rancher 2.0集群与工作负载告警

Rancher 2.0操作指南。本文将step by step演示如何使用Rancher 2.0中集成的告警功能,包括设置通知程序、设置集群级别以及工作负载级别的告警。 在Rancher 1.x时期,告警功能是很多Rancher用...

RancherLabs
48分钟前
1
0
Python中字符串拼接的N中方法

python拼接字符串一般有以下几种方法: ①直接通过(+)操作符拼接 s = 'Hello'+' '+'World'+'!'print(s) 输出结果:Hello World! 使用这种方式进行字符串连接的操作效率低下,因为python中...

木头释然
50分钟前
9
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部