JavaScript学习笔记

js、css、html是前端的基础,其中js用来定义页面的功能。博客记录了js基础的笔记。

基础

变量名

  1. 格式:var 变量名 = 变量值。

        作用域分为函数域和全局域,函数域的优先级更高。

        1.都加var,在方法内则是局部变量,在方法外则是全局变量。

        2.在方法内,加var为局部变量,会首先声明该变量,赋值时刻是按代码顺序;不加var则是全局变量(在执行当前方法之后),但不会在函数中首先声明,如果有形参,则定义的是形参值,作用域仍为函数内部。

  1. 对大小写敏感
  2. 对于常量,要么采用全大写,要么封装在类里面,关闭类的写入权限。
  3. this:以方法的形式调用时,返回的就是方法中的元素。以函数的形式调用时,返回的就是window中的元素。

数据类型及常用方法

数据的原始类型包括:number、string、boolean、symbol、null、undefined。

常用方法为:

方法 参数及返回值 示例
parseInt(str,number) 参数2:进制位数;返回str包含的数字 parseInt('12px')  return 12
parseFloat(str) 返回str包含的浮点数 parseFloat('12px')  return 12.0
isNaN(value) 将其参数转换为数字,然后检测它是否为 NaN(NaN表示一个错误,不能用===进行比较),返回true/False alert( isNaN(NaN) ); // true alert( isNaN("str") ); // true
isfinite(value) 将参数转换为数字,如果是NaN/Infinity/-Infinity,则返回false。 alert( isFinite("15") ); // true alert( isFinite("str") ); // false,因为是一个特殊的值:NaN alert( isFinite(Infinity) ); // false,因为是一个特殊的值:Infinity
string.indexOf(substr,index) 从头查找子字符串位置;参数2表示开始检索的字符位置;找到则返回index,未找到返回-1. let str = 'Widget with id'; alert( str.indexOf('id', 2) ) // 12
string.substring(start,end) 查询对应位置的子字符串,允许负数 let str = "stringify"; // 这些对于 substring 是相同的 alert( str.substring(2, 6) ); // "ring" alert( str.substring(6, 2) ); // "ring" // ……但对 slice 是不同的: alert( str.slice(2, 6) ); // "ring"(一样) alert( str.slice(6, 2) ); // ""(空字符串)
string.substr(start,end) 从 start 开始获取长为 length 的字符串
string.slice(start,end) 从 start 到 end(不含 end
stringA.localeCompare(stringB)  比较A、B字符
截断文本

数组的常用方法

①增删查改方法

arr.push(...item)尾端添加元素、pop()尾端提取元素、shift()首端提取元素、unshift()首端添加元素。

arr.splice(startIndex[],deleteNum,element1,element2,element3...)从第startIndex个位置删除deleteNum个元素,并替换为element1,element2,element3...元素。该方法能够实现增删查改的所有操作

arr.slice(startIndex,endIndex)表示将startIndex至endIndex的元素复制到一个新数组,不包括endIndex的元素。

②拼接方法

arr1.concat(arr2)将数组arr2拼接在arr1的尾端。

③遍历

number的精度问题

由于二进制有可能无法准确存储部分十进制浮点数,因此部分浮点数存在精度丢失的问题。如下:

alert(0.1+0.2===0.3)//结果为false

JavaScript学习笔记_第1张图片

JavaScript学习笔记_第2张图片

解决方法:

①使用Js原生方法parseFloat(value) + toFixed(digits),表示将数字先转换为数字类型,再通过toFixed()对该数字进行舍入。如下:

alert( parseFloat(0.1+0.2).toFixed(2) ); // "0.30"
alert( parseFloat(18466.67*100).toFixed(0) ); // "1846667"

JavaScript学习笔记_第3张图片

但是toFixed也存在一些问题,比如1.255.toFIxed(2)结果为1.25(未进行舍入),但是1.225.toFIxed(2)结果为1.23(进行舍入)。

②扩倍后再缩小,如要保留两位小数:0.1+0.2 = (0.1*100 + 0.2*100)/100 = 0.30。比较繁琐,不适合大数。

③使用第三方库Math.js或者big.js,官网地址如下:math.js | an extensive math library for JavaScript and Node.js (mathjs.org)和big.js API (mikemcl.github.io)。

例子如下:

big.js库:

0.1 + 0.2                  // 0.30000000000000004
x = new Big(0.1)
y = x.plus(0.2)            // '0.3'
Big(0.7).plus(x).plus(y)   // '1.1'

一些例子和练习

①写一个函数 ucFirst(str),并返回首字母大写的字符串 str

②写一个函数 checkSpam(str),如果 str 包含 viagra 或 XXX 就返回 true,否则返回 false

  function checkSpam(str) {
    let lowerStr = str.toLowerCase();
  
    return lowerStr.includes('viagra') || lowerStr.includes('xxx');
  }

③创建函数 truncate(str, maxlength) 来检查 str 的长度,如果超过 maxlength —— 应使用 "…" 来代替 str 的结尾部分,长度仍然等于 maxlength

  // ...你的代码...
  function truncate(str, maxlength){
    if(str.length < maxlength){
      return str;
    }
    return str.substr(0,maxlength-1)+'…';
  }

④我们有以 "$120" 这样的格式表示的花销。意味着:先是美元符号,然后才是数值。

创建函数 extractCurrencyValue(str) 从字符串中提取数值并返回。

  // ...你的代码...
  function extractCurrencyValue(str){
    return +str.slice(1)
  }

 
  

逻辑

1、for语句

for (初始语句; 条件; 条件为真值时执行的语句) {
  // 循环体
}

2、for...in...,可以遍历属性,遍历结束则直接终止。举例:

var obj = {
  name: '小红',
  age: 12,
  hobby: ['打篮球', '唱歌'],
};

for (key in obj) {
  console.log(obj[key]);
}

// 输出:
//   "小红"
//   12
//   ["打篮球", "唱歌"]

3、数组的foreach

var arr = ['第一项', '第二项', '第三项', '第四项', '第五项'];

arr.forEach(function(item, index, arr) {
  console.log('第' + (index + 1) + '项的值是:' + item);
});

运算符

二元运算符:+ 加法、- 减法、* 乘法、/ 除法、% 求余、** 幂 (ES2016 提案)。

一元运算符:+ 一元正号、- 一元负号、++ 递增、-- 递减、比较运算符。

相等运算符:== 相等、!= 不相等、=== 严格相等、!== 严格不相等。

关系运算符:> 大于、>= 大于等于、< 小于、<= 小于等于。

逻辑运算符:

  • && 与 (并且)、|| 或 (或者)、! 非 (取反)

转义表

\' 单引号
\" 双引号
\& 和号
\\ 反斜杠
\n 换行符
\r 回车符
\t 制表符
\b 退格符
\f 换页符

对象

对象创建及属性访问

对象中包含属性、方法,采用键值对的形式:

{
  prop1: 'value1',
  prop2: 666,
  prop3: {},
  method1: function() {
  },
}
var person = {
  'name': '小明',
  'age': 17,
  isAdult: false,
  sex: 'man',
  hobby: ['eat', 'sleep', 'play doudou'],
  parents: {
    mather: {
      name: '大红',
    },
    father: {
      name: '大明',
    },
  },
  say: function() {
    console.log('我叫' + this.name + ',我今年' + this.age + '岁了。');
  },
};

console.log(person); // 在控制台可以观察 person 对象
console.log(person.say())

可以看到,对象可以包含属性、方法,方法可以是对象或者是函数形式。

在访问对象属性的时候,可以采用两种形式:对象.属性名;对象['属性名']。访问方法时,需加上方法的参数,参数可为空。

创建对象的其他方法:①创建空对象直接采用new object即可。②调用create方法:Object.create(对象)。

对象属性的遍历

方式一:for循环,采用for...in的形式。如前所述。

方式二:keys。先获取key列表,再据其查询属性值。

      var objtest = {
          'color' : 'red',
          'length' : '100cm'
      };
      var keys = Object.keys(objtest);
      console.log(keys);
      keys.forEach(function(key) {
          console.log(objtest[key]);
      });

方式三:使用 Object.getOwnPropertyNames。

对象原型

对象原型是每个对象都有的隐藏属性。例如,通过构造函数创建对象后,类以及各个对象都会有隐藏属性:prototype。

prototype是类的一个属性,可以给该属性赋予键值对,如 Class.prototype.属性名 = 属性值。这样能够复用方法,减少开销。

举例:通过构造函数创造了两个对象,但两个对象共用prototype中的length属性。

        //构造函数
        function Dog(name){
            this.name = name;
        }

        Dog.prototype.length = 15

        var dog1 = new Dog("ali");
        console.log(dog1.name + dog1.length);//ali15

        var dog2 = new Dog("jack");
        console.log(dog2.name + dog2.length);//jack15

对象的引用和复制

对象变量保存的是对象本身的引用,因此,直接复制的结果是引用的复制。

浅拷贝

对于简单变量则需要浅拷贝,使用Object.assign:

//dest为目标对象,src为原对象属性,可一次拷贝多个对象属性。
Object.assign(dest, [src1, src2, src3...])

例子如下:

let user = { name: "John" };

let permissions1 = { canView: true };
let permissions2 = { canEdit: true };

// 将 permissions1 和 permissions2 中的所有属性都拷贝到 user 中
Object.assign(user, permissions1, permissions2);

// 现在 user = { name: "John", canView: true, canEdit: true }

深拷贝

若对象还包含其他引用,则需要使用深拷贝:_.cloneDeep(obj)。例子:

var objects = [{ 'a': 1 }, { 'b': 2 }];
 
var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false

对象的this

对象中的函数称为方法,方法内可以引用当前对象为this。当对象不存在时,this为undefined。

关于this的例子如下所示:

let user = { name: "John" };
let admin = { name: "Admin" };

function sayHi() {
  alert( this.name );
}

// 在两个对象中使用相同的函数
user.f = sayHi;
admin.f = sayHi;

// 这两个调用有不同的 this 值
// 函数内部的 "this" 是“点符号前面”的那个对象
user.f(); // John(this == user)
admin.f(); // Admin(this == admin)

admin['f'](); // Admin(使用点符号或方括号语法来访问这个方法,都没有关系。)

对象属性的可选链

可选链 ?. 语法有三种形式:

  1. obj?.prop —— 如果 obj 存在则返回 obj.prop,否则返回 undefined
  2. obj?.[prop] —— 如果 obj 存在则返回 obj[prop],否则返回 undefined
  3. obj.method?.() —— 如果 obj.method 存在则调用 obj.method(),否则返回 undefined

字符串

str.length

字符串拼接

三种方式:+、[str1,str2].join("")、str1.concat(str2)

常用的字符串方法:

方法 描述
replace replace 方法返回一个由替换值替换一些或所有匹配的模式后的新字符串。
match match 方法检索返回一个字符串匹配正则表达式的的结果。
split split 方法使用指定的分隔符字符串将一个String对象分割成子字符串数组,以一个指定的分割字串来决定每个拆分的位置
substring substring 方法返回一个字符串在开始索引到结束索引之间的一个子集, 或从开始索引直到字符串的末尾的一个子集。
toLocaleLowerCase toLocaleLowerCase 方法根据任何指定区域语言环境设置的大小写映射,返回调用字符串被转换为小写的格式。
toLocaleUpperCase toLocaleUpperCase 使用本地化(locale-specific)的大小写映射规则将输入的字符串转化成大写形式并返回结果字符串。

。 

构造函数

构造函数也属于普通函数,提供了同一种对象的创建模版类。两者首先在写法上有所区别:

        //普通函数
        function dog(name) {
            var obj1 = new Object();
            obj1.name = name;
            return obj1;
        }
        console.log(dog("ali"));
        console.log(dog("ali") instanceof Object);//true

        //构造函数
        function Dog(name){
            this.name = name;
        }

        var dog = new Dog("ali");
        console.log(dog.name);
        console.log(dog instanceof Dog);//true
  1. 命名方式有所不同。构造函数通常为大写字母开头。
  2. 调用方式不同。构造函数需要以new的形式。
  3. this指代不同。构造函数中的this表示实例对象,函数中的可能指向windows。

正则表达式

正则表达式主要是用来匹配、验证字符串的方法。常见方式有两种:构造函数RegExp;直接采用字面量构建。

        var exp1 = new RegExp("a","i");
        console.log(exp1.test("bc"));

        var exp2 = /a/i;//与构造器等价
        console.log(exp2.test("bc"));

        var exp3 = /a[de]c/;
        console.log(exp3.test("awvaec"));

其中,i表四忽略大小写,还有g表示全局匹配模式。

方括号[] 表示或者,如[de]表示d或者e。{}表示字符出现的次数,如(ab){3}表示ababab。

另外,正则表达式还可以与字符串方法联合使用。

一些量词

n+ 匹配任何包含至少一个 n 的字符串。
n* 匹配任何包含零个或多个 n 的字符串。
n? 匹配任何包含零个或一个 n 的字符串。
n{X} 匹配包含 X 个 n 的序列的字符串。
n{X,Y} 匹配包含 X 至 Y 个 n 的序列的字符串。
n{X,} 匹配包含至少 X 个 n 的序列的字符串。
n$ 匹配任何以 n 结尾的字符串。
^n 匹配任何以 n 开头的字符串。
?=n 匹配任何其后紧接指定字符串 n 的字符串。
?!n 匹配任何其后没有紧接指定字符串 n 的字符串。

邮件的正则表达式

        //邮箱:[email protected]
        var mailExp = /^\w{3,}(\.\w+)*@[A-z0-9]+(\.([A-z]){2,10}){1,2}$/;
        console.log(mailExp.test("[email protected]"))

内置对象

Function

var fn = new Function(函数参数1, 函数参数2, ..., 函数参数n, 函数体);

使用Function实现动态语言:

  

Date

var date = new Date()

时间戳、格式化时间、当前时间的相互转化:

  

DOM

DOM是一种标准,定义了HTML的结构,将其中的元素定义为节点,如:整个文档是一个节点、元素是节点、属性也是节点。

获取元素的方法有多个。

document.getElementsByXX

有一些内置方法,如:getElementById(通过id属性获取单个元素节点对象)、getElementsByName(通过name属性获取一组元素节点对象)、getElementsByTagName(通过标签名获取一组元素节点对象,返回类数组对象,可调用innerHTML遍历)等,即通过元素的属性值进行筛选,并得到结果。如下示例。

该案例结合了getElementById、getElementsByName。

还有一个getElementByTagName,是通过标签名称进行筛选,如下举例。

我是第一个段落。

我是第二个段落。

我是第三个段落。

我是第四个段落。

我是第五个段落。

还有方法:getElementsByClassName,使用方法类似,不再赘述。

还有querySeletor,主要针对CSS样式的查询:node.querySelector(CSS样式器)。如下,CSS的样式ID为tip,则查询条件为‘#tip’。querySeletorAll的使用方法类似。

今日大甩卖!!一双袜子 块!三双袜子只要 块!!

xx.getElementsxx

查找主体不同,上述的主体是document,这次的主体将范围缩小为对象。

  1. getElementsByTagName
  2. childNodes:统计元素的所有子节点的集合,节点之间的空白也会被当成节点。
  3. children:返回非空的所有子节点。
  4. firstChild
  5. lastChild

DOM与事件

事件表示网页的一系列操作,比如:点击、放大、加载成功等。一般需要在根据事件的发生产生一定的页面变化,比如点击按钮后变化颜色等。因此,需要监听这些事件。而事件的监听首先需要获取到DOM节点,然后调用相应的监听方法(一般是以on开头),如 点击事件就是 onclick。如下:





使用element.addEventListener绑定事件

采用的格式:

受监听的元素名.addEventListener(‘事件名,如click’,监听后操作,如function(){方法体}),

举例如下:

与事件的绑定addEventListener相反,解除绑定:removeEventListener。在上述示例中,input.removeEventListener即可。

获取事件对象的属性和方法

1、target

target表示事件触发的元素,

难以理解

我是第一个节点 a
我是第二个节点 b
我是第三个节点 c
我是第四个节点 d
我是第五个节点 e
我是最里面的一个元素 f

Promise与async/await

promise是完成异步功能的对象,意为诺言,表示经过一系列操作后会返回结果。Promise对象有几个关键要素:1个对象、2个方法、3个状态。

promise基础

1个对象指promise对象,创建Promise对象的主体称为生产者。生产者创建Promise对象时,接受两个参数:resolve和reject。resolve(value)表示:当消费该代码后,顺利完成任务并携带结果value;reject(error)表示,消费该代码后,发生错误,传递的就是error对象。

let promise = new Promise(function(resolve, reject) {
  // executor(生产者代码),两者选其一
    resolve("success result");
    reject("error occur")
});

2个方法:then()、catch()。作为消费者,需要调用生产者代码,然而生产者中的stat和result都是内部的,因此需要借助暴露出的方法then()、catch()获取运行结果。其中then(param1,param2)的2个参数分别对应生产者成功运行后的Result和运行失败后的Error,如下:

let promise = new Promise(function(resolve, reject) {
  setTimeout(() => resolve("done!"), 1000);
});

// resolve 运行 .then 中的第一个函数
promise.then(
  result => alert(result), // 1 秒后显示 "done!"
  error => alert(error) // 不运行
);

注意,一次运行只会运行result和error其中之一,根据运行结果,另一个会被忽略。

3个状态state指消费者调用生产者代码后,执行结果的三种状态:fulfilled、rejected、pending。每种状态都对应相应的结果:fulfilled->resolve(result) 、rejected->(error) 、pending->undefined。

整体示例如下:

------------------------- //  3s后喝咖啡,  再等2s,看电影------------------------------------

function getKaFei() {//喝咖啡
        let p = new Promise(function (resolve, reject) {
            //  resolve 返回成功的结果,还可以帮助我们取出异步代码的值
            //  reject 返回失败的结果,还可以帮助我们取出异步代码的值
            setTimeout(function () {
                resolve("喝咖啡")
            }, 3000)
        })
        return p;
    }
    // 看电影
    function getMovie() {
        let p = new Promise(function (resolve, reject) {
            //  resolve 返回成功的结果,还可以帮助我们取出异步代码的值
            //  reject 返回失败的结果,还可以帮助我们取出异步代码的值
            setTimeout(function () {
                resolve("看电影")
            }, 2000)
        })
        return p;
    }
    // getKaFei()它就是promise对象,是promise对象就可以用.then()取出 resolve的结果
    getKaFei()
        .then((data) => { console.log(data); return getMovie() })// return getMovie()就可以执行下一个.then
        .then(data => { console.log(data) })

Promise链

promise链通过.then传递promise的result,每次的then处理程序会新生成一个promise对象。Promise链不是同一个promise对象的简单重复调用,而是会构建新promise对象进行传递。例子如下:

new Promise(function(resolve, reject) {

  setTimeout(() => resolve(1), 1000); // (*)

}).then(function(result) { // (**)

  alert(result); // 1
  return result * 2;

}).then(function(result) { // (***)

  alert(result); // 2
  return result * 2;

}).then(function(result) {

  alert(result); // 4
  return result * 2;

});

以下是复杂场景的链式调用,首先是多个then串联,result会传递;其次在then内部还新构建新的Promise对象:

new Promise(function(resolve, reject) {

  setTimeout(() => resolve(1), 1000);

}).then(function(result) {

  alert(result); // 1

  return new Promise((resolve, reject) => { // (*)
    setTimeout(() => resolve(result * 2), 1000);
  });

}).then(function(result) { // (**)

  alert(result); // 2

  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(result * 2), 1000);
  });

}).then(function(result) {

  alert(result); // 4

});

PromiseAPI

共有6种API,仅展示前两个。

  1. Promise.all(promises) —— 等待所有 promise 都 resolve 时,返回存放它们结果的数组。如果给定的任意一个 promise 为 reject,那么它就会变成 Promise.all 的 error,所有其他 promise 的结果都会被忽略。
  2. Promise.allSettled(promises)(ES2020 新增方法)—— 等待所有 promise 都 settle 时,并以包含以下内容的对象数组的形式返回它们的结果:
    • status"fulfilled" 或 "rejected"
    • value(如果 fulfilled)或 reason(如果 rejected)。
  3. Promise.race(promises) —— 等待第一个 settle 的 promise,并将其 result/error 作为结果返回。
  4. Promise.any(promises)(ES2021 新增方法)—— 等待第一个 fulfilled 的 promise,并将其结果作为结果返回。如果所有 promise 都 rejected,Promise.any 则会抛出 AggregateError 错误类型的 error 实例。
  5. Promise.resolve(value) —— 使用给定 value 创建一个 resolved 的 promise。
  6. Promise.reject(error) —— 使用给定 error 创建一个 rejected 的 promise。

Promise.all()

参数为promise对象数组,返回结果为各个promise对象数组的结果,其顺序与代码顺序相关。下例中首先将urls数组映射为promise对象数组后,再将其作为参数调用promiseAll。最后的效果为依次弹窗url数组及其返回状态码。

let urls = [
  'https://api.github.com/users/iliakan',
  'https://api.github.com/users/remy',
  'https://api.github.com/users/jeresig'
];

// 将每个 url 映射(map)到 fetch 的 promise 中
let requests = urls.map(url => fetch(url));

// Promise.all 等待所有任务都 resolved
Promise.all(requests)
  .then(responses => responses.forEach(
    response => alert(`${response.url}: ${response.status}`)
  ));

注意,只要其中有一个promise抛出error,则整个promiseAll将中断(其他promise将被忽略)并抛出该error。

Promise.allSettled()

该api作为Promise.all()的补充,使用格式与all一致。当promise对象数组中的某promise对象发生error时,仍返回status和value/reason。

关键字async/await

async的作用为:让一个普通函数能够返回一个promise对象,从而使得非异步普通函数转变为异步。

await的作用为:await通常设置在promise对象的resolve上,当运行到该处时,程序会进行等待,直到异步对象运行完毕后,再执行其他代码,从而保证代码的稳定性。不能在非async函数中使用await

async+await结合后的例子如下,该例子使用try/catch处理error。

async function f() {

  try {
    let response = await fetch('http://no-such-url');
  } catch(err) {
    alert(err); // TypeError: failed to fetch
  }
}

f();

一般使用上述方法较多。也可以不用try进行包裹,这样就不能自由处理error,修改如下,该部分代码逻辑与普通promise一致。

async function f() {
   let response = await fetch('http://no-such-url');
}

f().catch(alert);

其他

微任务和promise处理代码的执行顺序:一般来说,promise异步任务会后于同步代码的执行,这是因为微任务队列的存在,微任务队列保持了队列先入先出的特性,所有异步任务都会进入该队列,且当其他任务完成后才会处理微任务。(微任务(Microtask) (javascript.info))

相关demo

1、重写下面的示例代码,使用 async/await 而不是 .then/catch

示例代码为:

function loadJson(url) {
  return fetch(url)
    .then(response => {
      if (response.status == 200) {
        return response.json();
      } else {
        throw new Error(response.status);
      }
    });
}

loadJson('https://javascript.info/no-such-user.json')
  .catch(alert); // Error: 404

完美修改方法:

async function loadJson(url) {
  // return fetch(url)
  //   .then(response => {
  //     if (response.status == 200) {
  //       return response.json();
  //     } else {
  //       throw new Error(response.status);
  //     }
  //   });

    let response=await fetch(url);
      if (response.status == 200) {
         return await response.json();
      }
      throw new Error(response.status);
}

loadJson('https://javascript.info/no-such-user.json')
  .catch(alert); // Error: 404

上述代码会抛出error404异常。

简略修改方法:

function loadJson(url) {
  // return fetch(url)
  //   .then(response => {
  //     if (response.status == 200) {
  //       return response.json();
  //     } else {
  //       throw new Error(response.status);
  //     }
  //   });

    let response=await fetch(url);
    //  if (response.status == 200) {
    //     return await response.json();
    //  }
    //  throw new Error(response.status);
}

loadJson('https://javascript.info/no-such-user.json')
  .catch(alert); // Error: 404

上述代码会按照正常流程抛出typeerror异常。

2、我们有一个名为 f 的“普通”函数。你会怎样调用 async 函数 wait() 并在 f 中使用其结果?

async function wait() {
  await new Promise(resolve => setTimeout(resolve, 1000));

  return 10;
}

function f() {
  // 1 秒后显示 10
  wait().then(result => alert(result));
}

f();

将await视作普通的promise对象,调用then方法即可

document

此处记录常用的方法。

DOM对象中搜索元素节点的方法大致有六种:

方法名 返回结果 搜索条件
querySelector 返回第一个匹配的元素 CSS-Selector
querySelectorAll 返回所有匹配的元素 CSS-Selector
getElementById 返回匹配的元素(因id唯一,故结果唯一) id
getElementsByName 返回所有匹配元素的集合 name
getElementsByTagName 返回所有匹配元素的集合 tag
getElementsByClassName 返回所有匹配元素的集合 class

你可能感兴趣的:(javascript,学习,笔记)