js笔记
js:js的运行机制是什么,为什么是单线程的?
js是单线程事件队列
这是因为在js的设计之初就是作为浏览器的脚本语言,主要作用就是与用户做交互,如果设计为多线程的话,那就会带来很多复杂的同步问题了,势必会影响浏览器的交互效果,不妨假设一下若js有两个线程,a线程需要将页面信息删除,b线程需要将页面信息修改显示,那就会让前端处理逻辑变得复杂了,也就会影响与用户的交互体验了。
js注入:
一、xss
xss是跨站脚本攻击(cross-site scripting)的简称,防御xss最佳的做法就是对数据进行严格的输出编码,使得攻击者提供的数据不再被浏览器认为是脚本而被误执行。
二、警惕iframe带来的风险
有些时候我们的前端页面需要用到第三方提供的页面组件,通常会以iframe的方式引入。典型的例子是使用iframe在页面上添加第三方提供的广告、天气预报、社交分享插件等等。
还好在html5中,iframe有了一个叫做sandbox的安全属性,通过它可以对iframe的行为进行各种限制,充分实现“最小权限“原则。使用sandbox的最简单的方式就是只在iframe元素中添加上这个关键词就好,就像下面这样:<iframe sandbox src="..."> ... </iframe>
sandbox还忠实的实现了“secure by default”原则,也就是说,如果你只是添加上这个属性而保持属性值为空,那么浏览器将会对iframe实施史上最严厉的调控限制,基本上来讲就是除了允许显示静态资源以外,其他什么都做不了。比如不准提交表单、不准弹窗、不准执行脚本等等,连origin都会被强制重新分配一个唯一的值,换句话讲就是iframe中的页面访问它自己的服务器都会被算作跨域请求。
另外,sandbox也提供了丰富的配置参数,我们可以进行较为细粒度的控制。一些典型的参数如下:
allow-forms:允许iframe中提交form表单
allow-popups:允许iframe中弹出新的窗口或者标签页(例如,window.open(),showmodaldialog(),target=”_blank”等等)
allow-scripts:允许iframe中执行javascript
allow-same-origin:允许iframe中的网页开启同源策略
ajax请求:
201-206:都表示服务器成功处理了请求的状态代码,说明网页可以正常访问。
200(成功) 服务器已成功处理了请求。通常,这表示服务器提供了请求的网页。
201(已创建) 请求成功且服务器已创建了新的资源。
202(已接受) 服务器已接受了请求,但尚未对其进行处理。
203(非授权信息) 服务器已成功处理了请求,但返回了可能来自另一来源的信息。
204(无内容) 服务器成功处理了请求,但未返回任何内容。
205(重置内容) 服务器成功处理了请求,但未返回任何内容。与 204 响应不同,此响应要求请求者重置文档视图(例如清除表单内容以输入新内容)。
206(部分内容) 服务器成功处理了部分 get 请求。
300-3007表示的意思是:要完成请求,您需要进一步进行操作。通常,这些状态代码是永远重定向的。
300(多种选择) 服务器根据请求可执行多种操作。服务器可根据请求者 来选择一项操作,或提供操作列表供其选择。
301(永久移动) 请求的网页已被永久移动到新位置。服务器返回此响应时,会自动将请求者转到新位置。您应使用此代码通知搜索引擎蜘蛛网页或网站已被永久移动到新位置。
302(临时移动) 服务器目前正从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。会自动将请求者转到不同的位置。但由于搜索引擎会继续抓取原有位置并将其编入索引,因此您不应使用此代码来告诉搜索引擎页面或网站已被移动。
303(查看其他位置) 当请求者应对不同的位置进行单独的 get 请求以检索响应时,服务器会返回此代码。对于除 head 请求之外的所有请求,服务器会自动转到其他位置。
304(未修改) 自从上次请求后,请求的网页未被修改过。服务器返回此响应时,不会返回网页内容。
4xx:http状态码表示请求可能出错,会妨碍服务器的处理。
400(错误请求) 服务器不理解请求的语法。
401(身份验证错误) 此页要求授权。您可能不希望将此网页纳入索引。
403(禁止) 服务器拒绝请求。
404(未找到) 服务器找不到请求的网页。例如,对于服务器上不存在的网页经常会返回此代码。
例如:http://www.0631abc.com/20100aaaa,就会进入404错误页面
405(方法禁用) 禁用请求中指定的方法。
500至505表示的意思是:服务器在尝试处理请求时发生内部错误。这些错误可能是服务器本身的错误,而不是请求出错。
.bfc概念:块级格式化上下文,是一个独立的渲染区域,让处于 bfc 内部的元素与外部的元素相互隔离,使内外元素的定位不会相互影响。
我们先了解一个名词:bfc(block formatting context),中文为“块级格式化上下文”。
先记住一个原则: 如果一个元素具有bfc,那么内部元素再怎么翻江倒海,翻云覆雨,都不会影响外面的元素。所以,bfc元素是不可能发生margin重叠的,因为margin重叠会影响外面的元素的;bfc元素也可以用来清除浮动带来的影响,因为如果不清除,子元素浮动则会造成父元素高度塌陷,必然会影响后面元素的布局和定位,这显然有违bfc元素的子元素不会影响外部元素的设定。
以下情况会触发bfc:
•<html>根元素
•float的值不为none
•overflow的值为auto,scroll,hidden
•display的值为table-cell,table-caption和inline--block中的任何一个
•position的值不为relative和static
position: absolute/fixed
display: inline-block / table
float 元素
ovevflow !== visible
显然我们在设置overflow值为hidden时使container元素具有bfc,那么子元素child浮动便不会带来父元素的高度坍塌影响。
24、css3动画实现的原理是什么?
css3动画是补间动画,原理是,将一套css样式逐渐变化成为另外一套样式,以百分比的来规定改变发生的时间。
mvc:核心是三层模型model-view-control。
视图层(view)
控制层(controller)
模型层(model)
js学习:
parsefloat(): 函数可解析一个字符串,并返回一个浮点数。该函数指定字符串中的首个字符是否是数字。如果是,则对字符串进行解析,直到到达数字的末端为止,然后以数字返回该数字,而不是作为字符串。
parseint(): 函数可解析一个字符串,并返回一个整数。
number(): 函数把对象的值转换为数字。
isnan(): 函数用于检查其参数是否是非数字值。 是数字是返回 ”false“ 不是数字是返回 ”true“
concat():方法用于连接两个或多个数组。该方法不会改变现有的数组,而仅仅会返回被连接数组的一个副本。
splice(index,howmany,item1,.....,itemx): 方法向/从数组中添加/删除项目,然后返回被删除的项目。 注释:该方法会改变原始数组。
index 必需。整数,规定添加/删除项目的位置,使用负数可从数组结尾处规定位置。
howmany 必需。要删除的项目数量。如果设置为 0,则不会删除项目。
item1, ..., itemx 可选。向数组添加的新项目。
var arr = ['microsoft', 'apple', 'yahoo', 'aol', 'excite', 'oracle'];
// 从索引2开始删除3个元素,然后再添加两个元素:
arr.splice(2, 3, 'google', 'facebook'); // 返回删除的元素 ['yahoo', 'aol', 'excite']
pop(): 方法用于删除并返回数组的最后一个元素。(会改变原数组)pop() 方法将删除 arrayobject 的最后一个元素,把数组长度减 1,并且返回它删除的元素的值。如果数组已经为空,则 pop() 不改变数组,并返回 undefined 值。
push():数组最后追加一个元素
shift(): 方法用于把数组的第一个元素从其中删除,并返回第一个元素的值。(会改变原数组)
unshift():数组首位追加一个元素
var max1 = math.max.apply(null, arr):求数组中的最大值
var max1 = math.min.apply(null, arr):求数组中的最小值
math.max(): 函数返回一组数中的最大值。返回给定的一组数字中的最大值。如果给定的参数中至少有一个参数无法被转换成数字,则会返回 nan。
math.min(): 函数返回一组数中的最大值。返回给定的一组数字中的最小值。如果给定的参数中至少有一个参数无法被转换成数字,则会返回 nan。
sort(): 方法用于对数组的元素进行排序。
var arr =[-1,-2,1,10,4,5,8] ;
var max2 = arr.sort(function(a, b){
return a - b;
});
string.split()方法:返回是一个数组------不管按照什么拆,拆出来是一个数组(字符串转换为数组)
join(): 方法用于把数组中的所有元素放入一个字符串。(数组转换为字符串)
array.isarray() :用于确定传递的值是否是一个 array。
slice() :(数组的截取)方法可从已有的数组中返回选定的元素。 arrayobject.slice(start,end)
slice()就是对应string的substring()版本,它截取array的部分元素,然后返回一个新的array
注意到slice()的起止参数包括开始索引,不包括结束索引
如果不给slice()传递任何参数,它就会从头到尾截取所有元素。利用这一点,我们可以很容易地复制一个array:
reverse()把整个array的元素给掉个个,也就是反转:
//字符串的操作:
.touppercase(): 方法用于把字符串转换为大写。
.tolowercase(): 方法用于把字符串转换为小写。
indexof(): 方法可返回某个指定的字符串值在字符串中首次出现的位置。
注释:indexof() 方法对大小写敏感!
注释:如果要检索的字符串值没有出现,则该方法返回 -1。
与string类似,array也可以通过indexof()来搜索一个指定的元素的位置:
substring() :方法用于提取字符串中介于两个指定下标之间的字符。
substr(1,4): 方法可在字符串中抽取从 start 下标开始的指定数目的字符。 1开始,截取4位
math.abs(x):abs() 方法可返回数的绝对值。
map(): 方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。
map() 方法按照原始数组元素顺序依次处理元素。
注意: map() 不会对空数组进行检测。
注意: map() 不会改变原始数组。
math.round():四舍五入取其整。
math.ceil():向上取整。
math.floor():向下取整。
math.pow(x,y): 方法可返回 x 的 y 次幂的值。
trim() 方法删除字符串两端的空白符:var str = " hello world! "; alert(str.trim());
var: 如果使用关键字 var 声明一个变量,那么这个变量就属于当前的函数作用域,如果声明是发生在任何函数外的顶层声明,那么这个变量就属于全局作用域。
let:
1、let 声明的变量具有块作用域的特征。
2、在同一个块级作用域,不能重复声明变量。
function foo(){
let a = 1;
let a = 2;//uncaught syntaxerror: identifier 'a' has already been declared
}
3、let 声明的变量不存在变量提升,换一种说法,就是 let 声明存在暂时性死区(tdz)。
for (var i = 0; i < 5; i++) {
settimeout(function(){
console.log(i);
},100)
};
会打印4个4 :settimeout是异步执行的,100毫秒后向任务队列里添加一个任务,只有主线上的全部执行完才会执行任务队列里的任务,所以当主线程for循环执行完之后 i 的值为5,这个时候再去任务队列中执行任务,i全部为5;每次for循环的时候settimeout都会执行,但是里面的function则不会执行被放入任务队列,因此放了5次;for循环的5次执行完之后不到1000毫秒;1000毫秒后全部执行任务队列中的函数,所以就是输出五个5啦
for (let i = 0; i < 5; i++) {
settimeout(function(){
console.log(i);
},100)
};
会打印0,1,2,3,4:假如把var换成let,那么输出结果为0,1,2,3,4;因为let i 的是区块变量,每个i只能存活到大括号结束,并不会把后面的for循环的 i 值赋给前面的settimeout中的i;而var i 则是局部变量,这个 i 的生命周期不受for循环的大括号限制;
let 和 var 的区别:
let 拥有自己的块级作用域,不会提升变量。
var 定义的变量,作用域是整个封闭函数,是全域的 。
let 定义的变量,作用域是在块级或是子块中。
var: 变量可以多次声明
let : 变量只能声明一次
const: 声明方式,除了具有 let 的上述特点外,其还具备一个特点,即 const 定义的变量,一旦定义后,就不能修改,即 const 声明的为常量。
const obj = {a:1,b:2};
console.log(obj.a);//1
obj.a = 3;
console.log(obj.a);//3
所以准确的说,是 const 声明创建一个值的只读引用。但这并不意味着它所持有的值是不可变的,只是变量标识符不能重新分配。
es6模板字符串:
var name = '小明';
var age = 20;
var message = `你好, ${name}, 你今年${age}岁了!`;
alert(message);
初始化map需要一个二维数组,或者直接初始化一个空map。map具有以下方法:
var m = new map(); // 空map
m.set('adam', 67); // 添加新的key-value
m.set('bob', 59);
m.has('adam'); // 是否存在key 'adam': true
m.get('adam'); // 67
m.delete('adam'); // 删除key 'adam'
m.get('adam'); // undefined
要获取字符串某个指定位置的字符,使用类似array的下标操作,索引号从0开始:var s = 'hello, world!'; s[0]; // 'h'
// 利用for循环计算1 * 2 * 3 * ... * 10的结果:
var sum = 1;
for(var i = 1;i<=10;i++){
sum = i*sum;
}
console.log(sum);
//数组排序
var arr = [7,2,3,6,8,1,11,22]
var arr2 = arr.sort(function(v1,v2){
return v1-v2
}).reverse()
//要取一个小数的小数点后四位,有什么方法?
使用 math.floor
var num = 3.141592653
var result = math.floor (num * 10000) / 10000
// 利用递归实现数组的扁平化
let ary = [1, [2, [3, [4, 5]]], 6];
let result = [];
let fn = function(ary){
for(let i = 0 ; i < ary.length; i++){
let item = ary[i];
if(array.isarray(ary[i])){
fn(item);
} else {
result.push(item)
}
}
}
fn(ary);
console.log(result);
//构造函数
function person(name, gender, hobby) {
this.name = name;
this.gender = gender;
this.hobby = hobby;
this.age = 6;
}
//原型对象上创建say函数
person.prototype.say = function(){
console.log("hello!");
}
//通过new关键字创建对象
var p1 = new person('zs', '男', 'basketball');
var p2 = new person('ls', '女', 'dancing');
var p3 = new person('ww', '女', 'singing');
var p4 = new person('zl', '男', 'football');
p1.say()//"hello"
//p1会继承构造函数的原型对象以及对象的say函数
function person(name, age) {
this.name = name;
this.age = age;
}
person.prototype.say = function() {
console.log('hello');
};
var p1 = new person('tom', 18);
var p2 = new person('jack', 34);
console.log(p1.say === p2.say);
var student = {
name:'student',
age:3,
run: function(){
console.log(this.name + 'is running');
}
};
a = new object(student);
b = object.create(student);
// 重点来了
a.proto === student.prototype; //true
b.proto === student.prototype; //true
b.__proto__ === student; //true
a.__proto__ === student; //false
a.__proto__ === student.__proto__; //true
//对象
由于javascript的对象是动态类型,你可以*地给一个对象添加或删除属性:
var xiaoming = {
name: '小明'
};
xiaoming.age; // undefined
xiaoming.age = 18; // 新增一个age属性
xiaoming.age; // 18
delete xiaoming.age; // 删除age属性
xiaoming.age; // undefined
delete xiaoming['name']; // 删除name属性
xiaoming.name; // undefined
delete xiaoming.school; // 删除一个不存在的school属性也不会报错
如果我们要检测xiaoming是否拥有某一属性,可以用in操作符:
var xiaoming = {
name: '小明',
birth: 1990,
school: 'no.1 middle school',
height: 1.70,
weight: 65,
score: null
};
'name' in xiaoming; // true
'grade' in xiaoming; // false
不过要小心,如果in判断一个属性存在,这个属性不一定是xiaoming的,它可能是xiaoming继承得到的:
'tostring' in xiaoming; // true
因为tostring定义在object对象中,而所有对象最终都会在原型链上指向object,所以xiaoming也拥有tostring属性。
要判断一个属性是否是xiaoming自身拥有的,而不是继承得到的,可以用hasownproperty()方法:
var xiaoming = {
name: '小明'
};
xiaoming.hasownproperty('name'); // true
xiaoming.hasownproperty('tostring'); // false
对象的遍历:
var list={
name:'xiaoming',
age:18,
school:'沈阳小学',
'other':null
};
for(var k in list){ console.log(k+':'+list[k]); }
变量提升:
javascript的函数定义有个特点,它会先扫描整个函数体的语句,把所有申明的变量“提升”到函数顶部
变量虽然提升了,但是不会提升变量的赋值。
名字空间:
全局变量会绑定到window上,不同的javascript文件如果使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突,并且很难被发现。
减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中。例如:
// 唯一的全局变量myapp:
var myapp = {};
// 其他变量:
myapp.name = 'myapp';
myapp.version = 1.0;
// 其他函数:
myapp.foo = function () {
return 'foo';
};
把自己的代码全部放入唯一的名字空间myapp中,会大大减少全局变量冲突的可能。
许多著名的javascript库都是这么干的:jquery,yui,underscore等等。
解构赋值:
从es6开始,javascript引入了解构赋值,可以同时对一组变量进行赋值。
eg:
var [x, y, z] = ['hello', 'javascript', 'es6'];
let [x, [y, z]] = ['hello', ['javascript', 'es6']];
let [, , z] = ['hello', 'javascript', 'es6'];
var person = {
name: '小明',
age: 20,
gender: 'male',
passport: 'g-12345678',
school: 'no.4 middle school'
};
var {name, age, passport} = person;
使用场景:
解构赋值在很多时候可以大大简化代码。例如,交换两个变量x和y的值,可以这么写,不再需要临时变量:
var x=1, y=2;
[x, y] = [y, x]
快速获取当前页面的域名和路径:
var {hostname:domain, pathname:path} = location;
http://www.joymood.cn:8080/test.php?user=admin&pwd=admin#login
location.href:得到整个如上的完整url
location.protocol:得到传输协议http:
location.host:得到主机名连同端口http://www.joymood.cn:8080/
location.hostname:得到主机名http://www.joymood.cn/
location.pathname:得到主机后部分不包括问号?后部分的/test.php
location.search:得到url中问号?之后井号#之前的部分?user=admin&pwd=admin
location.hash:得到#之前的部分#login
其实以我一年的编程经验来看,所谓函数引用、对象引用、函数名其实都是内存中的一个地址,这个地址指向了某个函数或对象或方法,谁拿到了这个地址,谁就拥有了调用函数、调用方法的权利,所以所谓传入函数作为参数,其实就是把这个地址传给了另外一个函数,让另外一个函数拥有操作这个函数的权利,我觉得就是这样,大家觉得呢?
css的作用域是?执行顺序?优先级?:
!important > id > class > tag
important 比 内联优先级高
//粘性定位(目前支持性不是很好)
position: -webkit-sticky;
position: sticky;
小程序有哪几种跳转页面的方式:
wx.switchtab:跳转 app.json 的页面(路由方式)
wx.navigateto:跳转到指定页面,保存当前页面。
wx.redirectto:跳转到指定页面,关闭当前页面。
wx.navigateback:返回之前页面,跳转之前的页面
那 es6 还新增了哪些东西?说你熟悉的举例:
新增了 let、const
新增了解构赋值;还有字符串、数组、对象的诸多特性和方法
新增了模块化功能写法 export 和 import
新增了 class 类继承的语法糖
新增了 promise 处理异步
新增了箭头函数 =>
window.onload 和 $(document).ready 有什么区别?:
执行时间不同:
window.onload 必须等到页面内包括图片的所有元素加载完毕后再去执行。
$(document).ready() 时 dom 结构回执完毕后就执行,不必等到加载完毕。
window.onload 不可同时编写多个,如果有多个 window.onload方法,只会执行一个。
$(document).ready() 可以同时编写多个,并且可以得到执行。
跨域如何实现?你一般什么方式跨域:
jsonp、cors、postmessage都可以实现跨域。
但 jsonp 只能实现 get 请求
什么是闭包?:使用场景:1、闭包可以将一些不希望暴露在全局的变量封装成“私有变量”。
在一些语言中,在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,则可能产生闭包。闭包可以用来在一个函数与一组“私有”变量之间创建关联关系。在给定函数被多次调用的过程中,这些私有变量能够保持其持久性。(其变量是私有的,不会外泄,且拥有持久性)
1.在一个外函数中定义了一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用。这样就构成了一个闭包。
2.一般情况下,在我们认知当中,如果一个函数结束,函数的内部所有东西都会释放掉,还给内存,局部变量都会消失。但是闭包是一种特殊情况,如果外函数在结束的时候发现有自己的临时变量将来会在内部函数中用到,就把这个临时变量绑定给了内部函数,然后自己再结束。
①必须有一个内嵌函数(函数里定义的函数)——这对应函数之间的嵌套
②内嵌函数必须引用一个定义在闭合范围内(外部函数里)的变量——内部函数引用外部变量
③外部函数必须返回内嵌函数——必须返回那个内部函数
<script type="text/javascript">
function name(n){
var pai = math.pi;
var all = (function(i){
return pai*i*i
})(n)
return all
}
console.log(name(2));
</script>
<script type="text/javascript">
var scope = "global scope";
function checkscope() {
var scope = "local scope";
function f() {
return scope;
}
return f();
}
console.log(checkscope());//local scope
</script>
什么是递归?:
自己调用自己
function sumrange(num) {
if (num === 1) return 1;
return num + sumrange(num - 1)
}
sumrange(3) // 6
1·函数 sumrange(3) 执行,sumrange(3) 压栈,遇到 return 关键字,但这里还马上不能出栈,因为调用了 sumrange(num - 1) 即 sumrange(2)
2·所以,sumrange(2) 压栈,以此类推,sumrange(1) 压栈,
3·最后,sumrange(1) 出栈返回 1,sumrange(2) 出栈,返回 2,sumrange(3) 出栈
所以 3 + 2 + 1 结果为 6
利用递归实现数组扁平化:
<script type="text/javascript">
var arr = [1,2,[3,4,[5,6]]];
var arr2=[];
function digui(arr){
for(let i = 0;i<arr.length;i++){
var itme = arr[i];
if(array.isarray(arr[i])){
digui(itme)
}else{
arr2.push(itme);
}
}
return arr2
}
console.log(digui(arr));
</script>
箭头函数=> es6标准新增了一种新的函数:arrow function(箭头函数)。
箭头函数语法:(parameters) => { statements } “x => x * x” =>前为携带参数,后面为表达式
如果没有参数,那么可以进一步简化:() => { statements }
如果只有一个参数,可以省略括号:parameters => { statements }
没有局部this的绑定 : 和一般的函数不同,箭头函数不会绑定this。 或者说箭头函数不会改变this本来的绑定。
1:箭头函数写代码拥有更加简洁的语法;
2:不会绑定this。
<script type="text/javascript">
function counter() {
this.num = 0;
this.timer = setinterval(()=> {
this.num++;//其中this指向counter
console.log(this.num);
}, 1000);
}
var b = new counter();
</script>
<script type="text/javascript">
function counter() {
this.num = 0;
this.timer = setinterval(function add() {
this.num++;//其中this指向windows对象
console.log(this.num);
}, 1000);
}
</script>
date:注意,当前时间是浏览器从本机操作系统获取的时间,所以不一定准确,因为用户可以把当前时间设定为任何值。
var now = new date();
now; // wed jun 24 2015 19:49:22 gmt+0800 (cst)
now.getfullyear(); // 2015, 年份
now.getmonth(); // 5, 月份,注意月份范围是0~11,5表示六月
now.getdate(); // 24, 表示24号
now.getday(); // 3, 表示星期三
now.gethours(); // 19, 24小时制
now.getminutes(); // 49, 分钟
now.getseconds(); // 22, 秒
now.getmilliseconds(); // 875, 毫秒数
now.gettime(); // 1435146562875, 以number形式表示的时间戳
正则:
^:表示行的开头
$:表示行的结束
\w:匹配字母、数字、下划线。等价于'[a-za-z0-9_]'。
\w:匹配非字母、数字、下划线。等价于 '[^a-za-z0-9_]'。
\d:匹配一个数字字符。等价于 [0-9]。
\d:匹配一个非数字字符。等价于 [^0-9]。
regexp对象的test():方法用于测试给定的字符串是否符合条件。
var re = /^\d{3}\-\d{3,8}$/;
re.test('010-12345'); // true
匹配手机号:
function isponeavailable($poneinput) {
var myreg=/^[1][3,4,5,7,8][0-9]{9}$/;
if (!myreg.test($poneinput.val())) {
return false;
} else {
return true;
}
}
json:
json.stringify(): 方法用于将 javascript 值转换为 json 字符串。
var str = {"name":"菜鸟教程", "site":"http://www.runoob.com"}
str_pretty1 = json.stringify(str)
json.parse(): 方法用于将一个 json 字符串转换为对象。将json数据解析为js原生值
上一篇: 第一章、Python环境搭建