爬虫&逆向--Day06--Javascript基础
一、
1992年底,美国国家超级电脑应用中心(NCSA)开始开发一个独立的浏览器,叫做Mosaic。这是人类历史上第一个浏览器,从此网页可以在图形界面的窗口浏览。但是该浏览器还没有面向大众的普通用户。
1994年10月,NCSA的一个主要程序员Jim Clark,成立了一家Netscape通信公司。这家公司的方向,就是在Mosaic的基础上,开发面向普通用户的新一代的浏览器Netscape Navigator。
1994年12月,Navigator发布了1.0版,市场份额一举超过90%。
Netscape 公司很快发现,Navigator浏览器需要一种可以嵌入网页的脚本语言,用来控制浏览器行为,因为当时,网速很慢而且上网费很贵,有些操作不宜在服务器端完成。比如,如果用户忘记填写“用户名”,就点了“发送”按钮,到服务器再发现这一点就有点太晚了,最好能在用户发出数据之前,就告诉用户“请填写用户名”。这就需要在网页中嵌入小程序,让浏览器检查每一栏是否都填写了。
管理层对这种浏览器脚本语言的设想是:功能不需要太强,语法较为简单,容易学习。
1995年5月,Netscape 公司只用了10天,就设计完成了这种语言的第一版。
Netscape 公司的这种浏览器脚本语言,最初名字叫做 Mocha,1995年9月改为LiveScript。12月,Netscape公司与Sun公司(Java语言的发明者和所有者)达成协议,后者允许将这种语言叫做JavaScript。这样一来,Netscape公司可以借助Java语言的声势,而Sun公司则将自己的影响力扩展到了浏览器,索引并不是因为JavaScript本身与Java语言有多么深的关系才叫做javaScript。
1996年3月,Navigator 2.0 浏览器正式内置了 JavaScript 脚本语言,也就是可以直接在浏览器中运行JavaScript 脚本语言。
JS的作用:JavaScript, 是一门能够运行在浏览器上的脚本语言. 简称JS。简单来说可以处理前端的一些简单的业务逻辑和用户行为、网页事件的触发和监听。
那么既然JS是可以运行在浏览器上的脚本. 并且, 我们知道本质上, 浏览器是执行HTML程序的. 那么如何在HTML中引入JS呢?
方案一, 直接在<script>标签中引入编写js代码

方案二, 将js代码写在js文件中, 然后通过script标签的src属性进行引入

1.1、环境安装
Node.js是一个开源的、跨平台的JavaScript运行时环境!
进入官网地址下载安装包
官网:
选择对应你系统的Node.js版本

安装程序
(1)下载完成后,双击

(2)直接点【Next】按钮,此处可根据个人需求修改安装路径,我这里路径改为了D:\CodeSoftware\Node\Node,修改完毕后继续点击【Next】按钮

(3)不选中,直接点击【Next】按钮(不做前端开发的话,没必要安装这些工具,减少内容)

(4)测试安装是否成功,按下【win+R】键,输入cmd,打开cmd窗口,输入:

成功显示版本说明安装成功!
1.2、Pycharm配置
-
文件->设置
![]()
-
插件(Plugins)->插件市场搜索node,安装
![]()
-
检查语言与框架中Node.js的配置,我的自动添加了路径,如果没有,配置前边安装的node.js的路径即可。
-
![]()
-
然后就可以使用PyCharm去运行js文件了。

JS虽然是一个脚本语言. 麻雀虽小, 五脏俱全. 在js中也是可以像其他编程语言一样. 声明变量, 条件判断, 流程控制等等. 我们先看一下JS中的数据类型
在js中主要有这么几种数据类型

在js中声明变量用var来声明
在js中使用// 来表示单行注释. 使用/* */表示多行注释.
var 变量名; // 创建变量, 此时该变量除了被赋值啥也干不了. var 变量名 = 值; // 创建一个变量, 并且有值. var 变量名 = 值1, 变量名2 = 值2, 变量名3 = 值3.....; // 一次创建多个变量.并都有值 var 变量名1, 变量名2, 变量名3 = 值3; // 创建多个变量. 并且只有变量3有值
数据类型转换:
//string --> number :parseInt(字符串) var a = "10086" a = parseInt(a); // 把字符串a转换成整数 console.log(a + 10); //10096 console.log(typeof a, a) //number 10086 // number -> string : 数字.toString() 或者 数字 + "" var a = 100; var b = a.toString(); console.log(typeof a, a) //number 100 console.log(typeof b, b) //string 100 var c = a + ""; console.log(typeof a, a) //number 100 console.log(typeof c, c) //string 100 // number -> string: 数字转化成16进制的字符串 var m = 122; var n = m.toString(16); console.log(typeof m,m); //number 122 console.log(typeof n,n); //string 7a // 进制转换:十六进制的AB转换成十进制是多少 var d = parseInt("AB", 16); console.log(typeof d, d); //number 171 // 自动转换:弱类型中的变量会根据当前代码的需要,进行类型的自动隐式转化 var box1 = 1 + true; // true 转换成数值,是1, false转换成数值,是0 console.log(typeof box1, box1); //number 2
// split 正则分割,经常用于把字符串转换成数组 var str = "广东-深圳-南山"; var ret = str.split("-"); console.log(ret); // [ '广东', '深圳', '南山' ] // substr substring 截取 截取三个字符 var str = "hello world"; var ret = str.substring(0, 3) // var ret = str.substr(0,3); 截取从下标0到下标2的三个字符,以字符串的形式输出 console.log(typeof ret, ret); // string hel // trim 移除字符串首尾空白 或 特殊字符,比如:\n 换行符 等 var password = " ge llo "; // \n换行符也可以移除 var ret = password.trim(); console.log(password.length); // 13 console.log(ret.length, ret); // 6 ge llo // 切片,当前方法支持使用负数代表倒数下标 // slice(开始下标) 从开始位置切到最后 // slice(开始下标,结束下标) 从开始下标切到指定位置之前 var str = "0123456789"; var str1 = "helloworld" var ret = str.slice(3, 6); // 开区间,不包含结束下标的内容 var ret1 = str1.slice(3, 6) console.log(ret, ret1); // 345 low var ret = str.slice(5); var ret1 = str1.slice(5); console.log(ret, ret1); // 56789 world var ret = str.slice(2, -1); //切到最后一个元素,但是不包含最后一个元素 var ret1 = str1.slice(2, -1); console.log(ret, ret1); // 2345678 lloworl var str = "0123456789"; var str1 = "helloworld" //s.substring(start, end) //字符串切割, 从start切割到end var ret = str.substring(2, 5) var ret1 = str1.substring(2, 5) console.log(ret, ret1) // 234 llo //s.length //字符串长度 var ret = str.length var ret1 = str1.length console.log(ret, ret1) // 10 10 //s.charAt(i) //第i索引位置的字符 s[i] var ret = str.charAt(3) var ret1 = str1.charAt(3) console.log(ret, ret1) //3 l //s.indexOf('xxx') //返回xxx的索引位置, 如果没有xxx. 则返回-1 var ret = str.indexOf('3') var ret1 = str1.indexOf('l') // 返回的是第一个l的索引位置 console.log(ret, ret1) //3 2 //s.lastIndexOf("xxx") //返回xxx的最后一次出现的索引位置,如果没有xxx. 则返回-1 var ret = str.lastIndexOf('3') var ret1 = str1.lastIndexOf('l') // 返回的是最后一次出现索引的位置 console.log(ret, ret1) //3 8 s.toUpperCase() //转换成大写字母 s.startsWith("xxx") //判断是否以xxx开头 s.charCodeAt(i) //某个位置的字符的ascii String.fromCharCode(ascii) //给出ascii 还原成正常字符
// match 正则匹配 // js中也存在正则,正则的使用符号和python里面是一样的 // 语法:/正则表达式主体/修饰符(可选) //修饰符: //i:执行对大小写不敏感的匹配。 //g:执行全局匹配(查找所有匹配而非在找到第一个匹配后停止)。 var str = "我的电话是: 13312345678,你的电话: 13512345678"; var ret = str.match(/\d{11}/g); // 匹配,提取数据 \d是数字,{11}允许数字出现11次 console.log(ret); // [ '13312345678', '13512345678' ] // replace 正则替换 var str = "我的电话是: 13512345678"; var ret = str.replace(/(\d{3})\d{4}(\d{4})/, "$1****$2"); // 正则的捕获模式 $1$2表示的正则中第一个和第二个小括号捕获的内容 console.log(ret); // 我的电话是: 135****5678 // search 正则查找,如果查找不到,则返回-1 var str = "hello"; var ret = str.search(/l/); // \d 是数字,返回-1 返回搜索的结果出现的次数 console.log(ret); // 2
-
undefined类型
(1) 当声明的变量未初始化时,该变量的默认值是 undefined。
(2)当函数无明确返回值时,返回的也是值 undefined;
-
null类型
另一种只有一个值的类型是 null,它只有一个专用值 null,即它的字面量。值 undefined 实际上是从值 null 派生来的,因此 js 把它们定义为相等的。
尽管这两个值相等,但它们的含义不同。undefined 是声明了变量但未对其初始化时赋予该变量的值,null 则用于表示尚未存在的对象。如果函数或方法要返回的是对象,那么找不到该对象时,返回的通常是 null。
-
原始值和引用值
// 初始值类型 var a = "hahahaha"; var b = a; //b引用了a 都指向相同的字符串 a = "alvin"; console.log(a); //alvin console.log(b); //hahahaha // 对象类型 var arr1=[1,2]; arr2 = arr1; arr1.push(3); console.log(arr1) // [ 1, 2, 3 ] console.log(arr2); // [ 1, 2, 3 ] arr1=[4,5]; console.log(arr1); // [ 4, 5 ] console.log(arr2); // [ 1, 2, 3 ]
//算术运算符 + 数值相加 - 数值相减 * 数值相乘 / 数值相除 % 数值求余 ** 数值求幂 a++ 变量被使用后自增1 var a = 10 console.log(a++) // 输出结果为10 console.log(a) // 输出结果为11 //++:前++和后++ //后++:先使用后+1 //前++:先+1,后使用 var a = 10 var b = a++ // var b=a; a=a+1 console.log(a,b) // 11 10 var a=10; var b=++a; // a=a+1;b=a console.log(a,b) // 11 11 // ++a 变量被使用前自增1 var b = 10 console.log(++b) // 输出的就是11 // b-- 变量被使用后自减1 先使用,在-- // --b 变量被使用前自减1 先--,在使用 // 在python中是没有++操作的. 但是在js中是有的. a++; // 翻译一下就是a = a + 1 ++a; // 翻译一下就是a = a + 1 a--; // 翻译一下就是a = a - 1 --a; // 翻译一下就是a = a - 1 //赋值运算符 = += -= *= /= %= **= //比较运算符,比较的结果要么是true, 要么是false > 大于 < 小于 >= 大于或者等于 <= 小于或者等于 != 不等于[计算数值] == 等于[计算] // !== 不全等[不仅判断数值,还会判断类型是否一致] // === 全等[不仅判断数值,还会判断类型是否一致] var num1 = 3.14; let num2 = "3.14"; console.log(num1 == num2); // true,因为==运算符会进行类型转换,比较它们的值是否相等 console.log(num1 === num2); // false,因为===运算符不会进行类型转换,比较它们的值和类型是否都相等 console.log(num1 !== num2); // true,会比较数值或类型相等 //逻辑运算符 && 并且 and 两边的运算结果为true,最终结果才是true || 或者 or 两边的运算结果为false,最终结果才是false ! 非 not 运算符的结果如果是true,则最终结果是false ,反之亦然. // 条件运算符[三目运算符] // 条件(表达式) ? true : false var age = 12; var ret = age >= 18 ? "成年" : "未成年"; console.log(ret); // 未成年
编程语言的流程控制分为三种:
-
顺序结构(从上向下顺序执行)
-
分支结构
-
之前我们学习的方式就是顺序执行,即代码的执行从上到下,一行行分别执行。
例如:
console.log("星期一"); // 星期一
console.log("星期二"); // 星期二
console.log("星期三"); // 星期
if (条件) { // 条件为true时,执行的代码 } if (条件) { // 条件为true时,执行的代码 } else { // 条件为false时,执行的代码 } if (条件1) { // 条件1为true时,执行的代码 } else if (条件2) { // 条件2为true时,执行的代码 } else { // 上述条件都不成立的时候,执行的代码 }
switch (条件) { case 结果1: // 满足条件执行的结果是结果1时, 执行这里的代码.. break; case 结果2: // 满足条件执行的结果是结果2时, 执行这里的代码.. break; ..... default: // 条件和上述所有结果都不相等时, 则执行这里的代码 } var choose = 'yy'; switch (choose) { case 'y': console.log('yes'); break; case 'n': console.log('no'); break; default: console.log('nothing'); // nothing break; }
2、执行效率更高。switch…case会生成一个跳转表来指示实际的case分支的地址,而这个跳转表的索引号与switch变量的值是相等的。从而,switch…case不用像if…else那样遍历条件分支直到命中条件,而只需访问对应索引号的表项从而到达定位分支的目的。
while (循环的条件) { // 循环条件为true的时候,会执行这里的代码 }
var count = 0 while (count < 10) { console.log(count); // 0 1 2 3 4 5 6 7 8 9 count++; }
// 循环三要素 for(1.声明循环的开始; 2.条件; 4. 循环的计数){ // 3. 循环条件为true的时候,会执行这里的代码 } // 循环的执行步骤 1 2 3 4 2 3 4 2 3 4 for(被循环数据的下标 in 被循环的数据){ // 当被循环的数据一直没有执行到最后下标时(最后一位时),就会不断执行这里的代码 }
// 方式1 for (var i = 0; i < 10; i++) { console.log(i) // 0 1 2 3 4 5 6 7 8 9 } // 方式2 var arr = [111, 222, 333] for (var i in arr) { console.log(i, arr[i]) } /* 0 111 1 222 2 333 */
for (var i = 0; i < 10; i++) { if (i === 8) { //continue // 退出当次循环,继续开始下一次新的循环 break // 退出当前整个循环 } console.log(i) } /* continue 0 1 2 3 4 5 6 7 9 缺少8,因为continue跳过本次循环,继续下一次循环 */ /* break 0 1 2 3 4 5 6 7 到8的时候停止,因为break,退出当前整个循环 */
// 创建方式1: // var arrname = [元素0,元素1,….]; var arr = [1, 2, 3]; console.log(arr) // [ 1, 2, 3 ] // 创建方式2: // var arrname = new Array(元素0,元素1,….); var test = new Array(100, "a", true); console.log(test) // [ 100, 'a', true ]
var arr = ["A", "B", "C", "D"]; // 内置属性 console.log(arr.length); // 4 // 获取指定下标的成员 console.log(arr[3]); // D console.log(arr[arr.length - 1]); // D 获取最后一个成员 // (1) pop() 出栈,删除最后一个成员,并把删除的最后一个成员返回 var arr = [1, 2, 3, 4, 5]; var ret = arr.pop(); console.log(arr); // [1, 2, 3, 4] console.log(ret); // 5 // (2) push() 入栈,给数组后面追加成员 var arr = [1, 2, 3, 4, 5]; arr.push("a"); console.log(arr); // [1, 2, 3, 4, 5, "a"] // (3) shift是将数组的第一个元素删除 var arr = [1, 2, 3, 4, 5]; arr.shift() console.log(arr); // [2, 3, 4, 5] // (4) unshift是将value值插入到数组的开始 var arr = [1, 2, 3, 4, 5]; arr.unshift("hahahaha") console.log(arr); // [ 'hahahaha', 1, 2, 3, 4, 5 ] // (5) reverse() 反转排列 var arr = [1, 2, 3, 4, 5]; arr.reverse(); console.log(arr); // [5, 4, 3, 2, 1] // (6) slice(开始下标,结束下标) 切片,开区间 包含开始,不包含结束 var arr = [1, 2, 3, 4, 5]; console.log(arr.slice(1, 3)); // [ 2, 3 ] // (7) concat() 把2个或者多个数组合并 var arr1 = [1, 2, 3]; var arr2 = [4, 5, 7]; var ret = arr1.concat(arr2); console.log(ret); // [ 1, 2, 3, 4, 5, 7 ] // (8) split() 把字符串按照指定的符号进行切割成单字符串放到数组中 var str = "广东-深圳-南山"; var arr = str.split("-"); console.log(arr); // ["广东", "深圳", "南山"]; // (9) join() 把数组的每一个成员按照指定的符号进行拼接成字符串 var arr1 = ["广东", "深圳", "南山"]; var str1 = arr1.join("-"); console.log(str1); // 广东-深圳-南山
var arr = [12, 23, 34] for (var i in arr) { console.log(i, arr[i]) } /* 0 12 1 23 2 34 */
Object 的实例不具备多少功能,但对于在应用程序中存储和传输数据而言,它们确实是非常理想的选择。
创建 Object 实例的方式有两种。
/***** 创建 Object 实例的方式有两种 方式一 **********/ var person = new Object(); //创建好了一个对象 person.name = "alvin"; person.age = 18; console.log(person) // { name: 'alvin', age: 18 }
另一种方式是使用对象字面量表示法。对象字面量是对象定义的一种简写形式,目的在于简化创建包含大量属性的对象的过程。下面这个例子就使用了对象字面量语法定义了与前面那个例子中相同的person 对象:
var person1 = { name: "alvin", age: 18, say: function () { alert(123); } }; console.log(person1) // { name: 'alvin', age: 18, say: [Function: say] }
-
object可以通过. 和 []来访问。
console.log(person["age"]); // 18 console.log(person.age) // 18
-
object可以通过for循环遍历
for (var attr in person1) { console.log(attr, person1[attr]); } /* name alvin age 18 say [Function: say] */
-
最后一种创建Object对象的方式:
/* 当前创建好的一个对象,有两个属性 name 和age {}中有几个this. 该对象就有几个成员 注意:如果下面有this就是在定义对象 如果没有this,就是在定义函数 */ function People(name, age) { this.name = name; //this表示对象的调用者 this.age = age; this.chi = function () { console.log(this.name, "在吃东西") } } p1 = new People("hahaha", 18); console.log(p1) // People { name: 'hahaha', age: 18, chi: [Function (anonymous)] } p1.chi(); // hahaha 在吃东西 p2 = new People("wawawa", 20); console.log(p2) // People { name: 'wawawa', age: 20, chi: [Function (anonymous)] } p2.chi(); // wawawa 在吃东西
4.2、json序列化和反序列化
// json是一种数据格式, 语法一般是{}或者[]包含起来 // 内部成员以英文逗号隔开,最后一个成员不能使用逗号! // 可以是键值对,也可以是列表成员 // json中的成员如果是键值对,则键名必须是字符串.而json中的字符串必须使用双引号圈起来 // json数据也可以保存到文件中,一般以".json"结尾. { "name": "xiaoming", "age":12 } [1,2,3,4] { "name": "xiaoming", "age":22, "sex": true, "son": { "name":"xiaohuihui", "age": 2 }, "lve": ["篮球","唱","跳"] }
JavaScript中也支持序列化和反序列化的方法:
// js对象,因为这种声明的对象格式很像json,所以也叫json对象 // 创建一个对象 var data = { name: "xiaoming", age: 22, }; // 把对象转换成json字符串 序列化:将js中的对象转换成json格式的字符串 var json_str = JSON.stringify(data); console.log(json_str); // {"name":"xiaoming","age":22} // 把json字符串转换成json对象 反序列化:将json格式的字符串转换成js中的对象 //var str = `{"name":"xiaoming","age":22}`; var obj = JSON.parse(json_str); console.log(obj); // { name: 'xiaoming', age: 22 } /* 序列化:可以将对象转换成JSON字符串的形式(可以将对象进行持久化存储) 反序列化:可以将JSON格式的字符串转换成对象 * */
4.3、
// 创建Date对象 // 方法1:不指定参数 var d1 = new Date(); //获取当前时间 console.log(d1); // 2025-07-03T02:05:38.269Z console.log(d1.toLocaleString()); // 2025/7/3 10:05:38 把时间类型转换为字符串 // 方法2:参数为日期字符串 var d2=new Date("2004/3/20 11:12"); console.log(d2.toLocaleString( )); // 2004/3/20 11:12:00 var d3=new Date("04/03/20 11:12"); console.log(d3.toLocaleString( )); // 2020/4/3 11:12:00
// 获取时间信息 // 获取日期和时间 var date = new Date(); // getDate() 获取日 console.log(date.getDate()); // 3 因为是7月3日 // getDay () 获取星期 console.log(date.getDay()) // 4 星期四 // getMonth () 获取月(0-11) console.log(date.getMonth()) // 6 实际是7月份 // getFullYear () 获取完整年份 console.log(date.getFullYear()) // 2025 实际2025年 // getHours () 获取小时 console.log(date.getHours()) // 10 实际上午10点 // getMinutes () 获取分钟 console.log(date.getMinutes()) // 17 实际上午10点17分 // getSeconds () 获取秒 console.log(date.getSeconds()) // 18 实际上午10点17分18秒 // getMilliseconds () 获取毫秒 console.log(date.getMilliseconds()) // 944 // getTime() //时间戳, console.log(date.getTime()) // 1751509038944 注意:js时间戳是13位整数,python是10位整数
// Math对象的内置方法 // abs(x) 返回数值的绝对值 var num = -10; console.log(Math.abs(num)); // 10 // ceil(x) 向上取整 var num = 10.3; console.log(Math.ceil(num)); // 11 // floor(x) 向下取整 var num = 10.3; console.log(Math.floor(num)); // 10 // max(x,y,z,...,n) console.log(Math.max(3, 56, 3)); // 56 // min(x,y,z,...,n) // random() 生成0-1随机数 console.log(Math.random()); // 生成0-10之间的数值 console.log(Math.random() * 10); // round(x) 四舍五入 // 生成0-10之间的整数 console.log(Math.round(Math.random() * 10));
// 函数的定义方式1 function 函数名 (参数){ 函数体; return 返回值; } /* 功能说明: 可以使用变量、常量或表达式作为函数调用的参数,参数必须放在()中 函数由关键字function定义 函数名的定义规则与标识符一致,大小写是敏感的 返回值必须使用return 在函数的定义当中,如果用到的this,就表明当前的函数可以创建实例对象 如果没有this就是一个单纯的函数的定义 */ // 函数的定义方式2 // 用 Function 类直接创建函数的语法如下: var 函数名 = new Function("参数1","参数n","function_body"); // 虽然由于字符串的关系,第二种形式写起来有些困难,但有助于理解函数只不过是一种引用类型
//f(); --->OK function f() { console.log("hello") // hello } f() //----->OK
检查装载阶段:会先检测代码的语法错误,进行变量、函数的声明
// 位置参数 function add(a,b){ console.log(a); console.log(b); } add(1,2) add(1,2,3) // 错误 add(1) // 错误 // 默认参数 function stu_info(name,gender="male"){ console.log("姓名:"+name+" 性别:"+gender) } stu_info("bobo") // 姓名:bobo 性别:male stu_info("hahaha","man") // 姓名:hahaha 性别:man
// 最多只能返回一个参数 function f1(x,y,z){ return x,y,z } var ret = f1(1,3,6) console.log(ret) // 6 // 如果想要范围多个参数,就需要把参数放到数组中 function f2(x,y,z){ return [x,y,z] } var ret2 = f2(1,3,6) console.log(ret2) // [ 1, 3, 6 ]
// 如果在函数中没有写return 默认返回一个undefined function f3(x,y,z){ console.log("666") // 666 } var ret3 = f3(1,3,6) console.log(ret3) // undefined
// 局部变量,是在函数内部声明,它的生命周期在当前函数被调用的时候, 当函数调用完毕以后,则内存中自动销毁当前变量 // 全局变量,是在函数外部声明,它的生命周期在当前文件中被声明以后就保存在内存中,直到当前文件执行完毕以后,才会被内存销毁掉 //千万不要再函数内部存在和全局变量同名的变量 var num = 10; // 在函数外部声明的变量, 全局变量 function func(){ num = 20; // 函数内部直接使用变量,则默认调用了全局的变量, } func(); console.log("全局num:",num); // 全局num: 20 var num1 = 10; function func1(){ var num1 = 20; // 局部变量num1,被重新定义,并且随着函数销毁了 } func1(); console.log("全局num:",num1); // 全局num: 10 var num2 = 10; function func2(){ var num2 = 20; console.log("局部num:",num2); // 局部num: 20 } func2(); console.log("全局num:",num2); // 全局num: 10
匿名函数,即没有函数名的函数,但是需要变量去指向该函数,方便调用。在实际开发中使用的频率非常高!也是学好JS的重点。
// 匿名函数赋值变量 var foo = function () { console.log("这是一个匿名函数!") }; foo() //调用匿名函数 // 匿名函数的自执行 (function (x,y) { console.log(x+y); // 5 })(2,3) function f1(){ console.log("我是外部函数f1"); // 我是外部函数f1 return function (){ console.log("我是匿名函数") // 我是匿名函数 } } var ret = f1() // 返回的是匿名函数,等于ret指向匿名函数 ret()



浙公网安备 33010602011771号