JS进阶知识——(二)函数高阶知识

本文详细介绍了JavaScript中的原型与原型链,包括显式和隐式原型,原型链的作用;探讨了执行上下文和上下文栈的概念,以及变量提升和函数提升的原理;此外,还深入讨论了闭包的原理、作用、生命周期和应用,并提供了面试题实例,帮助读者深化对JS高级知识的理解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

JS进阶知识。


前言

这是函数的高级知识,主要涉及到JS原型链问题、执行上下文问题、作用域问题以及函数的闭包问题,是面试最常问到的JS考察点。

一、原型与原型链。

1.1 什么是原型(prototype)?

		<title></title>
	</head>
	<!--                                        
	 1、函数的prototype属性                        ↑
	 *每个函数都有一个Prototype属性 它默认指向一个object 空对象(即为原型对象)_
	 *原型对象中有一个属性 constructor 它默认指向函数对象
	 
	 2、给原型对象添加属性(一般都是作为方法添加)
	 *作用:所有的函数对象的实例对象自动拥有原型中的属性与方法;
		 
	 -->
	<body>
		<script type="text/javascript">
			console.log(Date.prototype)
			function Fun1(){
				
			}
			console.log(Fun1.prototype)//默认指向一个object空对象 没有属性
			
			console.log('________________________________')
			//原型对象中有一个属性 constructor 它默认指向函数对象
			console.log(Date.prototype.constructor === Date)
			console.log(Fun1.prototype.constructor === Fun1)
			
			//函数对象的实例对象自动拥有原型中的属性与方法;
			Fun1.prototype.test = function (){
				console.log('test()')
			}
			var fun1 = new Fun1();
			fun1.test();
		</script>

便于理解 关系如下图所示

 其实为了理解原型链 我们首先得了解并承认下列最基础的结论:

 我们继续。

1.2 什么是显式原型与隐式原型?

 

		<title></title>
		<!-- 
		 1.每个函数function 都有个prototype ,即为 显式原型(属性)
		 
		 2、每个实例对象都有一个__proto__, 即为  隐式原型(属性) 
		 
		 3、对象的隐式原型的值 等于 其构造函数的显式原型的值。
		***我们通过显式原型添加方法! 我们能直接通过显式原型直接操作它。
		但是我们不能直接操作隐式原型!!(ES6之前        -我们正在学习ES5);
		 -->
	</head>
	<body>
		<script type="text/javascript">
			function Fn(){//故创建函数对象的内部语句就是 this.prototype = {}
				
			}
			//1、每个函数function 都有一个prototype 显式原型
			console.log(Fn.prototype)
			//2、每个实例对象都有一个__proto__, 即为  隐式原型(属性) 
			var fun1 = new Fn ()//由后面往前推我们可以知道————
			//创建实例就是 this._proto__ = Fn.prototype   将原型函数的显示原型地址值赋值给了实例对象的隐式原型。
			console.log(fun1.__proto__)
			
			//3、对象的隐式原型的值 等于 其构造函数的显式原型的值。
			console.log(Fn.prototype === fun1.__proto__)//返回一个true;
			//两个都是引用类型的 变量保存的是 !地址值!  两个保存一样的地址 指向同一块 →原型对象;
			
			
		</script>

! 注意 在Chrome等多数浏览器中 在控制台查看对象属性时 __proto__ 一般以[[prototype]]存在。 

1.3 原型链:

<title></title>
		<!-- 
		 
		 -->
	</head>
	<body>
		<script type="text/javascript">
			function Fn (){
				this.test1  = function(){
					console.log('test1')
				}
			}
			Fn.prototype.test2 = function(){
				console.log('test2')
			}
			var fn = new Fn ()
			fn.test1()
			fn.test2()
			console.log(fn.toString())
			
			//fn的__proto__是FN.prototype   而后者的__proto__是 Object.prototype. 而再后者的__proto__找不到 返回undefined
			
			/*原型链————在访问一个对象的属性时 现在自身的属性中查找 没找到再去__proto__链上往原型对象里找
			如果最终找不到了 返回undefined(现在返回Null))别名*隐式原型链  作用 查找对象的属性与方法。
			构造函数 与 原型 与实体对象的关系图解!
			
			*/
		   console.log('______________________________________________')
		   console.log(Object.prototype.__proto__)
		   
		   /*原型链补充总结:
		   1*函数的显式原型指向的对象默认是空的Object实例对象(但是Object不满足 它的显式原型对象是)
		
		   */
		  console.log(Fn.prototype instanceof Object) //true
		  console.log(Object.prototype instanceof Object)//false 唯一例外
		  console.log(Function.prototype instanceof Object)//true
		  console.log('————————————————————————————————————————————————————————————————')
		  /* 2 Function 是它自身的实例。。 所有函数都是Function 的实例对象。
		    它是实例对象 有隐式原型↓              ↓ 它是自身的实例 有显式原型属性                                   */
		 console.log(Function.__proto__ === Function.prototype)//输出true
		 //Object的原型对象是原型链尽头 
		 console.log(Object.prototype.__proto__ === null)//找不到它的原型了
		</script>

更直观地 我们可以画出下列图 所有的原型链关系基本都可以在下图中找到对应关系:

 1.4 原型链属性问题:

<title></title>
	</head>
	<body>
		<script type="text/javascript">
			function Pers(){
				
			}
			Pers.prototype.name = 'mqy'
			var per1 = new Pers()
			console.log(per1.name,per1)//**这里来到Pers.prototype找到属性name并输出
			
			var per2 = new Pers()
			per2.name = 'yyy'//这里是为 per2函数添加了name属性 值为yyy
			console.log(per1.name)//仍然是‘mqy’没变 因为在自身per1里没有这个属性 就去原型链中找
			console.log(per2.name)//改变为'yyy'了; 因为在per2中已经找到了这个属性 不去原型链中找了。
			//总结:读取对象属性值时,会自动去原型链中查找,而再设置对象属性值时,不查找原型链 如果当前对象没有此属性 直接添加此属性
			
			console.log('——————————————再来看看属性——————————————————————')
			function Person (name,age){
				this.name = name 
				this.age = age 
			}
			Person.prototype.setName = function (name){
				this.name = name 
			}
			var person1 = new Person('wsl',20)
			person1.setName('mls')
			console.log(person1)//可以看到对象的属性值就直接存放在构造的对象里 而加入的函数仍然在原型对象中
			
			var person2 = new Person('yrj',22)
			person2.setName('kyx')
			console.log(person2)
		</script>

原型链的作用:

原型链:每一个对象,都有一个原型对象与之关联,这个原型对象它也是一个普通对象,这个普通对象也有自己的原型对象,这样层层递进,就形成了一个链条,这个链条就是原型链。通过原型链可以实现JS的继承,把父类的原型对象赋值给子类的原型,这样子类实例就可以访问父类原型上的方法了。

作用:
1.数据共享 节约内存内存空间。
2.实现方法属性等的继承。

二、执行上下文与上下文栈

2.1 变量提升与函数提升

变量提升即将变量声明提升到它所在作用域的最开始的部分。——一般通过var申明变量实现。

创建函数有两种形式,一种是函数声明,另外一种是函数字面量,只有函数声明才有变量提升

说明:函数提升优先级高于变量提升,且不会被同名变量声明时覆盖,但是会被同名变量赋值后覆盖。

<title></title>
	</head>
	<!-- 
	
	 什么导致了变量提升?
	 js引擎执行前对代码的解析、
	 
	 -->
	<body>
		<script type="text/javascript">
			//试问a输出多少
			var a = 3
			function fn(){
				console.log(a)//var定义的变量(此处为a)在定义语句之前都可以访问到 只不过值为undefined
				var a = 4 //到这一步为a赋值   这就叫var的变量提升 值为undefined
			}
			fn()//a输出undefined
			
			fn2()//可以调用下面的fn2定义 这就是函数提升 值就是函数对象
			function fn2 (){
				console.log('fn2')
			}
			//如果是var fn3 = funtion(){} 则产生的是变量提升而不是函数提升 。
		</script>

2.2 执行上下文

执行上下文一般分为全局执行上下文和函数执行上下文。


全局执行上下文只有一个,它是由浏览器创建的,也就是常说的 window 对象。

函数执行上下文可能会有多个,当一个函数被调用时,就会产生一个函数执行上下文。当函数执行时,会创建一个称为执行期上下文的内部对象,函数执行就会产生执行期上下文(独一无二的),执行完一次会销毁一次。


具体实行如下所示:

		<title></title>
	</head>
	<!-- 1、代码分类(位置)
	*全局代码
	*函数(局部代码)
	2、全局执行上下文  
	*在执行全局代码前,将window确定为全局执行上下文
	*对全局数据进行预处理   *var定义的全局变量 ==》undefined 并!添加!为window的属性
	 *function声明的全局函数 ==>赋值(fun),并添加为window的方法!
	 *this == 》 赋值为 window   
	 *最后再开始执行全局代码。。。
	 
	 3、在调用函数时,准备执行函数体之前,创建对应的函数执行上下文对象
	 *对局部数据进行预处理
	 *形参变量 == >赋值(实参) ==>添加为执行上下文的属性
	 *arguments ==>赋值(实参变量) ==> 添加为执行上下文的属性
	 *var 定义的局部变量 ==>undefined 添加为执行上下文的属性
	 *function声明的函数  ==>赋值 fun  添加为执行上下文的方法
	 *this ==> 赋值 (调用函数的对象)
	 *开始执行函数体代码。。
	 
	 -->
	<body>
		<script type="text/javascript">
		//全局执行上下文
			console.log(a1,window.a1)
			console.log(a2,window.a2)
			console.log(this)
			//既然可以访问这些  说明在代码开头js就已经执行了某些操作 window!
			var a1 = 3
			function a2(){
				
			}
			console.log('_____________________________________________')
		//函数执行上下文
			function fn (a1){
				console.log(a1)//2
				console.log(a2)//undefined
				a3()//a3()
				console.log(this)//window
				console.log(arguments)//封装实参的伪数组(2,3)
				var a2 = 3 
				function a3 (){
					console.log('a3()')
				}
			}
			fn(2,3)
		</script>

2.3 执行上下文栈

JavaScript 引擎创建了执行上下文栈(Execution context stack,ECS)管理执行上下文

代码实行如下所示:

		<title></title> 
	</head>
	<!-- 
	 1.在全局代码执行前,js引擎就会创建一个栈来存储管理所有的执行上下文对象
	 2.在全局执行上下文(window)确定后 将其添加到栈中(压栈)
	 3.在函数执行上下文创建后 将其添加到栈中(压栈)
	 4.在当前函数执行完后,将栈顶的对象移除。(出栈)
	 5.当所有的代码执行完后 栈中只剩下window. 
	 
	 
	 -->
	<body>
		<script type="text/javascript">
			var a = 10//产生一个全局执行上下文对象window
			var bar = function(x){
				var b = 5
				foo(x + b)//产生一个执行上下文对象
			}
			var foo = function(y){
				var c = 5 
				console.log(a + c + y)
			}
			bar (10)//产生一个执行上下文对象 在函数调用的时候产生
			//不会出错 这两个都是var 定义的函数   执行上下文对象产生次数 n+1   ___1是window   n是函数调用次数。
			/*执行上下文栈:
		</script>

2.4 执行上下文栈 面试题实例:

		<title></title>
	</head>
	<!-- 
	 1.依次输出什么?
	 
	 
	 2.整个过程中产生了几个执行上下文?
	 -->
	<body>
		<script type="text/javascript">
			console.log('global begin:'+ i)//undefined
			var i = 1
			foo(1)
			function foo(i){
				if(i==4){
					return;//递归终止
				}
				console.log('foo( ) begin:' + i) //fb=1 ;fb=2 fb = 3 
				foo(i + 1)//递归调用自身 f1 f2 f3依次压入栈
				console.log('foo() end:'+ i)//结束之后开始出栈 fe = 3 fe = 2 fe = 1
			}
			console.log('global end:' + i)//i=1;

三、作用域与执行上下文

<title></title>
		<!-- 
		1.理解**就是一块地盘 一个代码块所在的区域   它是静态的(相对于上下文对象),在编写代码时就确定了
		
		2.分类**全局作用域
		**函数作用域
		**块作用域(仅在ES6后产生)
		
		3.作用及:*隔离变量,不同作用域下的同名变量不会有冲突。
		 -->
	</head>
	<body>
		<script type="text/javascript">
			var a = 10//作用域数 也是 n+1 (n个定义函数作用域 和 一个全局作用域。)
			var b = 20 
			function fn(x){
				var a = 100,c = 300
				console.log('fn()',a,b,c,x)
				function bar(x){
					var a= 1000,d = 400
					console.log('bar()',a,b,c,d,x)//其实就是就近原则 从函数作用域往全局作用域从里往外找。
					
				}
				bar(100)
				bar(200)
				
			}
			fn(10)
		</script>

执行上下文:

		<title></title>
		<!-- 
		 1.区别1
		 *全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了,而不是在函数调用时。
		 *全局执行上下文环境是在全局作用域确定之后,js代码马上执行之前创建
		 *函数执行上下文是在调用函数时产生,在函数体代码执行之前创建
		 
		 2.区别2
		 *作用域是静态的,只要函数定义好了就一直存在,且不会再变化
		 *上下文环境是动态的,调用函数时创建,函数调用结束时,上下文环境就会被释放。
		 
		 3.联系
		 *上下文环境(对象)是从属于所在的作用域
		 *全局上下文环境 ==> 全局作用域
		 *函数上下文环境 ==> 对应的函数使用域。
		 
		 -->
	</head>
	<body>
		<script type="text/javascript">
			
			
		</script>

面试题实例:

<title></title>
		<!-- 
		 -->
	</head>
	<body>
		<script type="text/javascript">
		var x = 10 
			function fn(){
				console.log(x)
			}
			function show(f){
				var x = 20
				f()
			}
			show(fn)//输出10  函数的作用域写代码时就确定了   fn内部找不到x 就在全局作用域找 x=10; 输出10
			console.log("_______________________________________")
			
			var obj = {
				fn2:function(){
					// console.log(fn2) 这样找不到fn2 因为函数作用域里没有 往外找的全局作用域也没有
					console.log(this.fn2)//这样可以找到这个fn2  因为这个fn2算是this.的属性。
				}
			}
			obj.fn2()//fn2 is not defined 
		</script>

四、闭包

4.0 闭包的引入

通过下面这个案例可以看出闭包是如何工作 生效的

<title></title>
		<!-- 
		 -->
	</head>
	<body>
		<button type="button">测试1</button>
		<button type="button">测试2</button>
		<button type="button">测试3</button>
		<script type="text/javascript">
			var btns = document.getElementsByTagName('button')
			//遍历加监听每个按钮
			// var length = btns.length 
			// for(var i = 0; i <length; i++){
			// 	var btn = btns[i]
			// 	btn.index = i //如果没有这句话
			// 	btn.onclick = function(){
			// 		alert('第'+(  i+1)+'个')//输出三个4  说明在函数执行的时候 i在几种情况下都为3 
			// 	}//               ↑把这个改成this.index既可以正常输出 1  2  3 
			// }
			// console.log(i)
			
			for(var i =0,length = btns.length;i < length ; i++){
				(function(i){
					var btn = btns[i]
					btn.onclick = function(){
						alert('第'+(  i+1)+'个')
					}
				})(i)//这些代码也可以正常执行  这就是  闭 包 的工作。
			}
		</script>

4.1  闭包的原理? 如何理解

<title></title>
		<!-- 
		 1、如何产生闭包? ——*当一个嵌套的内部子函数引用了嵌套外部夫函数的变量(函数)时,就产生了闭包
		 
		 2、闭包到底是什么?
		 *用chrome 调试查看 理解一:闭包是嵌套的内部函数(绝大部分人)
		 理解二:包含被引用变量的那个对象Z(少数人) 注意:闭包存在于嵌套的内部函数中。
		 
		 3.产生闭包的条件?
		 *函数嵌套
		 *内部函数引用了外部函数的数据(变量/函数)
		 
		 
		 -->
	</head>
	<body>
		<script type="text/javascript">
			function fn1 (){
				var a = 2 
				var b = 3 
				function fn2 (){//执行函数定义 不用执行内部函数fn2 就可以产生函数定义进而产生闭包(需要调用外部函数以定义内部)
				//这里把函数定义改为  var fun2 = function(){}就不会产生闭包 因为这里是对fun2变量提升 没有定义函数。
					console.log(a)
				}
				fn2()
			}
			fn1()
		</script>

下面就是一些常见的闭包的实例 可以加深我们的理解  显得这个概念不是那么抽象。

<title></title>
		<!-- 
		 -->
	</head>
	<body>
		<script type="text/javascript">
		//情况一 将函数作为另一个函数的返回值
			function fn1(){
				var a  = 2
				function fn2(){
					a++
					console.log(a)
				}
				return fn2
			}
			var  f  = fn1()
			f()//3
			f()//4  两次一共只创建了一个内部函数fn2 故总共只产生了一个闭包。
			fn1()//又创建一个闭包对象——外部函数又执行一次  内部函数对象被创建
			//而跟内部函数被执行的次数没有关系。
			console.log('______________________________________________')
			//情况二   将函数作为实参传递给另一个函数调用
			function showDelay(msg,time){//有外部函数 有内部函数  内部函数里引用了外部函数的msg 没有time 故产生了一个只包含msg 的闭包对象。
				setTimeout(function(){
					alert(msg)
				},time)
			}
			showDelay('mmmsn',2000)
			
		</script>

4.2  闭包的作用:

<title></title>
		<!-- 
		1.使用函数内部的变量 在函数执行完后 仍然可以存活在内存中(延长了局部变量的生命周期)
		2.让函数外部可以操作(读写)到函数内部的数据(变量/函数)
		
		问题:
		1.函数执行完后,函数内部声明的局部变量是否还存在?
		一般不存在,,,但是有闭包的话存在于闭包之中。
		2.在函数外部能直接访问到函数内部的局部变量吗?
		访问不到。
		-->
	</head>
	<body>
		<script type="text/javascript">
			//情况一 将函数作为另一个函数的返回值
				function fn1(){
					var a  = 2
					function fn2(){
						a++//执行完之后a还存在 于闭包之中
						console.log(a)
					}
					function fn3(){//fn3 在fn1执行完之后会被释放  因为他没有被引用 被存于闭包之中
					//但是它没有成为垃圾对象 因为 后面的f存起来了fn1的返回值  保存着fn3的地址。 因为有f指向着fn3 所以fn3被引用 不会成为垃圾对象 。
						a--
						console.log(a)
					}
					return fn3
				}
				
				var  f  = fn1()
				f()//3
				f()//4  两次一共只创建了一个内部函数fn2 故总共只产生了一个闭包。
				fn1()//又创建一个闭包对象——外部函数又执行一次  内部函数对象被创建
				//而跟内部函数被执行的次数没有关系。
				console.log('______________________________________________')
				//情况二   将函数作为实参传递给另一个函数调用
				// function showDelay(msg,time){//有外部函数 有内部函数  内部函数里引用了外部函数的msg 没有time 故产生了一个只包含msg 的闭包对象。
				// 	setTimeout(function(){
				// 		alert(msg)
				// 	},time)
				// }
				// showDelay('mmmsn',2000)
		</script>

4.3 闭包的生命周期

		<title></title>
		<!-- 
		 1.产生:在调用外部函数时 在嵌套的内部函数定义执行完时 就产生了(不是在调用内部函数时)
		 
		 2.死亡:在嵌套的内部函数变为垃圾对象时
		 -->
	</head>
	<body>
		<script type="text/javascript">
			function fn1(){
				//在这一步 进入fn1时  fn2的定义就完成了 fn2中对于a的闭包已经产生
				var a = 2
				function fn2(){
					a++
					console.log(a)
				}
				return fn2
			}
			var f = fn1()
			f()//3
			f()//4
			//加上下面这一步 使得 fn2不再有人包含这个闭包对象  没有变量再去引用这个闭包对象。
			// f = null 
		</script>

4.4 闭包的应用——自定义JS模块

		<title></title>
		<!-- 
		 自定义js模块:*具有特定功能的js文件
		 *将所有的数据和功能都封装在一个函数内部(私有的)
		 
		 -->
	</head>
	<body>
<!-- 		<script type="text/javascript" src="testModule.js">

		</script>
		<script type="text/javascript">
			var funtest = Test()
			funtest.doAny()
			funtest.doSome()
			//这就用到了闭包——
		</script> -->
		<script type="text/javascript" src="testModule2.js">
		</script>
		<script type="text/javascript">
			testModule2.doAny()//也利用了闭包 将方法添加到了window中
		</script>

闭包的缺点及其解决:

		<title></title>
		<!-- 
		 1.缺点
		 *执行完函数后,函数内的局部变量没有释放,占用内存的时间会边长。
		 *容易造成内存泄露。
		 
		 2.解决
		 *能不用闭包就尽量不用
		 *及时释放内存 。
		 
		 -->
	</head>
	<body>
		<script type="text/javascript">
			function fn1 (){
				var arr = new Array[100000]
				function fn2(){
					console.log(arr.length)
				}
				return fn2
			}
			var  f = fn1()//产生闭包了 那个数组arr还存在 内存还在占用
			f()
			f=null //让内部函数成为垃圾对象 使得浏览器回收掉闭包对象 释放内存
		</script>

 

4.5  补充——内存溢出与内存泄漏

	</head>
	<!-- 
	 1.内存溢出
	 *一种程序运行出现的错误
	 *当程序运行需要的内存超过了剩余的内存时,就会出现内存溢出的错误
	 2.内存泄漏
	 *占用的内存没有得到及时释放
	 *内存泄漏积累多了就容易导致内存溢出
	 *常见的内存泄漏:
	 *意外的全局变量
	 *没有及时清理的计时器或回调函数
	 *闭包对象。
	 
	 
	 
	 -->
	<body>
		<script type="text/javascript">
			// //1、内存溢出
			// var obj = {}
			// for(var i = 0 ; i<10000 ; i++){
			// 	obj[i] = new Array(10000000000)//内存溢出 浏览器崩溃 无法运行
			// }
			
			// 意外的全局变量
			function fn(){
				a = 3 //这是一个全局变量 因为它是在全局中定义的 只是在局部里赋值而已
				console.log(a)
			}
			fn()//所以在函数运行完之后 a并没有被释放  这就造成了意外的全局变量。
			
			var int1 = setInterval(function(){//启动循环定时器后不清理 不断执行
				console.log("___________________")
			},1000)
			clearInterval(int1)//需要用这个清除掉定时器 否则会不断循环 申请内存
			
			function fn1(){
				var a = 4
				function fn2 (){
					console.log(++a)
				}
				return fn2
			}
			var f = fn1()
			f()
			f = null //需要这一代码将闭包对象清理掉。
			
		</script>

4.6 闭包面试题实例

第一题:

		<title></title>
	
	</head>
	<body>
		<script type="text/javascript">
			//情况一
			var name = 'the window'
			var object = {
				name:'my object',
				getNameFunc:function(){
					return function(){
						return this.name ;
					}//有函数嵌套  没有内部函数引用外部变量  没有闭包
				}
			}
			alert(object.getNameFunc()())//返回 the window 
			//object.getNameFunc() 执行的是一个函数  function(){return this.name}  此函数直接执行
			//故而this指的是window  this.name 是全局变量中的name
			
			//情况二
			var name2 = 'the window'
			var object2 = {
				name2 : "my object2",
				getNameFunc: function(){
					var that = this
					return function(){
						return that.name2//有函数嵌套  有内部函数引用外部变量  有闭包
					}
				}
			}
			alert(object2.getNameFunc()())//返回 my object2
			//object2.getNameFunc() 同样是直接执行函数  这里的this同样是window  但是这里的that 是被object里的this赋值了的 。
			
		</script>

第二题:

		<script type="text/javascript">
			function fun(n,o){
				console.log(o)
				return{
					fun:function(m){
						return fun(m,n)
					}
				}
			}
			var a = fun(0)
			a.fun(1)
			a.fun(2)
			a.fun(3)
			console.log("————————————————————————————————————————————————————————————————————")
			var b = fun(0).fun(1).fun(2).fun(3)
			console.log("————————————————————————————————————————————————————————————————————")
			var c = fun(0).fun(1)
			c.fun(2)
			c.fun(3)
		</script>

总结

函数高阶知识点 深入理解JS的底层机制重点就在此。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值