day22JS-对象静态方法
1. 创建对象的三种方法
- 用{}字面量创建:var 对象名 = {};
- 用new的方式创建:var 对象名 = new Object();
- 以什么对象为原型创建一个新对象:Object.create(对象名);
注意!!!
- Object.create(对象名)方法的参数只能是对象或null。
- 如果使用Object.create(null)作为原型创建对象,无属性,没有原型链,所以不能使用对象的任何方法。
- Object.create(对象名)方法可以设置原型链。
案例1:
// 方法一:var o = {};// 方法二:var o = new Object();// 方法三: Object.create() 以什么对象为原型创建一个新对象var o = { a: 1, b: 2 };var o1 = Object.create(o);o1.c = 3;o1.d = 4;console.log(o1);
案例2:
var o = Object.create(null);console.log(o);//无属性console.log(o + "1");//报错
案例3:
var o1 = { a: 1 };var o2 = Object.create(o1);o2.b = 2;o2.z = 10;var o3 = Object.create(o2);o3.c = 3;o3.z = 100;var o4 = Object.create(o3);o4.d = 4;console.log(o4);
2. 对象属性
对象属性分为对象属性和原型属性。
当获取对象属性时,首先查找对象的对象属性,如果没有则向下继续查找原型属性,找到最近的原型属性返回。
当给对象设置属性时,不能设置原型属性,只能设置对象的对象属性。原型对象的引用地址和对应的对象是一致。
案例1:
var o1 = { a: 1 };var o2 = Object.create(o1);o2.b = 2;o2.z = 10;var o3 = Object.create(o2);o3.c = 3;o3.z = 100;var o4 = Object.create(o3);o4.d = 4;console.log(o4);// 对象属性中分为对象属性和原型属性// 当获取对象属性时,首先查找对象的对象属性,如果没有则向下继续查找原型属性,找到最近的原型属性返回console.log(o4.d);console.log(o4.b);console.log(o4.z);// 当给对象设置属性时,不能设置原型属性,只能设置对象的对象属性o4.z = 1000;console.log(o4);// 原型对象的引用地址和对应的对象是一致o3.z = 1;console.log(o4);
3. 获取原型链的方法
获取原型链的方法:
- 对象名.__proto__ :禁止使用__proto__
- Object.getPrototypeOf(对象名)
设置原型链的方法:
- 对象名.__proto__ = 对象名;:禁止使用__proto__
- Object.setPrototypeOf(目标对象, 作为原型链的对象);
Object.create(对象名);
案例1:
var o1 = { a: 1 };var o2 = Object.create(o1);o2.b = 2;o2.z = 10;var o3 = Object.create(o2);o3.c = 3;o3.z = 100;var o4 = Object.create(o3);o4.d = 4;// 原型对象的引用地址和对应的对象是一致// 获取原型链 禁止使用__proto__console.log(o4.__proto__ === o3);// 获取原型链console.log(Object.getPrototypeOf(o4));
案例2:
var o1 = { a: 1 };var o2 = { b: 2 };// 禁止使用// o2.__proto__ = o1;// 设置原型链Object.setPrototypeOf(o2, o1);console.log(o2);
案例3: 使用两种方法让第一个对象{a:1}作为第二个对象{b:2}的原型,第二个对象{b:2}作为第三个对象 {c:3}的原型,形成原型链。
// 方法一:var o1 = { a: 1 };var o2 = Object.create(o1);o2.b = 2;var o3 = Object.create(o2);o3.c = 3;console.log(o3);
-----------------------------------------------// 方法二:var o1 = { a: 1 };var o2 = { b: 2 };var o3 = { c: 3 };Object.setPrototypeOf(o2, o1);Object.setPrototypeOf(o3, o2);console.log(o3);
4. 迭代器和判断属性是否是当前对象的对象属性
遍历方法:
- for in 遍历:遍历对象时,可以遍历对象中的对象属性和原型链属性,不能遍历不可枚举属性。
- for of 遍历 :只能遍历迭代器。不能遍历对象。对象要使用for of必须使用迭代器。
判断这个属性是不是当前对象的对象属性的方法:
- Object.hasOwn(对象名,属性名):判断这个属性是不是当前对象的对象属性。
Object.hasOwn(数组名,所以下标):判断数组的下标是不是空元素。
2. 对象.hasOwnProperty(属性名) :判断这个属性是不是当前对象的对象属性。
迭代器:
- Object.entries(对象名):将对象转换为数组迭代器(二维数组)。例如:[["a",1],["b",2]]。
- Object.fromEntries(数组迭代器) :将数组迭代器(二维数组)转换为对象。
案例1:
var o1 = { a: 1 };var o2 = { b: 2 };var o3 = { c: 3 };Object.setPrototypeOf(o2, o1);Object.setPrototypeOf(o3, o2);// 遍历对象时,可以遍历对象中的对象属性和原型链属性,不能遍历不可枚举属性// Object.hasOwn(对象,属性)判断这个属性是不是当前对象的对象属性// 对象.hasOwnProperty(属性) 判断这个属性是不是当前对象的对象属性for (var key in o3) {console.log(key);console.log(key, Object.hasOwn(o3, key));console.log(o3.hasOwnProperty(key));}
案例2:
var arr = ["a", "b", , "c"];// Object.hasOwn(数组,下标)判断数组的下标是不是空元素console.log(Object.hasOwn(arr, 2));console.log(arr.hasOwnProperty(2));
案例3:
var o = { a: 1, b: 2, c: 3 };console.log(Object.entries(o));// [["a",1],["b",2],["c",3]]// 对象不是迭代器,本身不能使用for offor (var [key, value] of Object.entries(o)) {console.log(key, value);}
案例4:
// 将迭代器转换为对象var o = Object.fromEntries([["a", 1], ["b", 2]])console.log(o);
5. 对象浅复制
Object.assign(目标对象,...源对象) :将源对象中所有可枚举的对象属性复制到目标对象的对象属性上,如果源对象中的属性有重复,后面的会覆盖前面的。源对象可以是多个。 这个方法最后返回目标对象。
案例1:
var o = { a: 1, b: 2 };var o1 = Object.create(o);o1.c = 3;o1.d = 4;var o2 = { e: 4, f: 5 };Object.assign(o2, o1);console.log(o2);
案例2:
var o1 = { a: 1 };var o2 = { b: 2, z: 1 };var o3 = { c: 3, z: 2 };var o4 = { d: 4 };Object.assign(o4, o1, o2, o3)console.log(o4);
案例3:
var o1 = { a: 1 };var o2 = { b: 2 };var o3 = Object.assign({}, o1, o2);console.log(o3);// {...}这种写法一般用于创建一个新对象// Object.assign() 用于已有对象复制使用var o4 = { ...o1, ...o2 };console.log(o4);
6. 冻结、密封、禁止扩展对象
- Object.freeze(对象名):冻结对象。对象不能删除属性,不能添加新属性,不能修改属性值。在js中没有enum枚举,所以使用该方法可以属性enum 枚举。
- Object.isFrozen(对象名):当前对象是否被冻结。
- Object.seal(对象名): 密封对象,可以修改属性,但是不能删除和添加新属性。
- Object.isSealed(对象名):判断是否被密封。
- Object.preventExtensions(对象名) : 禁止扩展对象,可以删除属性,可以修改属性,但是不能添加新属性。
- Object.isExtensible(对象名) : 判断是否被禁止扩展对象。false代表禁止扩展, true表示可扩展。
案例1:
// 冻结对象 对象不能删除属性,不能添加新属性,不能修改属性值var o = { a: 1, b: 2 };Object.freeze(o)// 删除对象属性delete o.a;console.log(o);// 修改对象属性o.a = 10;console.log(o);// 添加对象属性o.c = 10;console.log(o);
案例2:
const COLOR = Object.freeze({ RED: "RED", BLUE: "BLUE", YELLOW: "YELLOW", GREEN: "GREEN" })// 当前对象是否被冻结console.log(Object.isFrozen(COLOR));//true
案例3:
var o1 = Object.seal({ a: 1, b: 2 })o1.a = 10;console.log(o1);// 判断是否被密封console.log(Object.isSealed(o1));
案例4:
// 禁止扩展 可以删除属性,可以修改属性,但是不能添加新属性var o1 = Object.preventExtensions({ a: 1, b: 2 })delete o1.a;o1.b = 10;o1.c = 20;console.log(o1);// false代表禁止扩展 true表示可扩展console.log(Object.isExtensible(o1));
7. 添加属性并定义该属性特性的方法
1. Object.defineProperty(对象, "属性名", 属性描述对象) :定义单个属性。该方法不能删除,在严格模式下,删除会报错;不能重新定义;当属性定义为不可枚举时,for in是不能遍历到该属性的,但能拿到该属性的值;当该属性定义为不可修改时,该属性不能修改,如果在严格模式下,删除会报错。
属性描述对象中的属性:
- enumerable:是否可枚举。布尔值。
- configurable: 是否可删除或者重新设置属性描述对象。布尔值。
- writable:是否可写,是否可以修改。布尔值。
- value : 如果value是非函数,则这是一个属性值,如果value是一个函数,则这是一个方法。value:function(){...}或value(){...}都表示value是一个函数。
该方法的使用模板1:
Object.defineProperty(对象名, "属性名", {
enumerable: true,
configurable: true,
writable: true,
value: 属性值,
})
该方法的使用模板2:
Object.defineProperty(对象名, "属性名",{
enumerable: true,
configurable: true,
set(value) {
},
get() {
}
})
特性写法:这个写法默认该属性不可删除,不可修改,不可枚举
Object.defineProperty(对象名, "属性名", {
// 默认如果不写则为false
value: 属性值
})
2. Object.defineProperties(对象,对象多属性描述对象):定义多个属性。
该方法使用模板:
Object.defineProperties(对象名, {
// 属性名:属性描述对象
a: {
enumerable: true,
configurable: true,
writable: false,
value: 1
},
b: {
value: 2
}
})
案例1:
// 属性var o = { a: 1, b: 2 };Object.defineProperty(o, "c", {enumerable: true,configurable: true,writable: true,value: 3,})console.log(o);
案例2:
var o = { a: 1, b: 2 };Object.defineProperty(o, "play", {enumerable: true,configurable: true,writable: true,// value:function(){}value() {console.log("play");}})console.log(o);
案例3:
var o = { a: 1, b: 2 };Object.defineProperty(o, "c", {configurable: false,writable: true,enumerable: true,value: 3})// 不能删除delete o.c;console.log(o);// 不能重新定义Object.defineProperty(o, "c", {//报错configurable: true,writable: true,enumerable: true,value: 3})console.log(o);
案例4:属性c不能被遍历出来。
var o = { a: 1, b: 2 };Object.defineProperty(o, "c", {configurable: true,enumerable: false,//不可枚举writable: true,value: 3})console.log(o);for (var key in o) {console.log(key);}
案例5:
var o = { a: 1, b: 2 };Object.defineProperty(o, "c", {configurable: true,enumerable: true,writable: false,//不可修改,不可写value: 3})o.c = 10;console.log(o.c);//3
案例6:定义多个属性
var o = {};Object.defineProperties(o, {// 属性名:属性描述对象a: {enumerable: true,configurable: true,writable: false,value: 1},b: {value: 2}})console.log(o);
案例7:
var o = {a: 1,"b-1": 2,[Symbol("a")]: 3}Object.defineProperties(o, {c: {value: 4},[Symbol("b")]: {writable: true,value: 5}})console.log(o);
8. 遍历不可枚举属性的方法
for in遍历:不靠谱,只能遍历可枚举,并且只能是字符的串属性名。
- Object.getOwnPropertyNames(对象名) :获取对象的所有可枚举和不可枚举的字符串属性名。
- Object.getOwnPropertySymbols(对象名):获取对象的所有可枚举和不可枚举的Symbol属性名。
- Reflect.ownKeys(对象名): Reflect 针对Object对象的所有方法反射方法集,获取到对象中所有的可枚举和不可枚举的任意类型属性名。
案例1:
var o = {a: 1,"b-1": 2,[Symbol("a")]: 3}Object.defineProperties(o, {c: {value: 4},[Symbol("b")]: {writable: true,value: 5}})console.log(o);// 不靠谱 只能遍历可枚举,并且字符串属性名// for (var key in o) {// console.log(key, o[key]);// }// 获取对象的所有可枚举和不可枚举的字符串属性名var arr = Object.getOwnPropertyNames(o);// 获取对象的所有可枚举和不可枚举的Symbol属性名var arr1 = Object.getOwnPropertySymbols(o);console.log(arr, arr1);
案例2:遍历
var o = {a: 1,"b-1": 2,[Symbol("a")]: 3}Object.defineProperties(o, {c: {value: 4},[Symbol("b")]: {writable: true,value: 5}})console.log(o);// 合并写法一;var arr = Object.getOwnPropertyNames(o).concat(Object.getOwnPropertySymbols(o))for (var i = 0; i < arr.length; i++) {console.log(arr[i], o[arr[i]]);}-----------------------------------------------------------------------------------------var o = {a: 1,"b-1": 2,[Symbol("a")]: 3}Object.defineProperties(o, {c: {value: 4},[Symbol("b")]: {writable: true,value: 5}})console.log(o);// 合并写法二;var arr = [...Object.getOwnPropertyNames(o), ...Object.getOwnPropertySymbols(o)]for (var i = 0; i < arr.length; i++) {console.log(arr[i], o[arr[i]]);}
-----------------------------------------------------------------------------------------var o = {a: 1,"b-1": 2,[Symbol("a")]: 3}Object.defineProperties(o, {c: {value: 4},[Symbol("b")]: {writable: true,value: 5}})console.log(o);// 获取到对象中所有的可枚举和不可枚举的任意类型属性名// Reflect 针对Object对象的所有方法反射方法集var arr = Reflect.ownKeys(o);for (var i = 0; i < arr.length; i++) {console.log(arr[i], o[arr[i]]);}
9. 获取对象属性的描述对象
- Object.getOwnPropertyDescriptor(对象名, "属性名") :获取对象属性的描述对象。
- Object.getOwnPropertyDescriptors(对象名) : 获取对象属性上所有的描述对象。
案例1:
var o = {a: 1,"b-1": 2,[Symbol("a")]: 3}Object.defineProperties(o, {c: {value: 4},[Symbol("b")]: {writable: true,value: 5}})// 获取对象属性的描述对象var desc = Object.getOwnPropertyDescriptor(o, "c");console.log(desc);
案例2:
var o = {a: 1,"b-1": 2,[Symbol("a")]: 3}Object.defineProperties(o, {c: {value: 4},[Symbol("b")]: {writable: true,value: 5}})// 获取对象属性上所有的描述对象var descs = Object.getOwnPropertyDescriptors(o);console.log(descs);
10. 对象群组方法
Object.groupBy( 数组迭代器, function (元素, 索引下标) { }):遍历或分组。返回的是数组。
案例1:
var o = { a: 1, b: 2 };Object.groupBy(Object.entries(o), function (item, index) {console.log(item, index);})
案例2:
var arr = ["a", "b", "c"];Object.groupBy(arr, function (item, index) {console.log(item, index);})
案例3:
var list = [{ a: 1, type: 0, price: 1000 },{ a: 2, type: 1, price: 2000 },{ a: 3, type: 0, price: 2000 },{ a: 4, type: 1, price: 3000 },{ a: 5, type: 1, price: 4000 },{ a: 6, type: 0, price: 5000 },]var list1 = Object.groupBy(list, (item) => item.type);console.log(list1);
11. isNaN()、Number.isNaN()和Object.is()的区别
- isNaN() :该方法会隐式转化,在判断是不是NaN。
- Number.isNaN():该方法直接判断NaN。不会隐式转化。
- Object.is():该方法是判断两个值是否绝对相等(===),不会隐式转化。能判断与NaN是否绝对相等(===)。
案例1:
// isNaN()// Number.isNaN()不会隐式转换// Object.is() 与===相类似 判断是否绝对相等var a = 1;var b = "1";console.log(Object.is(a, b));//false// ===永远不等于NaNconsole.log(Number("a") === NaN);console.log(Object.is("a", NaN));
12. 获取对象key-valus的方法
Object.keys(对象名) :只能获取到对象中所有可枚举的字符串属性名组成数组。
Object.values(对象名) :只能获取到对象中所有可枚举的字符串属性名对应属性值组成数组。
案例:
var obj = { a: 1, b: 2, c: 3 };// 只能获取到对象中所有可枚举的字符串属性名组成数组console.log(Object.keys(obj));// 只能获取到对象中所有可枚举的字符串属性名对应属性值组成数组console.log(Object.values(obj));
13. 对象的getter和setter
getter和setter是访问器属性。
希望可以像属性一样使用=号赋值,并且在赋值后还可以执行多条语句。
案例:本案例中,怎么做到重新执行 obj.init();就能到达重新渲染页面的效果。
var obj = {list: [1, 2, 3, 4],init() {document.body.innerHTML = `<ul>${this.list.reduce((v, t) => v + `<li>${t}</li>`, "")}</ul>`}}obj.init();//怎么做到重新执行 obj.init();就能到达重新渲染页面的效果obj.list = ["a", "b", "c", "d"]obj.init();
解决方法:定义对象时,直接加入set和get方法。
- set 方法:有且仅有一个参数,并且不允许使用return返回任何值;当赋值时,执行set这个方法,参数就是赋值的结果,可以在赋值后执行多条语句;如果只写set 不写get,表示这个属性只能设置值,不能获取该属性,是只写属性。
- get 方法:不能有参数,并且必须return返回一个值;获取对象属性 时,执行get方法,获取最后要有一个获取的结果,所以需要return结果;如果只写get 不写set,表示这个属性只能获取值,不能设置改变值 ,是只读属性;这个get这个属性不能用来存储任何内容,需要借助第三方属性存储。
案例1:
// 定义对象时,直接加入set和getvar obj = {_list: 1,// set 方法 有且仅有一个参数,并且不允许使用return返回任何值// list 这个getset属性不能用来存储任何内容,需要借助第三方属性存储set list(value) {this._list = value;// console.log("set",value);},// get 方法 不能有参数,并且必须return返回一个值get list() {// console.log("get");// return ""return this._list;}}// 和属性的赋值方法完全相同// 当赋值时,执行set list这个方法,参数就是赋值的结果,可以在赋值后执行多条语句// 如果只写set 不写get,表示这个属性只能设置值,不能获取该属性 只写obj.list = "a";// 获取对象属性 执行get list方法,获取最后要有一个获取的结果,所以需要return结果// 如果只写get 不写set,表示这个属性只能获取值,不能设置改变值 只读console.log(obj.list);
案例2:
var obj = {_list: [],// 重新给属性赋值时才会执行setset list(value) {if (!Array.isArray(value)) return;this._list = value;document.body.innerHTML = `<ul>${this.list.reduce((v, t) => v + `<li>${t}</li>`, "")}</ul>`},get list() {return this._list;}}obj.list = [1, 2, 3, 4]document.addEventListener("click", () => {obj.list = ["a", "b", "c", "d", "e"]// obj.list.push(5);obj.list = obj.list.concat(5);})
案例3:div自动移动案例
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><style>div {width: 50px;height: 50px;background-color: red;position: absolute;left: 0;top: 0;}</style>
</head><body><div></div><script type="module">var div = document.querySelector("div");var obj = {_x: 0,_y: 0,set x(value) {this._x = value;div.style.left = value + "px";},get x() {return this._x},set y(value) {this._y = value;div.style.top = value + "px";},get y() {return this._y;}}setInterval(() => {// set getobj.x++;//相当于obj.x=obj.x+1obj.y++;}, 16)</script>
</body></html>
案例4:div自动移动案例的升级版
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><style>div {width: 50px;height: 50px;background-color: red;position: absolute;left: 0;top: 0;}</style>
</head><body><div></div><script type="module">var div = document.querySelector("div");// 如果对象已经存在,如果要给对象中添加setget属性,就必须使用defineProperty或者definePropertiesHTMLElement.prototype.a = 10;console.log(div.a);Object.defineProperties(HTMLElement.prototype, {_x: {writable: true,//可修改,不可枚举,不可删除value: 0},_y: {writable: true,//可修改,不可枚举,不可删除value: 0},x: {set(value) {this._x = value;this.style.left = value + "px"},get() {return this._x;}},y: {set(value) {this._y = value;this.style.top = value + "px"},get() {return this._y;}}})setInterval(() => {div.x++;div.y++;}, 16)</script>
</body></html>
14. 对象深复制
14.1 对象浅复制的弊端
复制对象的目的,修改对象后,不会引起源对象的改变,保留源对象的内容。
- JSON.stringify(对象名) :将对象转换为JSON格式的字符串。格式 '{"属性名":属性值}'。 属性值如果是字符串需要使用""。
- JSON.parse():把JSON格式的字符串转换为对象,返回新对象;可以用于把字符串强转为布尔类型和数值类型。
案例:将下面对象的age属性进行随机修改,修改范围在25<x<35的范围内就停止修改。
var data = {name: "xietian",age: 30,sex: "男"}
解决方法:下面的方法都是浅复制
var data = {name: "xietian",age: 30,sex: "男"}while (1) {// 复制对象方法一:// var newData = {}// for (var key in data) {// newData[key] = data[key]// }// 复制对象方法二:// var newData = Object.assign({}, data);// var newData = { ...data };// 复制对象方法三:var newData = Object.keys(data).reduce((v, k) => v[k] = data[k], {})newData.age = ~~(Math.random() * 100)if (newData.age < data.age + 5 && newData.age > data.age - 5) {break;}}console.log(newData);
浅复制只能复制一层,遇到对象中属性值还是对象的,改变复制新对象的属性,还是会改变源对象的属性值。属性值的引用地址没有变。例如:如下图所示:
var data = {a: 1,b: {c: 2}}
为了解决上述问题,使用要使用深复制。
14.2 对象深复制使用
14.2.1 方法一:使用lodash插件可以进行对象深复制
lodash插件的方法:
_.cloneDeep(对象名) :深复制
1. 下载lodash插件,打开集成终端输入npm i lodash。
2. 创建一个js的文件夹,在node_modules下把lodash.js放在js文件夹下,删除node_modules。
3. 在页面中引入lodash插件。
<script src="./js/lodash.js"></script>
4. 编写代码
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><script src="./js/lodash.js"></script>
</head><body><script>var data = {a: 1,[Symbol("a")]: 2,c: /a/g,b: {c: 3,play() {console.log("aa");}}}//深复制方法一:// var newData = JSON.parse(JSON.stringify(data));// console.log(newData);//深复制方法二:var newData = _.cloneDeep(data);// 改变源对象的值,查看是否会改变新对象的值data.b.c = 30;console.log(newData, data);</script>
</body></html>
lodash插件进行深复制也有弊端,有些数据还有引用关系。
14.2.2 方法二:封装对象深复制(重点)
遍历分为 广度遍历( for ,for in ,for of)和 深度遍历( while ,递归)。
1. 怎样做递归?递归的方式分两种。
- 方法一:先做深度遍历,在做广度遍历。
- 方法二:先做广度遍历,在做深度遍历。
案例1:二叉树的遍历
var tree = {value: 1,left: {value: 2,left: {value: 4,left: null,right: null},right: {value: 5,left: null,right: null},},right: {value: 3,left: {value: 6,left: null,right: null},right: {value: 7,left: null,right: null},},};// 先序遍历// function showTree(tree) {// console.log(tree.value);// if (tree.left) showTree(tree.left)// if (tree.right) showTree(tree.right)// }// 中序遍历// function showTree(tree) {// if (tree.left) showTree(tree.left)// console.log(tree.value);// if (tree.right) showTree(tree.right)// }// 后序遍历function showTree(tree) {if (tree.left) showTree(tree.left)if (tree.right) showTree(tree.right)console.log(tree.value);}showTree(tree);
案例2:
var obj = {a: 1,b: {c: [1, 2, 3, 4],d: {e: 3,},},f: {g: {h: 4,},},};//方法一:先做深度遍历,在做广度遍历。// function showDeep(obj) {// for (var key in obj) {// // 是对象,但不是null// if (obj[key] && typeof obj[key] === "object") {// showDeep(obj[key])// } else {// console.log(obj[key]);// }// }// }// 方法二:先做广度遍历,在做深度遍历。function showDeep(obj) {if (typeof obj !== "object" || !obj) {console.log(obj);}for (var key in obj) {showDeep(obj[key])}}showDeep(obj);
案例3:尾递归 ,在尾部递归。
// 递归 尾递归 在尾部递归var i = 0;var sum = 0;function getSum() {i++;if (i > 100) return;sum += i;getSum();}getSum();console.log(sum);
案例4: