js知识体系梳理-1

407 阅读20分钟

一、JS:轻量级的客户端脚本编程语言

1、编程语言 HTML+CSS是标记语言 编程语言是具备一定逻辑的,拥有自己的编程思想(面向对象编程[OOP]、面向过程编程)

    面向对象的语言 
        C++
        JAVA
        PHP
        C#(.net)
        JS
        …
    面向过程 
        C语言

2、目前的JS已经不仅仅是客户端语言了,基于NODE可以做服务器端程序,所以JS是全栈编程语言

3、JS主要由以下几部分组成

    ECMAScript(ES):规定了JS的语法和模型
    DOM:document object model 文档对象模型,提供各种API(属性和方法)让JS可以获取或者操作页面中的HTML元素(DOM元素)
    BOM:browser object model 浏览器对象模型,提供各种API让JS可以操作浏览器

二、ECMAScript

ECMAScript是JS的语法规范,JS中的变量、数据类型、语法规范、操作语句、设计模式等等都是ES规定的。

JS大部分的语法都是ES3.0的

三、变量(variable)

变量不是具体值,只是一个用来存储具体值的容器或者代名词,因为它存储的值可以改变,所以称为变量

基于ES语法规范,在JS中创建变量有以下方式

var (ES3)
function(ES3) 创建函数(函数也是变量,只不过存储的值时函数类型的而已)
let(ES6)
const(ES6) 创建的是变量
import(ES6) 基于ES6的模块规范导出需要的信息
class(ES6) 基于ES6创建类

创建变量,命名的时候要遵循一些规范:

1、严格区分大小写

2、遵循驼峰命名法:按照数字、字母、下划线或者$来命名(数字不能作为名字的开头),命名的时候基于英文单词拼成一个完整的名字(第一个单词字母小写,其余每一个有意义单词的首字母都大写) 不能使用关键字和保留字:在JS中有特殊含义的叫关键字,未来可能会成为关键字的叫做保留字

四、JS中的数据类型

数据值是一门编程语言进行生产的材料,JS中包含的值有以下类型(JS中数据类型分类)

基本数据类型(值类型)

    数字number
    字符串string
    布尔boolean
    null
    undefined

引用数据类型

    对象object 
        普通对象
        数组对象
        正则对象
        日期对象
        …
    函数function

ES6中新增加的一个特殊类型:Symbol(唯一的值)

[基本数据类型]
//=>number类型
var n = 13;//=>0 -13 13.2 数字类型中有一个特殊的值NaN(not a number代表不是一个有效的数字,但是属于number类型的)

//=>string类型
var s ='';//=>"" '13' "{}" JS中所有用单引号或者双引号包裹起来的都是字符串,里面的内容是当前字符串中的字符(一个字符有零到多个字符组成)


//=>布尔类型
var b = true;//=>布尔类型只有两个值 true真 false假


[引用数据类型]
//普通对象
var o ={name:'珠峰培训',age:9};//=>普通的对象:由大括号包裹起来,里面包含多组属性名和属性值(包含多组键值对,各组键值对由逗号分隔) {}空对象

//数组对象
var ary = [12,23,34,56];//=>中括号包裹起来,包含零到多项内容,这种是数组对象  []空数组

//正则对象
var reg = /-?(\d|([1-9])\d+)(\.\d+)?/g;//=>由元字符组成一个完整的正则 //不是空正则是单行注释,正则不可以为空

//function函数
function fn(){

}

[Symbol]
创建出来的是一个唯一的值
var a = Symbol('wyy');
var b = Symbol('wyy');

a==b //=>false
constSymbol的结合,定义唯一的不变的数值

扩展:JS代码如何被运行,以及运行后如何输出结果 [如何被运行]

  • 把代码运行在浏览器中(浏览器内核来渲染解析)
  • 基于NODE来运行(NODE也是一个基于V8引擎渲染和解析JS的工具,不是一门语言)

[如何输出结果]

  • alert():在浏览器中通过弹框的方式输出(浏览器提示框)

  • confirm():和alert的用法一致,输出的结果也是转换为字符串,只不过提示框中有确定和取消两个按钮,所以它是确认提示框

  • prompt():在confirm的基础上增加输入框,点击取消返回的是null,点击确定若有内容返回内容,若无内容返回''(空字符串)

  • console.log():在浏览器控制台输出日志(按F12 (FN+F12)打开浏览器的控制台)

    • Elements:当前页面中的元素和样式在这里都可以看到,还可以调整样式结构等
    • Console:控制台,可以在JS代码中通过.log输出到这里,也可以在这里直接的编写JS代码
    • Sources:当前网站的源文件都在这里
    • ......
  • console.dir():比log输出的更加详细一些(尤其是输出对象数据值得时候)

  • console.table:把一个JSON数据按照表格的方式输出

五、数据类型的详细剖析

基本数据类型

1.number数字类型

NaN:not a number 但是它是数字类型的

isNaN():检测当前值是否是非有效数字,返回true代表非有效数字,返回false代表是有效数字

把其他数据类型转换为number数字类型的方法
a、Number()

[字符串转换为数字]

  • Number('13') ->13
  • Number('13px')->NaN 如果当前字符串中出现任意一个非有效数字字符,结果则为NaN
  • Number('13.5')->13.5 可以识别小数

[布尔转换为数字]

  • Number(true)->1
  • Number(false)->0

[其它]

  • Number(null)->0
  • Number(undefined)->NaN
  • 把引用数据类型值转换为数字:先把引用值调取toString转换为字符串,然后再把字符串调取Number转换为数字

[对象]

  • ({}).toString() ->'[object Object]' ->NaN

[数组]

  • [12,23].toString()->'12,23'->NaN
  • [12].toString()->'12'->12

[正则] /^$/.toString() -> '/^$/' ->NaN

[函数] function(){}.toString ->' function(){}'->NaN

b、parseInt/parseFloat

等同于Number,也是为了把其它类型的数值转换为数字类型

和Number的区别在于字符串转换分析上

Number:出现任意非有效数字字符,结果就是NaN

parseInt:把一个字符串中的整数部分解析出来 parseFloat:把一个字符串中小数(浮点数)部分解析出来

parseInt('13.5px') //=>13
parseFloat('13.5px') //=>13.5

parseInt('width:13.5px') //=>NaN 从字符串最左边字符开始查找有效数字字符,并且转换为数字,直到遇到一个非有效数字字符,查找结束
isNaN检测的机制
  • Number.isNaN() 方法确定传递的值是否为 NaN和其类型是 Number,不是数字或是有效数字返回false,是NaN返回true(数字类型中只有NaN不是有效数字,其余都是有效数字)

  • isNaN() 首先验证当前要检测的值是否为数字类型的,如果不是,浏览器会默认把值转换为数字类型(其他基本类型转换为数字:直接使用Number()这个方法来转换的)

不同:和全局函数 isNaN() 相比,Number.isNaN()不会强制将参数转换成数字,只有在参数是真正的数字类型,且值为 NaN 的时候才会返回 true。

2.布尔类型

只有两个值:true/false

把其他数据类型转换为布尔类型的方法
  • Boolean()
  • !
  • !!
Boolean(1)->true
Boolean(0)->false
Boolean(NaN)->false

!'wyy'->先把其它数据类型转换为布尔类型,然后再取反

!!NaN->取两次反,等价于没取反,也就剩下转换为布尔类型,与Boolean的效果一样

真实项目中想将数据转换为布尔类型,一般直接用!!

规律:**在JS中只有0、NaN、空字符串、null、undefined**这五个值转换为布尔类型为false,其余都为true

3、null && undefined

都代表空或者没有

  • null:空对象指针
  • undefined:未定义

引用数据类型

object对象类型

普通对象

  • 由大括号包裹起来的
  • 有零到多组属性名和属性值(键值对)组成

属性是用来描述当前对象特征的,属性名是当前具备这个特征,属性值是对这个特征的描述(专业语法,属性名称为键[key],属性值称为值[value],一组属性名和属性值称为一组键值对)

var obj = {name:'wyy',age:8};

//=>对象的操作:对键值对的增删改查
[获取]
语法:对象.属性/对象[属性]
obj.name
obj['name'] 一般来说对象的属性名都是字符串格式,属性值可以是各种类型的

[增/改]
JS对象中,属性名是不允许重复的,是唯一的
obj.name='wyys';//=>原有对象中存在NAME属性,此处属于修改属性值
obj.sex='男';//=>原有对象不存在sex,此处相当于给当前对象新增一个属性sex
obj['age']=9;

[删除]
- 彻底删除:对象中不存在这个属性了
delete obj['age']

- 假删除:并没有移除这个属性,只是让当前属性的值为空
obj.sex=null;
obj.sex='';//=>不算假删除,代表的就是字符串,只不过为空而已

在获取属性值得时候,如果当前对象有这个属性名,则可以正常获取到值(哪怕是null),但是如果没有这个属性名,则获取的结果是undefined


----
var obj = {name:'wyy',age:8};
var name = 'fcfm'

obj.name//=>'wyy' 获取的是NAME属性的值

obj['name']//=>'wyy' 取的是NAME属性的值

obj[name]//=>此处的NAME是一个变量,我们要获取的属性名不叫NAME,是NAME存储的值'fcfm'=>obj['fcfm']=>没有这个属性,属性值是undefined

----
'name'和 name的区别?
=> 'name'是一个字符串值,它代表的是本身
=> name是一个变量,它代表的是本身存储的这个值

一个对象的属性名不仅仅是字符串格式的,还有可能是数字格式的

var obj = {name:'wyy',age:8,0:12};
obj[0] //=>12
obj['0'] //=>12
obj.0 //=>Uncaught SyntaxError(语法错误): Unexpected number

存储的属性名不是字符串也不是数字的时候,浏览器会调用toString的方法把这个值转换为字符串,然后再进行存储

var obj = {name:'wyy'}
obj[{}] = 300;
obj[[]] = 400;
obj[true] = 500;
obj = {name: "wyy", '[object Object]': 300, "": 400, 'true': 500};

数组对象(对象都由键值对组成的)

数组对象获取、增加、修改属性的方法是和普通对象一样的,其实普通对象对属性操作的方法对于其他对象类型及函数类型时一样的

数组对象的属性名是数字(我们把数字属性名称为当前对象的索引)

数组对象真删除 
被删除的键值对对应的索引即属性名会变没,属性值会变为empty,但是数组长度不会变
数组对象假删除 
与普通对象的效果一样,属性值变为null

六、JS的运行机制

这一段可以参考(www.muyiy.cn)

1、当浏览器(它的内核/引擎)渲染和解析JS的时候,会提供一个供JS代码运行的环境,我们把这个环境称之为"全局作用域(global scope)"->栈内存

2、代码自上而下执行(之前还有一个变量提升阶段)

   栈内存:本身就是一个供JS代码执行的环境,所有的基本类型值都会直接的在栈内存中开辟一个位置进行存储
   堆内存:用来存储引用数据类型中的信息值的,对象存储的是【键值对】,函数存储的是【代码字符串】
var a = 12;
var b=a;
b=13;
console.log(a);//=>12
  • 1)在当前作用域下开辟一个空间存储12
  • 2)在当前作用域中声明一个变量a(var a)
  • 3)让声明的变量和存储的12进行关联(把存储的12赋值给a,赋值操作叫做定义)

基本数据类型(也叫作值类型),是按照值(让两个空间没有关联)来操作的:把原有的值复制一份放到新的空间或者位置上,和原来的值没有关系

引用数据类型运行方法

引用数据类型的值不能直接存储到当前的作用域下(因为可能存储的内容过于复杂,我们需要先开辟一个新的空间(理解为仓库),把内容存储到这个空间中)

var obj1={n:100};
var obj2=obj1;
obj2['n'] = 200;
console.log(obj1.n);=>200
  • 1)因为对象的内容比较复杂,所以会另开辟一个新的内存空间(该空间不属于当前的作用域)->堆内存,把对象中的键值对一次存储起来(为了保证后面可以找到这个空间,此空间有一个16进制的地址)
  • 2)声明一个变量
  • 3)让变量和空间地址关联在一起(把空间地址赋值给变量)

引用类型不是按值操作,它操作的是空间引用地址:把原有空间的地址赋值给新的变量,但是原来的空间没有被克隆,还是一个空间,这样就会出现多个变量关联的是相同的空间,相互之间就会存在影响了

案例一

var ary1 = [3, 4];
var ary2 = ary1;
ary2[0] = 1;
ary2 = [4, 5];
ary2[1] = 2;
ary1[1] = 0;
console.log(ary1, ary2);

七、JS中的判断操作语句

1、if/else if/else

var num = 10;
if(num>5){
	num+=2;
}else if(num>8){
	num+=3;
}else{
	num+=4;
}
console.log(num);//=>12

只要有一个条件成立,后面不管是否还有成立的条件,都不在判断执行了

2、>= <= == 常规比较


if(0){
	//=>不管你在条件判断中写什么,最后总要把其计算出TRUE/FALSE来判断条件是否成立(把其他它类型的值转换为布尔类型,只有0/NaN/''/null/undefined)是false,其余都是true
}
if('3px'+3){
	//=>在JS中,+ - * / % 都是数学运算,除 + 以外,其余运算符在运算的时候,如果遇到了非数字类型的值,首先会转换为数字类型(Number),然后再进行运算

	//=>在JS中除了数学相加,还有字符串拼接的作用,如果运算中遇到了字符串,则为字符串拼接,而不是数学相加

	'3px'+3=>'3px3'
}

if('3px'-3){
	'3px'-3=>NaN
}
var num = parseFloat('wideth:12.5px');//=>NaN
if(num==12.5){
	alert(12.5);
}else if(num==NaN){//=>NaN!=NaN
	alert(NaN);
}else if(typeof num=='number'){//=>typeof NaN->"number"
	alert(0);
}else{
	alert('啥也不是!')
}

3、三元运算符

语法:条件?条件成立做的事情:不成立做的事情;

var num = 12;
if (num>10){
	num++;
}else{
	num--;
}

//=>改成三元运算符:num>10?num++:num--;

`特殊情况`

//=>如果三元运算符中的某一部分不需要任何的处理,我们用null/undefined/void 0...占位即可
var num = 12;
if (num>10){
	num++;
}
num>10?num++:null;

//=>如果需要执行多项操作,我们把其用小括号包裹起来,每条操作语句用逗号分隔
num=10if(num>=10){
	num+=10;
	num*=10;
}
num>10?(num++,num*10):null;

3、switch case

JS中的一种判断方式 switch case 应用于变量(或者表达式等)在不同值情况下的不同操作,每一种case结束后都要加break(结束整个判断)

var num = 10;
if(num==10){
	num++;
}else if(num==5){
	num--;
}else{
	num==0
}

//=>改写为switch case语句
var num = 10;
switch(num){
    case 10:
    	num++;
    	break;
    case 5:
    	num--;
    	break;
    default:
    	num==0;
}

swith case中每一种case情况与变量之间的比较都是基于'==='绝对相等来完成的

'10'==10 //=>true 相等比较,如果等号左右两边的类型不一样,首先会转换为一样的数据类型,然后再进行比较
//=>当前案例中,就是把字符串'10'转换为数字了,然后再比较的

'10'===10 //绝对比较,如果两边的数据类型不一样,则直接不相等,它要求类型和值都完全一样才会相等(真实项目中为了保证代码的严谨性,我们应该更多使用绝对比较)
var a = [4], b = '3';

function foo(a, b) {
    return Math.abs(a - b) < 2;
}
// 不加break,后面的条件不管是否成立,都会被执行;利用此机制,我们可以完成一些特殊的处理
switch (true) {
    case !(a && b):  
        console.log('a:'+a,' b:'+b);
        break;
    case foo(a,b):  
        console.log('a - b < '+Math.abs(a-b));
    case a == b:
        console.log(a+'=='+b);
        break;
    case a < b:
        console.log(a+'<'+b);
        break;
    case a > b:
        console.log(a+'>'+b);
        break;
    default:
        break;
}

输出
a-b<1
4==3

n++和n=n+1是否一样?

var n = '10';
n=n+1
//=>属于字符串拼接,结果是'101'
n+=1
//=>属于字符串拼接,结果是'101'
n++;
//=>此时这种写法还是数学运算,不是字符串拼接,结果是11

for 循环

作用:按照一定的规律,重复去做某件事情,此时我们就需要使用循环来处理了

for 循环的语法组成

  • 1.定义初始值 var i = 0
  • 2.设立循环成立的条件(条件成立循环继续,不成立循环结束) i<ary.length
  • 3.条件成立会执行循环体中的内容(大括号包裹的就是循环体)
  • 4.执行步长累加的操作
var ary = [12,23,45];

/*==itar [TAB] 自动补全循环的代码==*/

for (var i = 0; i < ary.length; i++) {
//    第一次循环 i=0 i<3 ... i=1
//    第二次循环 i=1 i<3 ... i=2
//    第三次循环 i=2 i<3 ... i=3
//    第四次循环 i=3 i<3  循环结束(本次没有循环)
    console.log(ary[i]);
}

例:
for (var i = 1; i <=10; i+=2) {
    if(i<=5){
        i++;
        continue;
    }else{
        i-=2;
        break;
    }
    i--;
    console.log(i);
}
console.log(i);//=>5

八、JS中检测数据类型的方法

  • typeof
  • constructor
  • instanceof
  • Object.prototype.toString.call()

1、typeof(逻辑运算符)

语法:typeof[value] =>检测value数据类型

返回值:使用typeof检测出来的结果是一个字符串,字符串中包含对应的数据类型,例如:'number'/'string'/'boolean'/'undefined'/'object'/'function'

typeof null =>'object' 因为null代表空对象指针(没有指向任何的内存空间)

typeof检测数组/正则/对象,最后返回的都是'object',也就是基于这种方式无法细分对象

九、获取页面中的DOM操作

1、document.getElementById

在整个文档中通过元素的ID属性值来获取这个元素对象

getElementById获取元素的方法

document限定了获取元素的范围,我们把这个范围称之为:"上下文[context]"

var oBox = document.getElementById('box');

1.通过getElemntById获取的元素是一个对象数据类型的值(里面包含很多内置的属性)

typeof oBox =>'object'

2.分析包含的属性
className:存储的是一个字符串,代表当前元素的样式类名

id:存储的是当前元素的ID值(字符串)

innerHTML:存储当前元素中所有的内容(包含HTML标签)

innerTEXT:存储当前元素中所有的文本内容(没有元素标签)

onclick:元素的一个事件属性,基于这个属性,可以给当前元素绑定点击事件

onmouseover:鼠标滑过事件

onmouseout:鼠标离开事件

style:存储当前元素所有的"行内样式值",获取和操作的都只能是写在标签上的行内样式,写在样式表中的样式,无法基于这个属性获取到

如何获取当前元素的所有样式(不管是写在哪的?) style只能获取写在行内上的

1.getComputedStyle(ele,null/伪类)一个可以获取当前元素所有最终使用的CSS属性值。返回的是一个CSS样式对象([object CSSStyleDeclaration]) 
非ie浏览器中使用,第一个参数是要获取样式对应的元素,第二个null就是考察本身
2.currentStyle是IE浏览器的一个属性,返回的是CSS样式对象
ie浏览器使用

element 指JS获取的DOM对象

element.style //只能获取行内式样式

element.currentStyle //IE浏览器获取非行内式样式

window.getComputedStyle(element,伪类) //非IE浏览器获取非行内式样式

document.defaultView.getComputedStyle(element,伪类)//非IE浏览器获取非行内嵌样式

[context].gexElementsByTagName

在指定的上下文中,通过元素的标签名获取一组元素集合

上下文是我们自己来指定的

var oBox = document.getElementById('box');

var boxList = oBox.getElementsByTagName('li');
console.dir(boxList);

1.获取的结果是一个元素集合(HTMLCollection),首先它也是对象数据类型的,结构和数组非常相似(数字作为索引,length代表长度),但是不是数组,我们把它叫做“类数组”

boxList[0] 获取当前集合中的第一个LI(通过索引获取到具体的某一个LI即可)

boxList.length 获取集合中LI的数量

2.集合中的每一项存储的值又是一个元素对象(对象数据类型,包含很多的内置属性,例如:id/className...)

boxList[1].style.color='red';  修改集合中第二个LI的文字颜色

案例:隔行变色

===利用css样式来实现隔行变色,利用伪类选择器==
//nth-child(n):当前容器所有子元素中的第N个
//nth-of-type(n):先给当前容器按照某一个标签名进行分组,获取分组中的第N个

==奇偶行变色==
//nth-of-type(even)  even代表偶数,odd代表奇数

==隔三行变色==
//nth-of-type(3n+1)
//nth-of-type(3n+2)
//nth-of-type(3n)


//1.获取BOX中所有的LI
//想要修改BOX的样式有2种方法

//a.通过style样式修改行内样式
oBox.style.backgroundColor = 'pink' //=>相当于给oBox增加了行内的样式

//b.先利用class实现在css中设置好样式,然后给元素添加这个class名即可
oBox.className='box bgColor';
oBox['className']+=' bgColor'// =>不要忘记加上空格,否则会变成class='boxbgColor',此时不仅新加的样式没有,原有的样式也不会有

十、函数

在JS中,函数就是一个方法(一个功能体),基于函数一般都是为了实现某个功能


===ES3标准中===
//=>创建函数
function fn(){
	var total = 10;
	total+=10;
	total/=2;
	total=total.toFixed(2);
	console.log(total);
}
fn(); =>执行函数的意思

ES3标准中:function 函数名([参数]){
	函数体:实现功能的JS代码
}
//=>执行函数
函数名();


===ES6标准创建箭头函数===

let 函数名(变量名)=([参数])=>{
	函数体;
}
函数名();

let fn=()=>{
	let total=10;
	...
}

函数的运行机制

函数作为引用数据类型中的一种,它也是按照引用地址来操作的

function fn(){
	var total = 10;
	total+=10;
	total/=2;
	total=total.toFixed(2);
	console.log(total);
}
fn();

[创建函数]
1.函数也是引用类型,首先会开辟一个新的堆内存,把函数体中的代码当做**字符串**存储到内存中(对象向内存中存储的是键值对)
2.把开辟的堆内存地址赋值给函数名(变量名)

输出fn =>代表当前函数本身
输出fn() =>代表执行函数

所以是否加小括号是两种不同本质的操作

[函数执行]
目的:把之前存储到堆内存中的代码字符串变为真正的JS代码,自上而下执行,从而实现应有的功能


1.函数执行,首先会形成一个私有的作用域(一个供代码执行的环境,也是一个栈内存)
2.把之前在堆内存中存储的字符串复制一份过来,变为真正的JS代码,在新开辟的作用域中自上而下执行
3.函数每次执行的时候都会开辟一个新的私有作用域将代码复制过来从上而下执行,每次执行函数都是独立的,两次执行没有关系

函数中的参数

参数是函数的入口:当我们在函数中封装一个功能,发现一些原材料不确定,需要执行函数的时候,用户传递进来才可以,此时我们就基于参数的机制,提供出入口

//=>创建函数的时候的参数是形参,形参相当于入口,是变量(n/m就是变量)
function sum(n,m){
	//=>n和m分别对应要求和的两个数字
	var total = 0;
	total = n+m;
	console.log(total);
}

//=>函数执行时的参数是实参,实参是具体的数据值
sum(10,20);   //=>n=10 m=20
sum(10);  //=>n=10 m=undefined
sum();    //=>n和m都是undefined
sum(10,20,30);  //=>n=10 m=20   30没有形参变量

选项卡

//思路:
//1.先让所有的LI&&DIV的class都为空(xxx.className=''),再让当前点击的这个LI和对应的DIV有ACTIVE这个样式类即可
//2.给所有的LI绑定点击事件,当点击任何一个LI的时候,都做第1步操作

//获取所有li和div元素
var tabBox = document.getElementById('tabBox');
var tabList = tabBox.getElementsByTagName('li');
var divList = tabBox.getElementsByTagName('div');

//封装一个方法完成页卡,即思路中的第一步
function changeTab(n) {
    //=>N:形参变量,当执行这个方法的时候,会把当前点击的这个LI的索引传递过来

    //=>1.所有都没有选中样式
    for (var i = 0; i < tabList.length; i++) {
        tabList[i].className = '';
        divList[i].className = '';
    }
    //=>2.当前点击的有选中样式
    tabList[n].className = 'active';
    divList[n].className = 'active';
}

//给每一个LI绑定点击事件

===按原有思路===
for (var i = 0; i < tabList.length; i++) {
    //tabList[i] <=>每一轮循环当前要操作的那个LI对象
    tabList[i].onclick = function () {
        changeTab(i);//=>需要把当前点击的这个LI的索引传递进来
    }
}

 //=>事件绑定:给当前元素的某一个事件绑定一个方法,绑定的时候方法没有执行(属于创建一个方法),当在页面中手动触发点击事件的时候绑定的方法才会执行
 //=>所以:
 i=0 第一次循环
 tabList[0].onclick=function () {
 "changeTab(i);"
 };
 i++
 i=1 第二次循环
 tabList[1].onclick=function () {
 "changeTab(i);"  changeTab(i)
 };
 i++
 i=2
 tabList[2].onclick=function () {
 "changeTab(i);"
 };
 i++
 i=3
 //循环结束,此时因为只是函数创建的阶段,所以函数体只是存放在堆内存中的代码字符串,也就是说此时的changeTab(i)中的i只是字符串i,并不是索引,循环三次结束后i等于3

//循环结束后点击li执行的时候
tabList[传进来的索引].onclick=function () {
 "changeTab(3);"


===正确的写法===
//给每一个li增加一个自定义属性,每个li是个单独的对象,可以用对象的属性增加方法来增加自定义属性来存储自己对应的索引
for (var i = 0; i < tabList.length; i++) {
    //=>每一轮循环的时候,给每一个LI设置一个自定义属性“ZF-INDEX”,存储的值是各自的索引
    tabList[i]['zfIndex'] = i;
    tabList[i].onclick = function () {
        //this:代表的是当前点击的这个LI
        changeTab(this.zfIndex);//=>需要索引
    }
}
 

选项卡的其他思路

让上一次有样式的li的样式清空,当前点击的li加上类样式

var lastIndex=0;//=>因为当前第一个li有样式,所以索引从0开始
for(var i=0;i<oList.length;i++){
	oList[i].myIndex=i;
	oList[i].onclick=function (){
            if(lastIndex===this.myIndex) return;
            
            oList[lastIndex].className='';
            oDiv[lastIndec].className='';
            this.className=oDiv[this.myIndex].className='active';
            lastIndex=this.myIndex;
	}
}


function change(curele){
    for(var i=0;i<oList.length;i++){
        if(oList[i]===curele){
            oList[i].className='active';
            oDiv[i].className='active';
            continues;
        }
        oList[i].className='';
        oDiv[i].className='';
    }
}

for(var i=0;i<oList.length;i++){
	oList[i].onclick=function (){
	    change(this);
	}
}