对象的特性:
1.唯一标识性,即使完全不一样的对象,内存地址也不同,所以他们不相等
2.对象具有状态,同一个对象可能处在不同状态下
3.对象具有行为,即对象的状态可能因为他的行为产生变迁
Js直到es6之前,有对象的概念,但是没有类的概念。
对象的分类:
1.内建对象
- 用Es标准中定义的对象,在任何的Es的实现中都可以使用(比如:Math,String,Number,Boolean,Function...)
2.宿主对象
-用Js运行环境提供的对象,目前来讲主要是指用浏览器提供的对象(BOM,DOM)(console.log/document.write)
3.自定义对象
-开发人自己定义的对象
-创建对象
- 使用new关键字调用的函数,是构造函数(constructor):专门用来创建对象的函数[new Object()]
- 使用typeof检查一个对象时,会返回object
在对象中保存的值称为属性
对象的属性值可以是任何数据类型,也可以是个函数(删除对象的属性:delete obj.name)
当函数作为对象的属性时,我们称这个函数为这个对象的方法,调用这个函数就是调用对象的方法,仅仅是名称上的区别
枚举对象中的属性 使用for..in语句(语法:for(var 变量 in 对象)
//对象中有几个属性就会执行几次,每次执行时,会将属性的名字赋值给变量
使用工厂方法创建对象
-通过该方法可以大批量的创建对象
-使用工厂创建的对象,使用的构造函数都是Object,所以导致创建的对象都是Object这个类型,就导致我们无法区分出多种不同类型的对象
function creatDog(name,age){
使用构造函数创建对象
-构造函数就是一个普通的函数,创建方式和普通函数没有区别,
-不同的是构造函数习惯首字母大写
-构造函数和普通函数的区别就是调用方式的不同,普通函数是直接调用,而构造函数需要使用new关键字来调用
function creatPerson(name,age){
this.name=name;
this.age=age;
this.say=function(){
alert('hello'+name);
}
}
var obj=new creatPerson("Jeo",23);
var obj2=new creatPerson("Rose",14);
obj.say();
构造函数的执行流程:
1.立即创建一个新的对象
2.将新建的对象设置为函数中this,在构造函数中可以使用this来引用新建的对象
function Person(){
alert(this);
}
var per=new Person();//object,就是新创建的对象:per
3.顺序执行代码
4.将新建对象作为返回值返回
function Person(){
}
var per=Person();
console.log(per);//undefined,作为普通函数调用,没有返回值
var per1=new Person();
console.log(per1);//object
- 使用同一个构造函数创建对象,我们称为一类对象,将一个构造函数称为一个类
- 将构造函数创建的对象,称为该类的实例
使用instanceof可以检查一个对象是否是一个类(构造函数)的实例
-语法:对象instanceof构造函数(是:true)
所有的对象都是Object的后代
创建一个Person构造函数
- 在Person构造函数中,为每一个对象都添加了一个say方法,
- 目前我们的方法是在构造函数内部执行创建的,也就是构造函数每执行一次就会创建一个新的say方法(即每个实例的say()都是唯一的
解决方法:
- 将say()方法定义在全局作用域
function creatPerson(name,age){
this.name=name;
this.age=age;
this.say=fun;
}
function fun(){
alert('hello:'+this.name);
}
var obj=new creatPerson("Jeo",23);
var obj2=new creatPerson("Rose",14);
obj.say();
缺点:
-将函数定义在全局作用域,污染了全局作用域的命名空间
-定义在全局作用域中也很不安全
解决方案:
原型prototype
我们所创建的每一个函数,解析器都会向函数中添加一个属性prototype
- 这个属性对应着一个对象,这个对象就是我们所谓的原型对象
-如果函数作为普通函数调用prototype没有任何作用
-当函数通过构造函数调用时,它所创建的对象中都有一个隐含的属性
-指向构造函数的原型对象,我们可以通过__proto__来访问该属性
-原型对象就相当于一个公共区域,所有同一个类的实例都可以访问到这个原型对象
-我们可以将对象中共有的内容,统一设置到原型对象中
-当我们在访问对象的一个属性或方法时,它会事先在对象自身中寻找,如果有则直接使用,如果没有则会去原型对象中寻找,如果找到则直接使用
以后创建构造函数时,可以将这些对象共有的属性和方法,统一添加到构造函数中的原型对象中,这样不用分别为每一个对象添加,也不会影响到全局作用域,就可以使每个对象都具有这些属性和方法
function Hello(){
}
Hello.prototype.a="hello";
var hel1=new Hello();
//hel1.a="我是hel1";
console.log(hel1.a);
当程序这样运行时控制台输出 hello
当程序去掉注释时,会输出 “我是hel1”
故修改say()方法,变成原型对象的方法
function creatPerson(name,age){
this.name=name;
this.age=age;
}
creatPerson.prototype.say=function(){
alert("hello:"+this.name);
}
var obj=new creatPerson("Jeo",23);
var obj2=new creatPerson("Rose",14);
obj.say();//hello:Jeo
检查一个对象是否含有某个属性(in),若对象中没有,原型中有则也返回true
检查一个对象是否含有某个属性(hasOwnProperty),只有对象自身含有属性时,才会返回true
function Test(){
}
Test.prototype.name="我是原型中的名字";
var test=new Test();
alert("name" in test);//true
----------------------------------------------
原型对象也是对象,所以它也有原型(原型链)
- 当我们使用一个对象的属性或方法时,会先在自身中寻找,自身中如果有,则直接使用,如果没有则去原型对象中寻找,若原型对象中有,则使用,若没有则去原型对象的原型中寻找,直到找到Object对象的原型,Object对象的原型没有原型,若在Object中依然没有找到,则返回undefined
var Person=function(name,age){ this.name=name; this.age=age; } Person.prototype.getAge=function(){ return this.age;} var per=new Person('Jeo',18); console.log(per.getAge());//18
关于原型链中一些结论:
- 函数对象的
__proto__
指向Function.prototype
; - 函数对象的
prototype
指向instance.__proto__
;(实例的) - 普通对象的
__proto__
指向Object.prototype
; - 普通对象没有
prototype
属性; - 在访问一个对象的某个属性/方法时,若在当前对象上找不到,则会尝试访问
ob.__proto__
, 也就是访问该对象的构造函数的原型obCtr.prototype
,若仍找不到,会继续查找obCtr.prototype.__proto__
,像依次查找下去。若在某一刻,找到了该属性,则会立刻返回值并停止对原型链的搜索,若找不到,则返回undefined。
针对上述结论:分别理解这两个代码:
Js中的继承分为两种接口继承和实现继承。接口继承只继承方法签名,而实现继承则继承实际方法,由于函数没有签名,在ECMAScript中无法实现接口继承,ECMAScript只支持实现继承,而且其实现继承主要是依靠原型链来实现的。
原型链:
是实现继承的主要方法,其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。其中,每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针
原型继承:通过__proto__并借助prototype来实现
var Person=function(name,age){
this.name=name;
this.age=age;
}
Person.prototype.getAge=function(){
return this.age;}
var per=new Person(Jeo,18);
console.log(per.getAge());//18
首先这段代码里,per.__proto__指向Person.prototype,通过原型来构造函数,
var Person=function(name,age){ this.name=name; this.age=age; } Person.prototype.getAge=function(){ return this.age;} //var per=new Person(Jeo,18); //console.log(per.getAge());//18 function Work(name,age,job){ Person.call(this,name,age); this.job=job; } //Work.prototype=Person.prototype; (1) Work.prototype=Object.create(Person.prototype);(2)
Work.prototype.constructor=Work;//调整constructor,以免constuctor错误
var per=new Work('Jeo',18,'doctor'); console.log(per);
我们发现用这种方法实现了原型链的继承,在f12中调试可以看到如下
执行(1)中代码得到第一个Work的情况,执行(2)得到第二,她们的原型对象是不同的
垃圾回收
-程序运行过程中会产生垃圾(这些垃圾积攒过多以后,会导致程序运行的速度过慢,所以我们需要一个垃圾回收的机制,来处理程序运行过程中产生的垃圾)
-当一个对象没有任何的变量或属性对它进行引用,此时,我们将永远无法操作该对象(此对象就好似一个垃圾,这种对象过多会占用大量的内存空间,导致程序运行变慢,所以这种垃圾必须进行清理
- 在JS中拥有自动的垃圾回收机制,会自动将这些垃圾对象从内存中销毁,我们不需要也不能进行垃圾回收的操作
-我们需要做的只是将不再使用的对象设置为null
函数的返回值:
由return返回
自执行函数:
//自执行写法1 (function T(){ alert(1) })() //自执行写法2 var T1=function(){ alert(1) }(); //传值 var para1={a:1;b:2,c:'Ok'} var T1=function(obj){ alert(obj.a) }(para1); //如果不用自执行方式,就得调用 function T(){ alert(1) } T();//调用
数组(Array)
属性:length,constructor,prototype
-数组也是一个对象
-它和我们普通对象功能类似,也是用来储存一些值的
-不同的是普通对象是使用字符串作为属性名的,而数组是使用数组来作为索引操作元素
-索引:
从0开始的一个整数就是索引
-数组的存储性能比普通对象要好,在开发中我们经常食用数组来存储一些数据
//创建数组对象
//使用构造函数创建数组时,也可以同时添加元素,将要添加的元素作为构造函数的参数传递,元素之间用,隔开
var arr=new Array();//var arr=new Array(1,2)
//使用typeof检查一个数组时,会返回Object,读取数组中的元素,语法:arr[0],如果读取不存在的索引,他不会报错而是返回undefined
使用字面量来创建数组:语法[](使用字面量创建数组时,可以在创建时就指定数组中的元素
var arr=[];//var arr=[1,2];
-获取数组的长度(语法:数组.length)
- 可以使用length属性来获取数组的长度(元素的个数)
- 对于连续的数组,使用length可以获取到数组的长度(元素的个数),对于非连续的数组,使用length会获取到数组的最大索引+1
- 修改length:如果修改的length大于原长度,则多出部分会空出来,如果修改length小于原长度,则多出的元素会被删除
- 向数组的最后一个位置添加元素:(语法:数组[数组.length]=值
- 数组中的元素可以是任意的数据类型(也可以是对象,函数)函数也是对象
push():
-该方法可以向数组的末尾添加一个或多个元素,并返回数组的新长度
-可以将要添加的元素作为方法的参数传递,这些元素会自动添加到数组的末尾
pop():
-该方法可以删除并返回数组的最后一个元素,并将删除元素作为返回值返回
unshift():
-向数组的开头添加一个或多个元素,并返回新的数组长度
-向前面插入元素后,其他元素索引会依次调整
shift():
-可以删除数组的第一个元素,并将删除的元素作为返回值返回
Js中提供forEach()方式遍历数组(兼容ie8以上的浏览器)
- 需要函数作为参数,像这种函数,由我们创建但不由我们调用的,我们称为回调函数,数组有几个元素,函数就会执行几次,每次执行时,浏览器会将遍历的元素以实參形式传递进来,我们可以来定义形參来读取这些内容
-浏览器会在回调函数中传递三个参数:
-value:当前正在遍历的元素
-index:当前正在遍历的元素的索引
-obj:当前正在遍历的数组(obj)
slice(start,end)
- 从数组中提取出指定元素,不会改变原数组,而是将截取到的元素封装到一个新数组中返回
- 参数:start:截取开始位置的索引,包含开始索引
end:截取结束位置的索引,不包含结束索引
(第二个参数可以不写,此时会截取从开始索引往后的所有元素)
(索引可以传递一个负值,如果传递一个负值,则从后往前计算)-1:倒数第一个;-2:倒数第二个
splice(start,num,args)(删除,添加,替换)
-可以用于删除数组中的指定元素,使用splice()会影响到原数组,会将指定元素从原数组中删除,并将被删除的元素作为返回值返回
-参数:start:开始位置的索引,num:表示删除的数量,args:可以传递一些新的元素,这些元素将会自动插入到开始位置索引前面
数组去重问题: var arr=[1,2,3,2,1,3,4,2,5]; var arr1=arr.sort(); var res=[]; for(var i=0;i<arr1.length;i++){ if(arr1[i]!==arr1[i+1]){ res.push(arr1[i]); } }
------------------------------------------------
concat()
- 可以连接两个或多个数组(元素),将新的数组返回,不会对原数组有影响
Join()
- 该方法可以将数组转换为一个字符串,该方法不会对原数组产生影响,而是将转换后的字符串作为结果返回,在Join()中可以指定一个字符串作为参数,这个字符串会成为数组中元素的连接符,默认为,
reverse()
-该方法用来反转数组,会直接修改原数组
sort()
- 可以用来对数组中的元素排序,会修改原数组,默认会按照Unicode编码排序
(即使对于纯数字的数组,使用sort()排序时,也会按照Unicode编码来排序,所以对于数字进行排序时,可能会得到错误的结果,我们可以自己来指定排序的规则)
- 我们可以在sort()添加一个回调函数,来指定排序规则,回调函数中需要定义两个形參,浏览器将会分别使用数组中的元素作为实參去调用回调函数,使用哪个元素不确定,但肯定是在数组中a一定在b前面
-浏览器根据回调函数的返回值决定元素的顺序
若返回一个大于0的值,则元素会交换,若返回一个小于0的数,则元素位置不变,若返回等于0,则认为两个元素相等,也不交换位置
var arr=[5,7.2,3,1]; arr.sort(function(a,b){//升序排列 /* if(a>b) return 1; if(a<b) return -1 if(a=b) return 0;*/
return a-b; }) alert(arr);
call()和apply()
function fun(){
alert('hell');}
fun.apply();
fun.call();
fun()
这三者执行效果一样
-------------------------------------
function fun(){
alert(this.name);
}
- 这两个方法都是函数对象的方法,需要通过函数对象来调用
- 当对函数调用call()和apply()都会调用函数执行
- 在调用call()和apply()可以将一个对象指定为第一个参数 ,此时这个对象将会成为函数执行时的tihs
- call()方法可以将实參在对象之后依次传递,apply()方法需要将实參封装到一个数组里统一传递
arguments
在调用函数时,浏览器每次都会传递进两个隐含的参数
- 函数上下文对象 this
- 封装实參的对象arguments
- arguments是一个类数组对象,它可以通过索引来操作数据,也可以获取长度
- 在调用函数时,我们所传递的实參都会在arguments中保存
- arguments.length可以用来获取实參的长度
- 我们即使不定义形參,也可以通过arguments来使用实參
只不过比较麻烦
arguments[0]:表示第一个实參
arguments[1]:表示第2个实參
- 它里面有一个属性叫做callee
这个属性对应一个函数对象,就是当前正在指向的函数的对象
function fun(){ console.log(arguments instanceof Array);//false console.log(Array.isArray(arguments));//false }
Date对象
- 在Js中使用Date对象来表示一个时间
创建一个Date对象 如果直接使用 构造函数创建一个Date对象,则会封装为当前代码执行的时间 var d=new Date() console.log(d);//Thu Feb 14 2019 13:35:00 GMT+0800 (中国标准时间) 创建一个指定的时间对象 需要在构造函数中传递一个表示时间的字符串作为参数 日期格式 月/日/年 时:分:秒 var d=new Date('12/03/2016 21:21:30') console.log(d);//Sat Dec 03 2016 21:21:30 GMT+0800 (中国标准时间)
Math
-Math和其他对象不同,它不是一个构造函数
它属于一个工具类不用创建对象,它里面封装了数学运算相关的属性和方法
比如
Math.PI 表示圆周率
console.log(Math.abs(-1)) //1
console.log(Math.ceil(1.2))//Math.ceil对一个数进行向上取整,小数位只要有值就自动进1
console.log(Math.floor(1.9))//Math.floor对一个数进行向下取整,小数位会被舍掉
console.log(Math.round(1.9))//Math.round对一个数进行四舍五入取整
Math.random() 生成一个0-1之间的随机数,(不包含0和1)
// 生成一个0-x之间的随机数
Math.round(Math.random()*x)
//生成一个1-10之间的随机数
Math.round(Math.random()*9+1)
//生成一个2-10之间的随机数
Math.round(Math.random()*8+2)
生成一个x-y之间的随机数(包含x,y)
Math.round(Math.random()*(y-x)+x)
包装类
基本数据类型:String Number Boolean Null Undefined
引用数据类型:Object
在JS中提供了三个包装类,通过这个三个包装类可以将基本数据类型的数据转换为对象
三个包装类:(浏览器底层自己使用)
String():将基本数据类型字符串转化为String对象
Number():将基本数据类型的数字转换为Number对象
Boolean():将基本数据类型的布尔值转换为Boolean对象(对象专程boolean值都是true)
注意:⚠️
- 在实际开发应用中不会使用基本数据的对象,如果使用基本数据类型的对象,在做一些比较时可能会带来一些不可预期的结果
- 方法和属性只能添加给对象,不能添加给基本数据类型
当我们对一些基本数据类型的值去调用属性和方法时,浏览器会临时使用包装类将其转换为对象,然后再调用属性和方法
创建一个字符串
- 在底层字符串是以字符数组的形式保存的
var str="Hello world" console.log(str.length);//11 console.log(str[0])//H
length属性:获取字符串的长度
charAt():返回字符串中指定位置的字符 根据索引获取指定的字符
str.charAt(9)//l
charCodeAt():返回字符串中指定位置的字符编码(Unicode编码)
fromCharCode():根据字符编码获取字符 调用方式:通过String.fromCharCode()调用
concat():用来连接两个或多个字符串
indexOf():用来检索一个字符串中是否含有指定内容,
-如果字符串中含有该内容,则会返回其第一次出现的索引
-若没有找到指定的内容,则返回-1
-可以指定一个第二个参数,用来指定开始查找的位置
lastIndexOf:
该方法的用法和indexOf()一样,不同的是indexOf是从前往后找,而lastIndexOf是从后往前找
-可以指定一个第二个参数,用来指定开始查找的位置
slice()
- 可以从字符串中截取指定的内容
- 不会影响原字符串,而是将截取到的内容返回
- 参数:
第一个,开始位置的索引(包括开始位置
第二个,结束位置的索(不包括结束位置)(如果省略第二个参数,则会截取到后面所有
- 也可以传递一个负数作为参数,负数的话是从后面开始计算(-1:倒数第一个,-2:倒数第二个)
substring()
- 可以用来截取一个字符串,和slice()类似
- 不同的是这个方法不能结束负值,若传递了负值,则默认为0
- 而且还会自动调整参数的位置,如果第二个参数小于第一个,则自动交换
substr()
- 用来截取字符串
- 参数:
1.开始索引的位置
2.第二个为字符串截取的长度
split():
- 可以将一个字符串拆分为一个数组
var str='abc,bcd,efg,def' var result=str.split(','); console.log(result[0]);//result是一个数组,result[0]=abc 逗号就会被舍去
正则表达式:
- 用于定义一些字符串的规则(计算机可以根据正则表达式,来检察一个字符串是否符号规则
- 获取将字符串中符合规则的内容提取出来
/** * 创建正则表达式的对象 语法: var 变量=new RegExp(”正则表达式“,”匹配模式“) 使用typeof检察正则对象,会返回object
test():检察一个字符串是否符合正则表达式的规则
如果符合则返回true,否则返回false
在构造函数中可以传递一个匹配模式作为第二个参数
可以是:
i 忽略大小写
g 全局匹配模式 */ var reg=new RegExp('a','i')//检察字符串中是否含有a console.log(reg);//返回/a/
使用字面量来创造正则表达式(使用字面量来创造正则表达更加简洁,使用构造函数创建正则表达是更灵活
语法: var 变量=/正则表达式/匹配模式
var reg=new RegExp('a','i')//reg=/a/i
创建一个正则表达式,检察一个字符串中是否有a或b
reg=/a|b/ //|表示或者
创建一个正则表达式,检察一个字符串中是否有字母
[]里面的内容也是或的关系 [ab]===a|b [a-z]表示任意小写字母
[A-Z]表示任意大写字母
[A-z]任意字母
检察一个字符串中是否含有abc或者adc或者aec
reg=/a[bde]c/
[^ ]:除了
split()无论是否指定全局,都会全局匹配
- 可以将一个字符串拆分为一个数组
- 方法中可以传递一个正则表达式作为参数,这样方法会根据正则表达式去拆分字符串
var str='12a23jg23o4jg45jn6' var result=str.split(/[A-z]/); console.log(result);// ["12", "23", "", "23", "4", "", "45", "", "6"]
search()即使设置全局匹配也没效果
- 可以搜索字符串中是否含有指定内容
- 如果搜索到指定内容,则会返回第一次出现的索引,如果没有搜索到则返回-1
- 它可以接受一个正则表达式作为参数,然后会根据正则表达式去检索字符串
var str='abc aef bac abe abf' var result=str.search(/ab[A-z]/) console.log(result);//0
match()
- 可以根据正则表达式,从一个字符串中将符合条件的内容提取出来
- 默认情况下,只会找到第一个符合要求的内容,找到后就会停止检索
我们可以设置正则表达式为全局匹配模式(可以为一个正则表达式设置多个匹配模式,且顺序无所谓
- 将匹配的内容封装到一个数组中返回,即使只查询到一个结果
replace()
- 可以将字符串中指定内容替换为新的内容
-参数:
1.被替换的内容,可以接受一个正则表达式作为参数
2.新的内容
- 默认自会替换第一个
var str='12a23jg23o4jg45jn6' var result=str.replace(/[A-z]/gi,''); console.log(result);//1223234456
创建一个正则表达式检察一个字符串中是否含有aaa
量词:
- 通过量词可以设置一个内容出现的次数
- {n}正好出现n次
- 量词只对它前面的一个内容起作用
- {m,n}出现m-n次(包含m,n)
- {m,}出现m次以上
- +:表示{1,}
- *:表示{0,}
- ?:{0,1}
检察一个字符串中是否以a开头 ^ 表示开头 $ 表示结尾
如果在正则表达式中同时使用^,$要求字符串必须完全符合正则表达式
reg=/^a$/ //就是a
创建一个正则表达式,用来检察一个字符串是否是一个手机号
reg=/^1{3-9}[0-9]{9}$/
. 表示任意字符
逻辑与和逻辑或
1在条件判断中使用她们
2在赋值操作中,我们有时也会使用她们
Var A=1||2:首先验证1是真还是假,若真,把1赋值给A,若为假,把2给A
Var A=1&&2:首先验证1是真还是假,若真,把2给A,若假,把1给A
Function fn(x)
{//给形式参数赋值默认值:验证传递参数值,如果没有传递实參,其默认值为零
if(x===undefined)//三个等号,undefined(false,(0)) {x=0} if(typeof x===’undefined’) {x=0} x=x||0(没有上述严谨,if这种是没传值,才会赋值默认值,这种是不传值或者传递的值是假,都让它等于零) } function fn(callback){ If(typeof callback===‘function’){ Callback();} Callback &&callback();//上面if判断的简写版(不严谨):默认callback要不然就传函数,要不然就不传 } Fn(function(){})//把一个函数作为值传递给形參,这种机制叫做回调函数
3逻辑与和逻辑或的混合应用模式
(逻辑与的优先级大于逻辑或)
console.log(0||1&&2||0||3&&2||1)//2
4逻辑或的实战应用:形參赋值默认值(初始化形參)
//es6新语法规范中可以直接给形參设置默认值
Function fn(x=0){}fn();
//如果x没有传递值(undefined也是没有传递值),默认值是零,一旦传递值,不管传递的是啥,都是按照传递的值处理的
var a=9;
function fn(){
a=0;
return function(b){
return b+a++;}}//函数返回值,就将作用域销毁,若返回地址则不销毁
var f=fn ();
console.log(f(5));
console.log(fn()(5));//进入fn函数,然后将5赋值给b
console.log(f(5));
Console.log(a)//5 5 6 2
<body> <input type="button" value="btn1"> <input type="button" value="btn2"> <input type="button" value="btn3"> <script> window.onload=function(){ var oBtn=document.getElementsByTagName('input');//指定标签名的对象集合 for(var i=0;i<oBtn.length;i++){ oBtn[i].onclick=function(){ alert(i); } } }//循环运行完了,但是btn是点击的时候才触发 </script> </body>
原因:作用域链的这种配置机制引出了一个副作用,即闭包只能取得包含函数中任何变量的最后一个值
解决方案: 通过匿名函数强制让闭包行为符合预期,如下:
window.onload=function(){ var oBtn=document.getElementsByTagName('input'); for(var i=0;i<oBtn.length;i++){ (function(i){ oBtn[i].onclick=function(){ alert(i); } })(i); } }
或者用Es6中的写法
window.onload=function(){ var oBtn=document.getElementsByTagName('input');//指定标签名的对象集合 for(let i=0;i<oBtn.length;i++){ oBtn[i].onclick=function(){ alert(i); } } }
或者直接添加index属性:
window.onload=function(){ var oBtn=document.getElementsByTagName('input');//指定标签名的对象集合 for(let i=0;i<oBtn.length;i++){ oBtn[i].index=(i+1); oBtn[i].onclick=function(){ alert(this.index); } } }