文章目录
一、面向对象简介
(一)对象
对象就是值的集合,任意值的集合。值包括所有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);//3
1.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指向同一个对象,这时对形参的修改就会影响到实参。但是不管怎样参数传递的本质都是将实参赋值给形参。
(二)参数的个数
function add(num1,num2){
return num1+num2;
}
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));//15
(三)arguments
arguments是每一个函数中都有的,是一个局部的东西,它是一个类数组(类似数组的对象),并不是真的数组,其实是一个对象。
真正的数组能用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);
六、函数的返回值
(一)return
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()方法,就按照重写方法的返回值输出