对象就是值的集合,任意值的集合。值包括所有5种基本类型以及3种引用类型的值。 为了方便访问值,就需要给值取个名字,这个就是属性。值和键在一起就形成了键值对。对象就是由一个又一个的属性所构成的。 对象无法直接使用,需要将它赋值给一个变量。 对象中的函数可称为方法。方法也是属性。 尽量少使用全局变量。 若是没有对象,就只能挨个声明全局变量,变量一多,容易造成命名冲突,而且分散。
创建对象的方式: 1.var cat = new Object();//实例化构造函数创建对象 2.var cat = {};//字面量创造对象,使用得最多 3.ES5下有个方法:Object.create();//老版本浏览器会存在兼容问题
使用对象:对对象进行读写 提取属性并赋值: 1.cat.name = ‘Tim’; 2.cat[‘name’] = ‘Tim’;//使用此方法方括号内部必须加引号。
创建新属性: cat.type = ‘加菲猫’;
读取属性: console.log(cat.name); console.log(cat[‘name’]); console.log(cat.type);
删除属性: delete cat.type; console.log(cat.type);//undefined 没有值的属性是undefined,不会报错。
检测对象是否有某属性: 属性名 in 对象名;//属性名需要加引号。 console.log(‘name’ in cat);//返回true。
遍历对象中的各个属性: for( var p in cat ) {//这里的的p代表属性名property,可取任意名字。 console.log§;//这里打印的是属性名; console.log(cat[p]);//这里打印的是属性所对应的值。方括号内可做运算,字符串拼接等。方括号比点语法的功能更强大,点语法后面只能跟表示字符串的,方括号是可以做运算的。 console.log(cat.p);//等同于cat[‘p’]; 相当于cat = {‘p’:‘123’} cat中有个属性名为p的属性,所以返回undefined。我们需要得到的是p这个变量所代表的值。 }
一次定义,n次调用. 1.function关键字:作用就是告诉js解析器(浏览器),这里是一个函数,必写。函数保存在内存中。 2.函数名:区分不同函数,通过函数名来直指函数在内存中的位置。没有名字的函数是匿名函数,有名字的叫命名函数。 3.参数:可以什么都不写,可以写一个或多个,用逗号分隔。js中传入的参数可以和函数定义上形参的数目不一致。 4.函数体:局部作用域。 return有两层含义:其一表示函数结束(不要把代码写在return之后,是不会被读取到的),其二将return后面的内容返回
函数定义和调用时发生了什么: 定义:封装了代码,window下面多了个属性,在局部作用域销毁后,该属性,节点仍可以调用。 调用:创建了局部作用域,接着赋值,执行,执行完毕后,销毁局部作用域,局部变量一块被销毁,第二次执行,会创建新的局部作用域,和之前不是同一个作用域。 为什么要使用函数: 1,复用代码。(自己和他人) 2,统一修改和维护。 3,增强代码可读性。
什么时候该使用函数: 1,当程序中有太多相似或相同的代码。 2,程序暴露过多细节,可读性变差的时候。
函数的本质: 1.可调用。 2.函数其实是对象。很多像js的语言才有。
对象定义的方式: 1.字面量的方式{}: function add(num1,num2){}; 2.构造函数的方式new Object(): new Function(‘num1’,‘num2’,’…’);
添加属性和方法: 对象添加属性和方法:
var person = {}; person.name = 'xm'; person.setName = function (name) { this.name = name; }函数添加属性和方法:
function add(num1, num2) { return num1 + num2; } add.sex = 'male'; add.setSex = function (sex) { this.sex = sex; } console.log(add.sex);//male console.log(add.setSex('female'));//undefined,函数内部只是发生了设置,没有返回值,这里输出的是返回值。 console.log(add.sex);//female console.log(add(1, 2));//3我们可以通过给函数添加属性,来保存一些特定的值,来缓存一些内容。
作为数据值使用:
var add=function (){};---匿名函数,负值给一个变量add。作为参数:
setTimeout(function(){},1000} //或者 function fn(){}; setTimeout(fn,1000);作为返回值:
function fn(){ return function(){console.log(1);}; var newFn=fn(); newFn(); 或者直接写fn()();三种定义方式: 1.字面量 function声明
function add() { // body... } //这里是一个声明,不是一个语句,所以是不用加分号的。 add();//调用2.var赋值表达式
var add = function (argument) { // body... }; add();//调用 var add = function fn(argument) { //这里函数有两个名字。但是这里的fn就变成了局部变量,只能在函数体内找到调用,在函数体外调用会报错。 // body... fn(); //作为局部变量,在函数体内可以调用。 add(); //add不仅可以在函数体内调用,也可以在函数体外调用。 }; add(); fn();//报错,是局部变量,只能在函数体调用3.构造函数(效率差一些,因为需要解析字符串,之后还要实例化这个函数)
var add = new Function('num1', 'num2', 'return num1 + num2;');//括号内的参数和函数体一定要用字符串的形式,加上引号包裹。 add();上面3种方法是的区别: 构造函数的方式效率会差一些,声明的过程实际分成了两步:先解析字符串,再实例化函数。所以一般不选择用构造函数的方式。 字面量的方式和用var赋值表达式的方式在预解析过程中存在很大区别,字面量的方式在预解析过程中是当做函数function对象,在预解析过程中就声明;而var赋值表达式是当做变量对待,预解析过程赋值undefined。 所以字面量的方式就将声明提前,在之前之后调用都可以。
根据团队风格选择方式。
函数定义的位置: 1.定义在全局作用域。 2.定义在函数作用域内,沿着作用域链向外查找。 3.if / for 中的代码块,js中相当于是全局的。不推荐在if / for 的代码块中声明函数,放在外面去。 4.定义在对象中。
var person={ name:'xm', setSex:function(sex){ this.sex=sex; } }; person.setName=function(name){ this.name=name; } person.setSex();//需要用对象.函数的方式调用函数定义在对象中,可以通过键值对的方式直接定义,也可以通过对象.的方式增加属性,然后用函数赋值。 这种情况下,不能直接通过函数名来调用,需要用对象.函数的方式调用。
匿名函数的调用方式: 1.将匿名函数赋值给变量,然后通过变量直接调用。
var add = function(){ //body... }; add();错误的方法:
function(){}();//这种方式会报错function语句需要函数名。其实理论上这种方式是可以让函数执行的,但是因为js解析器的原因导致它不能执行。 这个是由于Js解析器的问题,只要是以function开头,那么在预解析过程中就会将它视为函数声明,就不会认为是声明加执行。而这里先声明后执行就是一个语句了,但是在预解析过程中不会被当成语句,只会认为是声明,所以不能自我执行。
解决办法:不要以function开头,可以加一些无关紧要的合法的前缀。(不能用@) 2.将function赋值给变量,通过变量自调用。
var add = function(){}();//匿名函数的自我执行 (function(){})();//在function函数体外包裹括号。函数是一个对象,相当于把一个对象用括号包裹,这里是无意义的操作,但是避免了以function爱投。 4.
(function(){}());也可以把整个用括号包裹住,跟上一个效果相同,只不过这里是先执行完再加括号。 5.一元运算符在前面开头;
!+-~function(){}(); function(){}());//执行会报错但是console.log(function(){}())就能执行,因为这就不是以function开头了。
递归调用(自己调用自己):
递归最经典的例子就是计算阶乘。 5!= 54! 4!= 43! …
function factorial(num){ if(num<=1) return 1; return num*factorial(num-1)}; } console.log(factorial(5));我们使用递归,在某种程度上可以代替循环。 递归调用就是自己调用自己,但切记一定要有终止条件,否则函数将无限递归下去。
特殊函数的调用,也就是方法的调用: 1.声明在在对象中的函数(也就是对象中的方法),必须用对象加点的方法调用: object.functionName();
var operation = { add: function (num1, num2) { return num1 + num2; }, subtract: function (num1, num2) { return num1 - num2; } }; operation.add(1, 1);//还有一种常见的事件的声明的形式。比如点击事件就相当于把匿名函数赋值给document对象中的onclick属性,也就变成了onclick方法。
document.onclick = function () { console.log('你点击了文档!'); }; document.onclick();//也可以写句调用代码,手动模拟调用。当你在浏览器中进行点击,浏览器就自动调用了点击事件。
2.对于对象中的属性什么时候要加引号,什么时候不加引号呢? (1)如果属性是合法的标识符加引号和不加引号都可以。 合法的标识符:以字母、数字、下划线以及$符组成的标识符,同时不能以数字开头。变量就是这样组成的,这样就是合法的标识符。 (2)如果对象里面的属性含有不合法的标识符,如@,就必须加上引号。
var operation = { add: function (num1, num2) { return num1 + num2; }, subtract: function (num1, num2) { return num1 - num2; }, '@': function () {//非法字符串需要引号,冒号右侧除了字符串要用引号,其他可以不用。 console.log('@'); }, key: function () { // body... } }; console.log(operation.add(1, 2)); console.log(operation['@'](1, 2));//非法字符串传入,要给字符串加上引号,不用点的方式调用,而是外面再加上方括号调用。 //注意:点只能用来调用合法的标识符方法;一旦不合法,就要用方括号,方括号对于合法或不合法的标识符都适用。 var key = 'add';//声明一个变量为'add' console.log(operation[key](1, 2));//如果想要用变量调用对象中的方法,也是用方括号[],所以如果这里传变量,也是用方括号。3.方法的链式调用。
$('p').html('段落‘).css('background','red';)....; //实现对象里面函数的链式操作可以在每个函数最后return this,意思返回对象本身。但是不建议链太多,一旦某个节点出问题可能整个程序都崩溃,所以适当使用。 var operation = { add: function (num1, num2) { console.log(num1 + num2); return this;//链式调用返回自身对象,this指代operation }, subtract: function (num1, num2) { console.log(num1 - num2); return this; } }; operation.add(1, 2).subtract(2, 1);//相当于operation.subtract(2, 1);构造函数的调用:通过new调用。
//普通函数 function add(){ //body... } //构造函数(习惯性将构造函数名大写,以便于和普通函数区分,但这不是硬性要求。) function Person(){ //body... } var num=add();//调用普通函数,普通函数调用之后不管有没有写return,都会有一个返回值,没写return就返回undefined;写了返回值就返回返回值。 var obj=new Person();//调用构造函数,构造函数调用后不管怎么样,都会返回一个对象new 构造函数();//执行完后会返回一个对象,实例化一个函数,生成一个对象。
Js中常见的内置构造函数: Object();//本身是函数,只有通过new Object()后生成对象。 Array();//本身是函数,只有通过new Array()后生成对象。
普通函数调用、方法调用、构造函数调用都是直接调用。找到它本身,然后加个括号。
函数的间接调用: 每一个函数下面都有call方法和apply方法,可以通过它们间接调用。
var name='xm'; var person={}; person.name='xh'; person.getName=function(){ return this.name; }; console.log(person.getName());//xh console.log(person.getName.call(window));//xm,指定this指向的值(由person--->window) console.log(person.getName.apply(window));//xm,指定this指向的值(由person--->window) function add(num1,num2){ return num1+num2; } console.log(add(1,2));//3 console.log(add.call(window,1,2));//3 console.log(add.apply(window,[1,2]));//3 //注:apply的效果在于: var datas=[1,2]; console.log(add.apply(window,datas);//31.call() call方法和apply方法的第一个参数,可以改变this的指向,指定this指向的值,两个方法的第一个参数效果一样。 call方法和apply方法的区别在于第二个参数,call方法传参数是一个一个地传,而apply方法是把参数作为一个数组传。 要注意这时候,在声明函数的时候就不用写形参。
call和apply用处一: 当参数有多个,for循环一个一个传很麻烦,所以当我们得到的参数是数组形式时,就可以直接将数组的变量传入apply。
call和apply用处二: call和apply相当于给函数加上了翅膀,非常强大,可以在继承中来使用它们来继承父类中的属性和方法。我们甚至可以借用其它对象的一些方法,比如找数组来借用数组操作的方法。
call和apply用处三: call和apply还可以帮助我们明确判断一个数据到底是什么类型。 js原生的typeof判断基本数据类型,引用数据类型判断不了; instanceof虽然可以判断引用数据类型,但是它返回的是一个布尔值,一个个判断,需要遍历循环。 而用call和apply就会很明确地判定是array数组还是object对象。
函数的参数类型:基本类型、引用类型。 参数传递的本质:将实参赋值给形参,形参只是一个占位符。
如果传递的参数是引用类型,相当于obj = person。person将地址告诉obj,此时person和obj指向同一个对象,这时对形参的修改就会影响到实参。但是不管怎样参数传递的本质都是将实参赋值给形参。
1.实参个数==形参个数。
add(1,2)2.实参个数<形参要求的个数。
add(1) //num1=1; //num2=undefined;当有可选参数时,实际传入的参数就可以比想要的要少。所谓的可选参数就是你可以选择性地传,可传可不传,不传也没有关系,会设置一个默认值。
function pow(base,power){ if(!power)power=2; //一般写成power=power || 2;这里是短路操作,返回的结果不是布尔值,如果power没有值,就返回2赋值给power。 return Math.pow(base,power);//pow(底数,幂数) } console.log(pow(3,2));//9 console.log(pow(2,2));//4 console.log(pow(3));//9 $('p')//可以省略第二个参数,第二个参数省略时为document。 $('p', document) $('p', document.getElementById('box')) //在id为box下选择p标签3.实参个数>形参个数
当我们想要一次传入多个参数都进行运算时,就会有实参个数>形参个数的情况。 这时候可以利用arguments类数组,arguments指代的是实参的值,用数组的形式保存。arguments[0]代表第一个值,以此类推。注意形参就一个都不用传,因为实参的个数是不能确定的。
function add(){ if(arguments.length==0)}return; var sum=0; for (var i=0;i<arguments.length;i++){ sum+=arguments[i]; } return sum; } console.log(add());//undefiend console.log(add(1, 2, 3, 4, 5));//15arguments是每一个函数中都有的,是一个局部的东西,它是一个类数组(类似数组的对象),并不是真的数组,其实是一个对象。 真正的数组能用push()方法的,而arguments用push()方法会报错。 arguments的真身如下:
{ '0': 1,//注意这里的0、1、2的属性一定要加引号,因为以数字开头,不是合法的标识符。 '1': 2, '2': 3, length: 3 }要注意的两点: 1.要注意改变arguments时会同时改变参数,因为它们指向的是同一个东西。
function fn(name){ arguments[0]='xh'; console.log(name); } fn('xm');//'xh'arguments里面的每一个数据和形式参数是一一对应的,相当于参数多了一个叠名arguments,形参和arguments都指向同一个值,一个修改了之后,另一个也跟着变化。所以不要在函数中随意将arguments的值进行更改。
2.arguments是不会跨函数的,每个函数中所独有,不同函数虽然都写成arguments,但实际上的都是不一样。
function fn() { console.log(arguments);//[1] function fn2() { console.log(arguments);//[2] } fn2(2); } fn(1);3.arguments.callee指代函数本身,经常用在递归调用的时候。 如果更改函数名,递归调用时内部相应用到函数名的地方也需要更改,可以直接让内部的函数名直接写成arguments.callee,这样就不用多处修改。 函数的调用也可以通过arguments.callee()调用。
function factorial(num){ if(num<=1) return 1; return num*arguments.callee(num-1)}; } console.log(factorial(5));但是要注意在严格模式下"use strict"; 不准使用arguments.callee,也不能省略var声明全局变量(会报错)。 这时候就可以用到之前觉得很鸡肋的写法:将函数赋值给一个变量,同时加上函数名,函数就有了两个名字,然后在函数内部使用函数名。
"use strict"; var num = 1; num = 1;//报错,严格模式下,必须使用var声明变量。 var jicheng = function fn(num) { if (num <= 1) return 1; return num * fn(num - 1); }; console.log(jicheng(5)); console.log(jicheng(4));4.判断实参和形参的个数是否相等:arguments.length != 函数名.length;(前者代表实参个数,后者代表形参个数)
function add(num1,num2){ if(arguments.length!=add.length) throw new Error('请传入'+add.length+'个参数!'); return num1+num2; } console.log(add(1,1)); console.log(add(1)); console.log(add(1,2,3));函数自带有length属性,表示形参的个数。 throw new Error(‘请传入’+add.length+‘个参数’);//手动抛出错误信息
1.什么都不传。 2.数字。 3.字符串。 4.布尔值。(建议一个函数只做一件事,不要在函数中根据判断来分别做什么事,而是把分别的事写成分别的函数,再来判断) 5.undefined(参数设置不合理时,比如不必要的参数设在第一个,必要的设在第二个,使用的时候,不能跳过第一个参数,所以第一个参数必须写,有时候会写undefined) 6.null。 (null和undefind的功能一样,当某个可选参数不需要传递值时,用来占位。并且建议把必须的参数放在前面,可选参数放在后面。) 7.数组。 (数组虽然可以一次传递多个参数,但是必须按照形参规定的顺序传递) jQuery的each方法就相当于一个for循环遍历,each方法更简洁。这里其实也是包含有把函数作为参数,这里称之为回调函数,回调函数表示函数不立马执行,而是当一定条件满足后再回过头来调用函数。
$.each([1,2,3],function(index,item){ console.log(index); console.log(item); }); //相当于 for(var i = 0;i<[1,2,3].length;i++){ console.log(index); console.log(item); }8.对象。(当要传递3个或3个以上的参数时,并且还包含可选参数是可传可不传的,那就用对象作为参数,这样可以不用考虑传递值的顺序,可选参数就可传可不传)
$.each({name:'xm',sex:'male'},function(index,item){ console.log(index);//对应name和sex console.log(item);//对应xm和male }); function setPerson(obj){ var person = {}; person.name = obj.name || 'xh';//用短路操作给一个默认值 person.sex = obj.sex || 'male'; person.age = obj.age ||'18'; person.tel = obj.tel || '110'; person.addr = obj.addr || 'China'; } setPerson({ name:'xm'; age:'18'; addr:'China'; sex:'male'; })用对象传参非常灵活~ 9,函数当做参数传递(定时器的设置)
setTimeout(function () {},1000);1.参数相当于函数的输入,return相当于函数的输出。
2.return含义:代表函数的结束和返回值。
return:return用在函数中,表示函数的返回。 continue:用在循环中,表示跳出本次循环。(注意只是跳过本次循环,接着下一次循环,并不是跳出整个循环)
for(var i = 0;i<10;i++){ if(i===4)continue;//当循环到4时不打印,再接着下次循环打印5以及后面的数。 console.log(i); }break://跳出整个循环。用在循环中。(跳出当前的整个循环,开始执行循环外的语句)
for(var i = 0;i<10;i++){ if(i===4)break;//当循环到4时不打印,跳出循环,打印结果为:0 1 2 3 console.log(i); }注:switch中如果没有break,就会发生穿刺现象,也就是程序执行完选中的case后会继续往下执行,直到遇到break跳出switch语句或执行完所有case后退出switch语句。
var time='a'; switch(time){ case 'a': console.log('aaaaa'); case 'b': console.log('bbbbb'); case 'c': console.log('ccccc'); break; case 'd': console.log('dddddd'); }结果:aaaaabbbbbccccc
1.什么都没有。(return不加值,可以用来提前退出函数) 2.undefined。(和什么都没有的情况是一样的) 3.数字。 4.字符串。(当每次打印一个非字符串的时候,浏览器都会尝试自动将其转化为字符串toString())
alert('1,2,3');//正常字符串 alert([1,2,3]); //alert()期望接收字符串,就相当于 alert([1,2,3].toString());和上面一样5.布尔值。 6.null。(和undefined一样) 7.数组。
function add(num1,num2){ return [num1+num2,num1,num2]; }8.对象。
function getPerson(){ return{//js的语句末尾可以加分号也可以不加分号,不加分号的时候,js解析器会自动加上分号。所以如果把return后面对象的大括号放到下一行时,js解析器会在return末尾加上分号,这样就会报错。所以大括号放在return同一行 name:'xm'; age:18 } } console.log(getPerson());8.函数。
funciton fn(){ return function(argument){ console.log(1); } } fn()();//要调用做返回值的函数,需要再加一个括号。 var newFn = fn(); newFn();//也可以把函数赋值给一个变量,再通过变量调用。测试: 结果: 1,2,3; [object Object]; 调用了toString方法 解析: 1. document.write期望接收和输出字符串,当接收的参数不为字符串时,会调用参数的toString方法,将其转化成字符串输出 2. 数组调用toString()后,会将其中的元素用逗号拼接起来变成字符串 3. 一般的对象调用toString()后返回[object Object] 重写对象的toString()方法,就按照重写方法的返回值输出