编程之修,重在积累,而非资质。资质虽然重要,可是后天的努力更不可缺少。
直接量
编程世界中的直接量,就是表面上可以见到的数据值。常见的直接量有数字、小数、字符串。
-
字符串的出现必然带着双引号(单引号也可以),被很好地包裹住,而数字则是光秃秃的,如10或者20,没有双引号。
"hello world"; 'hello world'; 10;
变量的声明
直接量只是昙花一现,但是如果能用var定义一个变量,再将它指向那个直接量,就能有保存数据的妙用了。
- 定义了一个变量。
var a;
- 变量a指向了这个字符串。
var a; a = "hello world";
- 先定义变量,然后指向一个字符串,这种操作分成了两步,直接合并为一句更好。
var a = "hello world";
数据类型
在JavaScript中,数据可分为两类,分别为原生数据类型(primitive type)和对象数据类型(object type)。
- 原生数据类型包括数字、字符串、布尔值,还有两个特殊的类型:null和undefined。
var num = 6;
- 布尔值,它是一种只有true和false两种状态的类型。
- null和undefined,从用法上来看,
null和undefined都代表了直接量的空缺
,如果一个变量指向了其中任何一个,都代表false的含义,也表示没有或空的概念。而从根本意义上讲,undefined要比null更加严重一点,代表本不应该出现的错误
,比如我刚才定义了一个变量a,但是我没有把任何直接量赋给它,那么a就默认指向了undefined;而null不同,有的时候,我需要给某些变量赋值null,以达到清空的目的。
拓展阅读
JavaScript包括直接量和变量。首先说直接量,什么是直接量呢?在JavaScript的世界里,直接量包含数值(如10/20)、逻辑值(true/false)、字符串(如“nihao”)、null、undefined、对象和函数。
- 其中,函数也称方法,对象和函数会在之后的章节中慢慢介绍。你暂时可以认为
对象是存放数据的一个容器
,而函数是应用程序处理某一系列逻辑的一个过程设计
。 - null是一个特殊的关键字,
表示没有值
;null也是一个原始值,因为JavaScript是大小写敏感
的,所以null和Null、NULL或者其他变量是有区别的。 - undefined是一个顶级属性,它
代表某一个变量未定义
。同样,undefined也是一个原始值。
说完直接量,再来说变量。所谓变量,就是指向了某个直接量或者其他变量的“钥匙”。比方说,把一个直接量true比作一扇门,然后定义一个变量flag,最后通过赋值运算符“=”将这个true赋值给flag
,这样就完成了一个变量的绑定
。从此以后,你在别处使用变量flag,也就相当于使用了直接量true。简单来说,就是这么回事。
对象数据类型
所谓对象数据类型,是一种复合型的数据类型
,它可以把多个数据放到一起,就好像一个篮子,这个篮子里面的每一个数据都可以看作是一个单元,它们都有自己的名字和值。
- 这是创建对象的一种方式,也是最常用的方式。创建对象以后,就相当于开辟了一块内存,对象包含若干数据,每个数据都有自己的名字和值。对象好比是一个容器,现在我要在这个容器里面放一个数据。
var container = { caoyao : "草药" }; //在这个例子中,caoyao叫作键,草药叫作值,它是一种键值对的形式。 //一个键对应一个值,一个键和一个值就凑成了一对,键和值中间用冒号。
- 如果你想要在一个对象里面添加新的数据,则只需要添加一个逗号,然后写上新的键值对就行了。
var container = { caoyao : "草药", feijian : "乌木剑" };
- 上面演示的方式是在创建对象的时候立刻在对象里面设置键值对。其实还有其他办法,那就是在对象创建之后,在外面对这个对象的变量进行操作。
var container = {}; container.caoyao = "草药", container.feijian = "乌木剑" //container.caoyao中的点(.)就是对象访问属性的意思, // 正因为caoyao是container的属性,所以container才可以用点(.)。 // 对象包含若干数据,每个数据都是一个键值对,这些数据也叫作对象的属性。 // 那么键值对中的键就是属性名称,键值对中的值就是属性值。
- 如果我直接访问一个根本不存在的属性danyao,
var container = {}; container.caoyao = "草药", container.feijian = "乌木剑" console.log(container.danyao); //注意danyao属性不存在。 结果是undefined。 //danyao这个属性不存在于container对象中,因此它是未定义的,得到的结果就是undefined!
对象的取值
- 对象可以通过一个点号(.)访问其中的某一个数据
var container = {}; container.caoyao = "草药", container.feijian = "乌木剑" console.log(container.caoyao);
- 如果遇到这种情况,即事先不知道调用的属性叫什么名字,那么该如何用一个变量定义属性呢。
var container = {}; container.caoyao = "草药", container.feijian = "乌木剑" var prop = "caoyao"; console.log(container[prop]); //对象不仅可以用点号(.)访问它的一个属性,也可以用中括号([])。 //如果用中括号,里面就允许再写一个变量。当然了,写字符串也是可以的。 //console.log(container["caoyao"]); /*如果事先属性的名称未知,或者调用的属性是动态变化的,就不能使用点号了。 使用中括号可以最大程度地提升对象调用属性的灵活度!*/
循环遍历的奥妙
如果你希望一遍又一遍地运行相同的代码,并且每次的值都不同,那么使用循环是很方便的。
- 使用for循环
for(var i=0; i<10; i++){ console.log(i); }
- 使用while循环
var i = 0; while(i<10){ console.log(i); i++; }
i++是自增运算符,表示把当前的变量自增一个单位。而++i和i++是有区别的,前者代表先自增一个单位,再运算;后者相反,表示先运算,再自增一个单位。但是由于这段代码中的i++占单独一行,没有对i进行使用,所以不管是++i还是i++,只要这句话执行完毕,i的值都会自增。
while循环和for循环除了语法还有什么区别
for循环中有一个小括号。小括号里面有3个表达式,分别为“vari=0”,“i<10”还有“i++”。
- 第1个语句是在循环开始之前执行的,“var i=0”的意思是定义了一个变量i,是整数,初始值为0。
- 第2个语句是“i<10”,表示进入循环体的条件。循环体就是那个用大括号({})扩起来的部分。
- 最后一个语句“i++”,这个语句是在刚才我们所说的大括号里面的代码被全部执行之后才会被执行的。一般来说,上面这段语句里面的代码可以控制循环变量i自增一个单位或者自减一个单位。
调试代码
- 语句1执行完毕后就自然会执行语句2了,也就是“i<10”这句话,这就好比是一个if判断。
var i = 0; if(i<10){ console.log(i); }
while循环只是在语法上有所不同,其作用和for循环是一样的。
小结
下面是for循环的语法。
while循环会在指定条件为真时循环执行代码块。
对象内容的遍历
- typeof关键字,可以得到变量的类型。
var a = "123"; var fun = function{}; console.log(typeof(a)); console.log(typeof(fun)); /*a是一个字符串,所以typeof出来就是string; fun是一个函数,所以typeof出来就是function。*/
- 如何遍历一个对象。首先,新建一个简单的JavaScript对象。
var fun = { name = "叶小凡"; age = 16; eat:function(){ container.log("KFC"); } };
- 然后使用for循环进行遍历。
for(var i in fun){ container.log(i + " = " + fun[i]); } //既然有了属性名称,那么对象可以用点(.)的方式直接获取属性的值。 //当然,用中括号([])也是可以的。
- 运行结果如下。
因为遍历出来的属性名称是不确定的,而是用一个i变量指代,既然是变量,自然不可以用点号。
一旦遇到这种属性名称不确定的情况,就只能用一个变量代替,换句话说,不能用点号,只能用中括号。因此,当对象访问属性的时候,用中括号是更加灵活的。
JavaScript运算符
- JavaScript运算符,赋值运算、加、减、乘、除和取余数。
var a; //a=undefined var b = 10; //赋值运算符用于给JavaScript变量赋值 var c = 2; var s1 = b+c; //s1=12; var s2 = b-c; //s2=8; var s3 = b*c; //s3=20; var s4 = b/c; //s4=5; var s5 = b%c; //s5=1;取余数的意思是一个数字除以另一个数字,除不尽的部分就是余数。
- 还有自增运算符和自减运算符。自增运算符是++,自减运算符是 --。顾名思义,自增和自减运算符可以使得当前的变量自增一个单位或者自减一个单位。
var a = 1; var b; //b = undefined; var s1 = (b = a++ + --a) + a-- + b++; // b = a++ + --a ; => b = 2; a = 1; // s1 = (b = a++ + --a) + a-- + b++; => s1 = 5; a = 0; b = 3;
JavaScript数组
在JavaScript中,数组是一个非常灵活的类型。简单来说,数组就是一个容器,可以存放一个或者多个对象
。当然,这些对象的类型是没有限制的,不管它是什么,数组都可以存放。
数组有4种定义方式
第一种是用直接量创建数组,剩下的3种都是用构造函数创建数组
。其实用起来的话,还是第一种方式最好用,它是最简单的一种方式。
- 所谓直接量定义,就是用一对中括号声明一个数组对象。
var arr = ["first","second","third"]; console.log(arr);
- 采用构造函数的方式创建的一个数组对象,
在JavaScript中,每个类型其实都有一个函数作为支撑
,数组也不例外。在这个例子中,Array也叫作构造函数。1. var a = new Array(); //直接用构造函数创建一个空的数组 2. var b = new Array(8); //在创建的同时设置了一个初始的长度 3. var c = new Array("first","second","third");//在创建数组对象的同时就给它赋予了初值。 console.log(a.length); // 0,数组天生就拥有一个length属性; console.log(b.length); // 8,没错,函数可以打括号,打括号的意思是执行这个函数的函数体。 console.log(c.length); // 3,在创建数组的时候给它添加了3个元素。 console.log(b); //结果是[<8 empty items> ] //数组只有一个属性,就是length。length表示数组所占内存空间的数目,而不仅仅是数组中元素的个数。
数组方法
- push方法,它可以把一个元素添加到数组里面。把数组想象成一个长长的盒子,我如果想要给数组添加新的元素,就可以用这个方法。
var b = new Array(8); b.push("苹果"); b.push("香蕉"); b.push("牛油果"); console.log(b); //直接用push方法,那么元素就会被添加到数组的尾部,而且原来的8个位置无法占用,会直接跟在后面。
- 数组本身有写数据的能力,只要给数组变量加上一对中括号,然后在中括号里面写上对应的下标位置,就可以给对应的内存空间塞入数据啦。
var b = new Array(8); b.push("苹果"); b.push("香蕉"); b.push("牛油果"); b[0] = "黄瓜"; console.log(b);
删除数据需要用到数组的splice方法或者pop方法
。先说pop方法,这个方法可以删除数组尾端的元素。var b = new Array(8); b.push("苹果"); b.push("香蕉"); b.push("牛油果"); b[0] = "黄瓜"; b[0] = "西瓜"; b.pop(); //删除数组尾端的元素. console.log(b);
- pop方法会默认删除数组中的最后一个元素。可以这么认为,先进入数组的后删除,后进入数组的先删除。
- 第二种,就是splice方法。splice方法的作用是
插入、删除或者替换数组元素
,它不仅会在原有的数组上进行修改,还会返回被处理的内容,因此这是一个功能强大但不容易使用的方法。splice方法用前两个参数进行定位,余下的参数表示插入部分
。var a = [1,2,3,4,5]; a.splice(2,1); console.log(a); //运行结果为[1,2,4,5 ]。 //splice方法的第一个参数代表需要操作的数组的起始位置,因为数组的下标位置默认从0开始。 //splice方法的第二个参数代表要删除元素的个数。
- 如果要把数字3替换成数字38,并且再在38的后面加一个元素66。
var a = [1,2,3,4,5]; a.splice(2,1,38,66); console.log(a); //运行结果为[1,2,38,66,4,5 ]。
- join方法可以把数组中的所有元素放入一个字符串。元素是通过指定的分隔符进行分隔的,而这指定的分隔符就是join方法的参数。
var a = [1,2,3]; var str = a.join(","); console.log(str); //运行结果:1,2,3。
JavaScript函数
函数是一组可以被重复调用的代码语句。
函数七重关之一(函数定义)
- 第一种方法。
function test(){ alert("函数被调用了!"); } /* 函数的定义需要用到function关键字,函数名字是test,小括号里面是用来放参数的。 也就是说,函数里面如果需要用到一些从外面传进来的数据,就可以通过参数变量做传递。 最后就是函数体了,用大括号扩起来的部分就是函数的函数体。 */ test(); //函数的调用方法就是在函数名称右边加一个小括号,表示要去执行函数的函数体了。
- 第二种方法。
var a = function(){ document.write("This is My function"); //document.write方法表示用JavaScript向页面输出一段话。 } /* 第二种定义函数的方法需要先定义一个变量,比如‘var a’, 然后还是用function关键字定义一个函数,再把这个函数赋值给变量a。 因为最后要赋值给变量a,因此这里在定义函数的时候就不需要加上函数的名字了,这就是其中的一个区别。 用这种方法定义出来的函数,函数的名字就是变量的名字,也就是说,我要想调用这个函数,就要这样做。*/ a();
- 区别
第二个区别就体现在函数的调用上。
举一个例子test(); function test(){ document.write("This is My function"); } // 代码运行后,成功在页面上打印出:This is My Function! a(); var a = function(){ document.write("This is My function"); } // Uncaught TypeError: a is not a function /* 如果是用第一种方法定义的函数,它会被提前加载,因此调用语句可以写在函数的定义之前,因为那个时候函数已经被加载完毕了。 而用第二种方式定义的函数是不会被提前加载的,必须要执行到函数定义的语句才会加载这个函数,正因为这个道理,刚才的代码才会直接报错。 因为在调用a函数的时候,a函数还没有加载,强行调用一个不存在的函数自然是不被允许的!*/
注意:console.log(a); var a = function(){ alert("函数被调用了!"); } console.log(a); //第一个a打印出来是undefined,表示未定义。 //第二个a打印出来就是具体的函数了,说明这个时候函数a已经被加载完毕!
函数有没有被加载与变量有没有被定义是不同的事情。
函数有没有被加载,可以看成function有没有被赋值给变量a。console(apple); // ReferenceError: apple is not defined
JavaScript编译原理
- JavaScript代码在运行之前会经过一个编译的过程,而编译有三个步骤。
var a = 10; //第一个步骤是分词,得到的结果就是‘var、a、=、2、;’。 //第二个步骤是解析,由JavaScript编译器对刚才分词得到的一个个代码块进行解析,生成一棵抽象的语法树(AST)。 //抽象语法树定义了代码本身,通过操作这棵树可以精准地定位到赋值语句、声明语句和运算语句。 //最后一个步骤,就是代码生成。最终生成出来的就是一些机器指令,创建了一个叫作a的变量并放在变量区, //然后分配一些内存以存放这个变量,最后将数字10存储在了变量a所在的地方。
通过JavaScript的解析器把它解析为一棵抽象树。
- 首先是最顶层的大节点,也就是这棵树的顶端,上面清清楚楚地写着Program body,代表我们写的代码是一个程序。然后看这个程序里面的第一个也是唯一的一个子节点,上面清清楚楚地写着VariableDeclaration,意思就是变量声明。var a =10;这句话是一个程序,程序的目的是进行一个变量的声明。
- 在VariableDeclaration节点中包含两个子节点,一个是declarations[1],另一个是kind。declarations[1]是声明数组,中括号里面写了一个1,表示这个语句只声明了一个变量。kind代表种类,表示用var关键字声明一个变量。
- 展开里面的子节点,可以看到里面分为id和init两个子节点,id代表变量名,identifier是标识符,代表我们的变量名,也就是a。init表示变量的初始化操作,从语句上也能看出,它是将10赋给变量a。
- 如果没有给变量a赋值,那么JavaScript的解释器也会给变量a赋一个初始值,null代表空。注意:这里的null不要理解为JavaScript里面的数据类型null,而是语义上的空。实际上,在代码执行的时候,变量a的值是undefined。
- 代码中多了一个console.log输出语句,在生成的抽象语法树上,又结出了一个新的果实——ExpressionStatement(表达式语句)。表达式语句就是普遍意义上的一行JavaScript代码。console是一个内置对象,log是console对象的一个方法,变量a作为参数传入了log方法。总体来说,这就是一个函数的调用语句。
var a; console.log(a);
小提示:抽象语法树的创建可以在网站https://esprima.org/demo/parse.html上自行调试和验证。
函数七重关之二(作用域)
- 在JavaScript中,作用域分为两种,一种是全局作用域,另一种是函数作用域。
所谓作用域,就是指当你要查找某一个变量的时候,你可以在什么范围内找到这个变量。这个寻找的范围,就是作用域
。不管是全局作用域还是函数作用域,都被定义在词法阶段。词法阶段就是刚才所说的JavaScript编译代码的第一个步骤——分词。所以,词法阶段也叫作分词阶段。function test(){ console.log(a); } var a = 10; test(); //10; /*变量a和test函数都直接暴露在外面,都属于全局作用域。 而test函数的函数体,即用花括号包起来的部分(函数的函数体)则是函数作用域。 又因为test函数属于全局作用域,而它自己又拥有一个函数作用域,那么这样一来,就形成了一个作用域的嵌套。*/
- 当发生作用域嵌套的时候,只能里面的访问外面的,外面的无法访问里面的。而且需要注意一点,那就是作用域嵌套一般是针对全局作用域和函数作用域,或者是函数作用域和其他函数作用域而言的。
if(false){ var a = 20; } console.log(a); //a = undefined; /*var a = 20;这句话在if判断中,而if判断的条件是false,所以这句话的确不会执行。 但是,执行代码是在运行阶段,在代码的分词阶段和解析阶段,变量a依然会被获取,并且系统会默认给它一个undefined。 又因为变量a不是在某一个函数的函数体中,而是在全局作用域里面, 所以console.log方法依然可以访问这个变量,因此获取变量a的值就是undefined。*/
- 举例
var a = 10; function test(){ var a; function inner(){ console.log(a); } inner(); } test(); //undefined; /*在函数作用域里面嵌套了函数作用域,那么在最里面的inner函数中访问一个变量,就会优先在inner函数里面寻找,在当前函数作用域里面找不到, 那么就往上翻一层,在它的父级作用域,也就是test函数的作用域里面寻找,结果发现找到了。test函数里面定义了一个变量a,但是没有赋值, 那么a就是undefined。既然已经找到了,那么就不会去全局作用域里面寻找变量a了。所以,全局作用域里面的变量a其实就是一个摆设。*/
函数七重关之三(参数传递)
所谓的参数,就是指当函数调用的时候会传进来的值,也就是说,我们在定义参数的时候并不知道调用的过程中会有什么样的值传过来。
- 参数传递
function add(a,b,c){ var sum = a + b + c; console.log(sum); } add(1,2,3); //6 //sum是定义在add函数的函数体内部的,外面的全局作用域是没有办法直接访问函数作用域里面的sum变量的。 //只能在该函数的函数体内被访问,它也被叫作局部变量。
- 调用函数的时候少传了参数。
function add(a,b,c){ var sum = a + b + c; console.log(sum); } add(1); //结果是NaN,代表无法计算。a的值是1,b和c的值就是undefined, //任何变量在被赋予真正的值之前,其在编译阶段都是undefined。 //函数的参数可以被理解为一种预备变量,传参的过程就相当于是给预备变量赋值的过程。
- 调用函数的时候多传了参数,在函数里面访问额外的参数。
代码运行结果function add(a,b,c){ console.log(arguments); var sum = a + b + c; console.log(sum); } add(1,2,3,4); // 结果是6,强行加了第四个参数,对结果也不会有什么影响。 //其实所有的参数都会被装载到函数内部一个叫作arguments的数组里面。
- 参数个数任意,实现数字的累加。
function add(){ if(!arguments[0]){ return 0; } for(var i = 1; i < arguments.length; i++){ arguments[0]=arguments[0]+arguments[i]; } console.log(arguments[0]); } add(1,2,3,4); //因为在实现之前并不知道会有几个参数传进来,所以干脆就不设置任何参数了。 //先用arguments数组获取第一个位置的元素。如果第一个位置的元素不存在,那么就返回0。 //在函数的函数体中可以通过arguments数组的length属性预知未来传入参数的个数。
函数七重关之四(闭包)
一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure),也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。
在 JavaScript中,每当创建一个函数,闭包就会在函数创建的同时被创建出来,作为函数内部与外部连接起来的一座桥梁。
-
函数返回值。
function add(){ var sum = 0; for(var i = 0; i < arguments.length; i++){ sum = sum + arguments[i]; } return sum; } var sum = add(1,2,3,4); /*sum是函数作用域里面的变量,因此也叫局部变量,外面是没有办法直接访问这个局部变量的, 除非把sum作为返回值返回出去,外面才可以访问sum变量。*/
-
典型的闭包
function test(){ var a = 0; return function(){ //匿名函数 console.log(a); } } //因为test函数返回的结果是一个函数,不去调用的话就不会执行里面的代码,需要执行内部函数的函数体。 test()(); //第一个小括号是调用test函数,这个test函数中定义了一个局部变量a,还返回了一个内部函数。 //第一次调用的结果就是返回一个内部函数,而第二个圆括号才会调用那个内部函数。”
总结一下产生闭包的条件。
第一点,在函数内部也有一个函数。就好比这个例子,在test函数里面还有一个函数。
第二点,函数内部的函数里面用到了外部函数的局部变量。还是这个例子,test函数里面有一个局部变量a,并且被内部函数使用了。
第三点,外部函数把内部函数作为返回值return出去了。 -
使用了闭包,那么就会让这个局部变量不随着原函数的销毁而销毁,而是继续存在。利用闭包操作可以减少很多不必要的全局变量。
function test(){ var a = 0; return function(increment){ a += increment; console.log(a); } } var inner = test(); //先获取这个内部函数。 inner(1); //第一次调用内部函数 inner(1); //第二次调用内部函数 inner(1); //第三次调用内部函数 //代码执行结果分别为1、2、3。 /*正常情况下,我们调用一个函数,其里面的局部变量会在函数调用结束后销毁,这也是我们在全局作用域里面无法访问函数局部变量的原因。 给内部函数设置一个累加的参数,在每次调用内部函数的时候都把这个参数的值加上。证明打印出来的是同一个变量。*/
函数七重关之五(自执行函数)
只想执行一个函数,却无所谓这个函数叫什么名字。那么在这种情况下,就可以考虑使用自执行函数了。
-
自执行函数的格式 。
语法: (定义一个没有名字的函数)()
( function(){ console.log(123); } )(); //所谓自执行函数,顾名思义,就是在定义之后就立刻执行的函数,它一般是没有名字的。 //也正因为自执行函数没有名字,所以它虽然会被立刻执行,但是它只会被执行一次。
-
自执行函数一般可以和闭包配合使用
var inner = (function() { var a = 0; return function(increment){ a += increment; console.log(a); } } )(); inner(1); //第一次调用内部函数 inner(1); //第二次调用内部函数 inner(1); //第三次调用内部函数 //直接得到闭包环境下的内部函数了,外部函数只是为了产生闭包环境而临时定义的函数,正因为如此,所以根本没有必要给外部函数取一个名字!
函数七重关之六(“new”一个函数)
- this永远指向当前函数的调用者。
function hello(){ console.log(this); } window.hello(); //首先,这句话透露出的第一个信息是,this要么不出现,一旦出现,就一定出现在函数中。 //第二个信息是,this指向函数的调用者,换句话说,这个函数是谁调用的,那么this就是谁。 //我们调用hello函数,其实也就是window对象调用了这个hello函数。hello函数里面的this自然就指向了window对象。
- 在调用函数的时候使用了new。
function hello(){ console.log(this); } new hello(); //hello函数内部产生了一个新的对象,也就是hello函数的真实调用者——this关键字指向的那个对象。函数默认返回了这个新的对象。 var newObject = new hello(); console.log(newObject); //newObject就是函数里面的this,也就是函数内部新产生的那个对象了。 //这种函数叫作构造函数。通过这种方式,我可以通过构造函数构建一个对象模板。
- 所谓对象模板,就是指用一个函数的形式设计一种对象的种类。
function Fruit(name,smell,color){ //一般来说,如果这是一个构造函数,那么首字母就需要大写。 this.name = name; this.smell = smell; this.color = color; } var apple = new Fruit("苹果","香甜可口","红色");
- 自定义对象
var apple2 = { name : "苹果"; smell : "香甜可口"; color : "红色" }
- 用构造函数定义对象是有优势的。比如我需要2个苹果,使用构造函数的话,直接调用两次new函数就行了,可以非常方便地获得两个苹果。而使用大括号的方式就得写两次。
var apple1 = new Fruit("苹果","香甜可口","红色"); var apple2 = new Fruit("苹果","香甜可口","红色"); var apple3 = { name : "苹果"; smell : "香甜可口"; color : "红色" } var apple4 = { name : "苹果"; smell : "香甜可口"; color : "红色" } var apple5 = apple3; //apple3和apple5其实都是指向同一个苹果的。 var a1 =10; var a2 = a1; /*a1和a2还是不同的数据,虽然都是10,但是在内存上却处于不同的空间。 而引用数据类型则不同,如果简单地分出一个变量区和内存区,apple3和apple5就都属于变量区的两个不同的变量了,但是却指向同一块内存地址,也就是真实的对象地址。 这样一来,不管是apple3还是apple5,它们都拥有操作这一块内存区域的权限,也就是说,它们都可以修改真实对象的属性值。*/
函数七重关之七(回调函数)
所谓回调函数,就是指把一个函数的定义当作参数传递给另一个函数。
- 回调函数
function addSqua(num1, num2, callback){ var sum = num1 + num2; return callback(sum); } function squa(num){ return num*num; } let num = addSqua(1, 2, squa); console.log(num); //=>9
- 匿名回调函数
function addSqua(num1, num2, callback){ var sum = num1 + num2; return callback(sum); } let num = addSqua(1, 2, function squa(num){ return num*num; }); console.log(num); //=9
精度问题,化浮为整
- 计算0.1+0.2。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
function add(num1, num2) {
// 将数字换成字符串;
num1 = num1.toString();
num2 = num2.toString();
// 获取小数点位置;
var index1 = num1.indexOf(".");
var index2 = num2.indexOf(".");
//如果小数点存在,则获取各自的小数位数。
var ws1 = 0;
var ws2 = 0;
if (index1 != -1) {
ws1 = num1.split(".")[1].length;
}
if (index2 != -1) {
ws2 = num2.split(".")[1].length;
}
//获取小数位数大小;
var bigger = (ws1 > ws2) ? ws1 : ws2;
var smaller = (ws1 < ws2) ? ws1 : ws2;
//计算得到需要补齐的0的个数
var zerosCount = bigger - smaller;
//去除小数点;
num1 = num1.replace(".", "");
num2 = num2.replace(".", "");
//比较num1 和 num2;看谁是smaller,是smaller的一方需要补0;
if (ws1 == smaller) {
for (var i = 0; i < zerosCount; i++) {
num1 += "0";
}
} else {
for (var i = 0; i < zerosCount; i++) {
num2 += "0";
}
}
//开始计算
var num = parseInt(num1) + parseInt(num2);
//根据较大的小数位数计算倍数
var beishu = 1;
for (var i = 0; i < bigger; i++) {
beishu = beishu * 10;
}
num = num / beishu;
return num;
}
console.log(add(0.1, 0.2));
</script>
</body>
</html>