深入学习jquery源码之创建科学、复用率高的对象

常规创建对象的方式

通过{},[] 来定义数组和对象

1.{ } 大括号,表示定义一个对象,大部分情况下要有成对的属性和值,或是函数。

2.[ ]中括号,表示一个数组,也可以理解为一个数组对象。

3.{ } 和[ ] 一起使用,我们前面说到,{ } 是一个对象,[ ] 是一个数组,我们可以组成一个对象数组

调用起来对象的属性用.(点)叠加,数组用 [下标] 来访问。

通过”字面量“的方式来创建对象

var person = {
    name: "dongjc",
    age: 32,
    Introduce: function () { alert("My name is " + this.name + ".I'm " + this.age); }
 };
person.Introduce();  
person.worker = 'coding'; //丰富成员信息

调用方式

var ren ={};
ren.name="张三";
ren.sex="男";
ren.eat=function  () {
   alert("吃饭");
}
alert(ren.name);
alert(ren["name"]);

当我们创建一个对象 ren,会在栈内存中保存一个地址,栈为长度不可变的地址。

而栈中的地址就对应堆中的存储地址。堆中的存储地址,只要实例化会在堆中开辟一块空间,地址就是栈的地址,内容就是实例化对象里面的内容,如name,sex,eat。可以通过地址引用,访问里面的属性和方法。

当我们再实例化一个对象,又会保存另一个地址及开辟一块空间。

代码段,共同的属性或方法放在代码段中,不在堆中。只执行一次,节省内存空间。

 

通过object方式创建对象,动态添加属性和方法

        var p=new Object(); 
	p.name="Jack"; // 动态的添加属性
	p.func=speak;  // 动态的添加方法
	alert(p.name);
	p.func("Hello,Hello,大家好!"); 
	
	 delete p.name; 
	alert(p.name);
	delete p.func;
	p.func("Hello,Hello,大家好!"); 
	
	 p.name=undefined;
	p.func=undefined;
	alert(p.name);
	p.func("Hello,Hello,大家好!"); 
	

 

通过Function函数来创建对象

小括号中最后一个参数是函数体,之前所有的参数都是形参.

 var sayFunc=new Function("name","age","alert(name+'今年'+age+'岁了')");
 // sayFunc("李四",4);
 alert("sayFunc方法对象的方法参数个数:"+sayFunc.length);
 alert(sayFunc.toString());//获取源码
 alert(sayFunc.valueOf());//获取源码

 

 

函数劫持

函数劫持:改变javascript的预定义的函数预定义好的功能

window.alert = function(x){
			document.write(x) ;
		}
			alert("abc") ;

   a.如果两个函数的命名相同,后面的将会覆盖前面的函数。
   b.以基本语法声明的函数,会在代码运行的时候,提前加载到内存当中,以供以后使用,
     但是匿名函数形式命名的函数,会在执行到的时候,才进行赋值

   c.在不同的<script></script>块中的函数,使用和调用的时候,应该先定义,后执行。

 

通过工厂模式创建对象

工厂模式虽然解决多次创建相似对象的重复性问题,但是并没有解决对象识别问题,也就是typeof之后他都显示object,具体的对象是什么并没有显示

function createPerson(name,age,job)
    {
        var o=new Object();
        o.name=name;
        o.age=age;
        o.job=job;
        o.sayName=function(){
            alert(this.name);//this指的是o
        }
        return o;
    }
   var person1=createPerson("Tom",23,"厨师");
    person1.sayName();

 

创建科学、复用率高的对象

构造函数模式和工厂模式的区别

  1.没有显式的创建对象。

  2.将属性和方法赋给了this对象。

  3.没有return语句。

  4.函数名第一个字母大写。

构造函数模式优于工厂模式的原因就是,构造函数模式中的对象实例(person1)通过constructor属性或instanceof操作符可以验证person1既是Object的实例,也是Person的实例,同时也证明所有对象均来自于Object。

	function person(name,age){
		this.name2=name;          //那个对象引用this就代表那个对象
		this.age2=age;            //通过this关键字设置默认成员
		var worker = 'coding';    //没有this关键字,对象创建后,该变量为非成员只能在方法内部使用
		function speak(something){
			alert(something);
		}
		
		this.func=speak;
	}
	
	var p1=new person("Jack",12);
	alert(p1.name2);
	p1.func("Hello,EveryOne!");

	function C(){
		var privateFunc=function(){
			alert("私有方法");
		};
		privateFunc();
		this.objFunc=function(){
			alert("对象方法");
		};
		C.prototype.objFunc2=function(){
			alert("对象方法2");
		};
	}
	C.classFunc=function(){
		alert("类方法");
	};

 

定义对象模拟数组

function myArray () {
var lengs=  arguments.length;
for (var i=0; i<lengs; i++) {
this[i]=arguments[i];
}
}
var arr=new myArray(1,2,3);
alert(arr[0]);

构造函数也有缺点,对象是引用类型,对象实例化不是指针的改变,而是简单的复制,复制对象的方法和属性,假设一个对象有上千个实例,它就会复制上千个功能相同的方法,这显然是不可取的。

function dianshi (color,size,brand) {
var Tv={};
Tv.color=color;
Tv.size=size;
Tv.brand=brand;
Tv.look=function  () {
alert("看电视");
}
Tv.play=function  () {
alert("玩游戏");
}
Tv.dvd=function  () { 
alert("DVD");
} 
 return Tv;
}
var ds=dianshi("red","30inch","sony");
//alert(typeof ds)//返回object
alert(ds.color)
var ds1=dianshi("blue","40inch","changh");
alert(ds1["size"])//传递参数

   我们也可以把sayName()函数的定义战役到构造函数的外部,这样我们就将sayName属性设置成等于全局的sayName函数,这样实例化对象就共享全局作用域中的同一个sayName(),解决了构造函数对象方法的多次创建问题。但是全局作用域定义的sayName()函数只能被某个对象调用谈什么全局作用域,而且如果构造函数对象的方法有很多,就需要定义很多全局函数,封装性又从何谈起,于是原型模式应运而生。

 

原型模式创建对象

我们创建的每一个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,这个对象中包含着所有对象实例的属性和方法,这个对象就是原型对象。通俗的说,原型对象中的方法和属性可以被所有对象的实例所共享,对象实例就不用多次创建相同的方法和属性。

function Person(){
        
    };
    Person.prototype={
        name:"Tom",
        age:23,
        job:"web前端工程师",
        sayName:function(){
            alert(this.name);
        }
    }
    var person1=new Person();
    person1.sayName();

定义对象中的funciton

// 设置一个允许标记
var allowDemoRun = true;
var oldDemo = $.prototype.demo;
// 重写jQuery类的demo方法
$.prototype.demo = function(){
    // 假如标记为false就不允许执行
    if(!allowDemoRun) return;
    // 调用jQuery类原来的demo方法
    oldDemo&&oldDemo.apply(this,arguments);
}
$("p").demo();
buttonA.click(function() {
     
        allowDemoRun = false;
     
});
buttonB.click(function() {
     allowDemoRun = true;
});

​

原型链调用function

;define(function (require, exports, module) {
    var troubleMaker= {
        abC: function(plz) {
            this.abC = plz;
            return this;
        },
        deF: function(zlp) {
            this.deF = zlp;
            return this;
        }
    };
    module.exports = eventTracker;
});

在被引用的模块内第一次有效,第二次就会提示abC is not a function。问题原因:abC方法名和方法内部的this.abC重名了。

解决:

;define(function (require, exports, module) {
    var troubleMaker= {
        abC: function(plz) {
            this._abC = plz;
            return this;
        },
        deF: function(zlp) {
            this._deF = zlp;
            return this;
        }
    };
    module.exports = eventTracker;
});

共同的属性与方法放在代码段,节省内存空间

function Tv(color,size,brand) {
this.color=color;
this.size=size;
this.brand=brand;
this.play=function  () {
alert("玩游戏");
}
}

Tv.prototype.look=function  () {
 alert("看电视");
 }
Tv.prototype.dvd=function  () {
  alert("DVD");
 }
Tv.prototype.aaa={name:"张三"};//只能共享属性或函数,不能共享对象
var sony=new Tv("red","20 inch","sony");
var changhong =new Tv("red","20 inch","CH");

 delete sony.color
 delete sony.play//undefine
 delete sony.look//能访问到

sony.look();
changhong.look();

sony.aaa.name="李四"//李四
changhong.aaa.name//李四

 

在处理构造函数的时候,可以通过let 绑定来共享一个或者多个私有成员,而不使用闭包

​
  var Thing;
  {
      let privateScope = new WeakMap();
      let counter = 0;
  
      Thing = function(){
          this.someProperty = 'foo';
          privateScope.set(this, {
              hidden: ++counter,
         });
     };
 
     Thing.prototype.showPublic = function(){
         return this.someProperty;
     };
 
    Thing.prototype.showPrivate = function(){
         return privateScope.get(this).hidden;
     };
 }
 
 console.log(typeof privateScope);   //undefined
 
 var thing = new Thing();
 console.log(thing);                //Thing { someProperty: 'foo' }
 console.log(thing.showPublic());   //foo
 console.log(thing.showPrivate());  //1

​

 

全局jquery对象的创建

(function(){
var jQuery = function(){
	return new F();
};
var F = function(){
	return this;
};
F.fn = F.prototype = {
	jquery : 1.0,
	test : function(){
		console.log('test');
		return this;
	}
};
jQuery.fn = jQuery.prototype = F.prototype;
window.jQuery = window.$ = jQuery;
})();

var  a = $('body').test('li');
console.log('');
$.fn.extend = function(){console.log('extended');}
a.extend();

虽然可以通过对象实例访问保存在原型对象中的值,但却不能通过对象实例重写原型的值。其实对象实例获取某一属性的值是从本身开始寻找,然后是原型对象,最后是构造函数对象,所以重写对象实例的属性值(这个值可以通过delete操作符删除)仅仅是阻断了获取原型属性值的途径,但是没有改变其中的值。

 

引用类型存在的问题

function Obj(){
                this.a=[]; //实例变量
                this.fn=function(){ //实例方法

                }
            }
            var o1=new Obj();
            o1.a.push(1);
            o1.fn={};
            console.log(o1.a); //[1]
            console.log(typeof o1.fn); //object
            var o2=new Obj();
            console.log(o2.a); //[]
            console.log(typeof o2.fn); //function

在o1中修改了a和fn,而在o2中没有改变,由于数组和函数都是对象,是引用类型, 这就说明o1中的属性和方法与o2中的属性与方法虽然同名但却不是一个引用,而是对Obj对象定义的属性和方法的一个复制。 这个对属性来说没有什么问题,但是对于方法来说问题就很大了,因为方法都是在做完全一样的功能,但是却又两份复制,如果一个函数对象有上千和实例方法, 那么它的每个实例都要保持一份上千个方法的复制,这显然是不科学的

            function Person(name){
                this.name=name;
            }

            Person.prototype.printName=function(){
                alert(this.name);
            }

            var person1=new Person('Byron');
            var person2=new Person('Frank');

Person的实例person1中包含了name属性,同时自动生成一个__proto__属性,该属性指向Person的prototype,可以访问到prototype内定义的printName方法,大概就是这个样子的

根据上图可以看出Person对象会自动获得prototyp属性,而prototype也是一个对象,会自动获得一个constructor属性,该属性正是指向Person对象。 当调用构造函数创建一个实例的时候,实例内部将包含一个内部指针(很多浏览器这个指针名字为__proto__)指向构造函数的prototype,这个连接存在于实例和构造函数的prototype之间, 而不是实例与构造函数之间

 

构建一个复用率高的对象

实例对象的属性或函数则定义到prototype中, 如果希望每个实例单独拥有的属性或方法则定义到this中,可以通过构造函数传递实例化参数。

            function Person(name){
                this.name=name;
            }

            Person.prototype.share=[];

            Person.prototype.printName=function(){
                alert(this.name);
            }

 

构建一个类,new来创建对象

 var jQuery = function () {
     // 构造函数
 }
 
 jQuery.prototype = {
     version: '1.1.1',
     name: function() {
         console.log(this.version);
     }
 }
 
 var a = new jQuery();
 
 a.name(); // "1.1.1"

 

构造函数创建的属性是实例私有的,原型创建的属性和方法是实例共享的。

    function SuperType(name,colors)
    {
        this.name=name;
        this.colors=colors;
    };
    SuperType.prototype.sayName=function()
    {
        alert(this.name);
    };
    function SubType(age,name,colors)
    {
        SuperType.call(this,name,colors);
        this.age=age;
    };
    SubType.prototype=new SuperType();
    SubType.prototype.sayAge=function()
    {
        alert(this.age);
    };
    var person1=new SubType(23,"Tom",["blue","red","green"]);
    document.writeln(person1.colors);//来自父对象构造函数
    document.writeln(person1.name);//来自父对象构造函数
    person1.sayName();//来自父原型对象
    document.writeln(person1.age);//来自子对象构造函数
    person1.sayAge();//来自子原型对象

prototype内属性、方法是能够共享

            function Person(name){
                this.name=name;
            }

            Person.prototype.share=[];

            Person.prototype.printName=function(){
                alert(this.name);
            }
            var person1=new Person('Byron');
            var person2=new Person('Frank');

            person1.share.push(1);
            person2.share.push(2);
            console.log(person2.share); //[1,2]

 

借助原型可以基于已有的对象创建新对象,同时还不用创建自定义类型方法来达到这个目的

    function object (o)//这个o相当于父对象实例
    {
        function F(){}//这个F相当子对象
        F.prototype=o;//继承
        return new F();//实例化传出
    }
    function inheritPrototype(subType,superType)
    {
        var prototype=object(superType.prototype);//创建对象
        prototype.construct=subType;//增强对象
        subType.prototype=prototype;//指定对象
    }
    function SuperType(name)
    {
        this.name=name;
    }
    SuperType.prototype.sayName=function()
    {
        alert(this.name);
    }
    function SubType(name,age)
    {
        SuperType.call(this,name);
        this.age=age;
    }
    inheritPrototype(SubType,SuperType);
    SubType.prototype.sayAge=function(){
        alert(this.age);
    }
    var person1=new SubType("Tom",23);
   person1.sayName();

 

属性方法覆盖

实际上当代码读取某个对象的某个属性的时候,都会执行一遍搜索,目标是具有给定名字的属性,搜索首先从对象实例开始,如果在实例中找到该属性则返回, 如果没有则查找prototype,如果还是没有找到则继续递归prototype的prototype对象,直到找到为止,如果递归到object仍然没有则返回错误。 同样道理如果在实例中定义如prototype同名的属性或函数,则会覆盖prototype的属性或函数。

           function Person(name){
                this.name=name;
            }

            Person.prototype.share=[];
            var person=new Person('Byron');
            person.share=0;

            console.log(person.share); //0而不是prototype中的[]

 

constructor

在 Javascript 语言中,constructor 属性是专门为 function 而设计的,它存在于每一个 function 的prototype 属性中。这个 constructor 保存了指向 function 的一个引用。 
在定义一个函数时,JavaScript 内部会执行如下几个动作:

1.为该函数添加一个原形(即 prototype)属性 
2. 为 prototype 对象额外添加一个 constructor 属性,并且该属性保存指向函数F 的一个引用

这样当我们把函数 F 作为自定义构造函数来创建对象的时候,对象实例内部会自动保存一个指向其构造函数(这里就是我们的自定义构造函数 F)的 prototype 对象的一个属性proto,

所以我们在每一个对象实例中就可以访问构造函数的 prototype 所有拥有的全部属性和方法,就好像它们是实例自己的一样。当然该实例也有一个 constructor属性了(从 prototype 那里获得的),每一个对象实例都可以通过 constrcutor 对象访问它的构造函数。

面向对象创建对象

原型模式的缺点:因为所以对象实例共享原型对象的方法和属性,但是往往实例都有他自己私有的属性,这时候原型模式就不适用了,所以我们可以混合使用构造函数模式和原型模式。

无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,默认情况下prototype属性会默认获得一个constructor(构造函数)属性, 这个属性是一个指向prototype属性所在函数的指针

function Person(){

            }

用面向对象语法表示的时候,原型对象的constructor属性不在指向Person,因为每创建一个函数,同时会创建它的prototype对象,用面向对象语法本质上相当于重写了prototype对象,constructor属性也会变成新对象的constructor属性(这里指向Object)

    function Person(){

    };
    Person.prototype={
        constructor:Person,
        name:"Tom",
        age:23,
        job:"厨师",
        sayName:function(){
            alert(this.name);
        }
    }
    var person1=new Person();
    var person2=new Person();
    person1.name="Mike";
    alert(person1.name);
    alert(person2.name);

对象类型判断

var f = new F();
if(f.constructor === F) {
// do sth with F
}

其实 constructor 的出现原本就是用来进行对象类型判断的,但是 constructor 属性易变,不可信赖。我们有一种更加安全可靠的判定方法:instanceof 操作符。

if(f instanceof F) {
// do sth with F
}

 

原型链继承

一个子类对象可以获得其父类的所有属性和方法,称之为继承。

由于 constructor 存在于 prototype 对象上,因此我们可以结合 constructor 沿着原型链找到最原始的构造函数,如下面代码:

function Base() {}

// Sub1 inherited from Base through prototype chain
function Sub1(){}
Sub1.prototype = new Base();
Sub1.prototype.constructor = Sub1;

Sub1.superclass = Base.prototype;

// Sub2 inherited from Sub1 through prototype chain
function Sub2(){}
Sub2.prototype = new Sub1();
Sub2.prototype.constructor = Sub2;

Sub2.superclass = Sub1.prototype;

// Test prototype chain
alert(Sub2.prototype.constructor);// function Sub2(){}
alert(Sub2.superclass.constructor);// function Sub1(){}
alert(Sub2.superclass.constructor.superclass.constructor);// function Base(){}

 

constructor 易变,那是因为函数的 prototype 属性容易被更改,我们用时下很流行的编码方式来说明问题

function F() {}
    F.prototype = {
    _name: 'Eric',
    getName: function() {
    return this._name;
    }
};

初看这种方式并无问题,但是你会发现下面的代码失效了:
var f = new F();
alert(f.constructor === F); // output false

F 不是实例对象 f 的构造函数了吗?当然是!只不过构造函数 F 的原型被开发者重写了,这种方式将原有的 prototype 对象用一个对象的字面量{}来代替。而新建的对象{}只是 Object 的一个实例,系统(或者说浏览器)在解析的时候并不会在{}上自动添加一个 constructor 属性,因为这是 function 创建时的专属操作,仅当你声明函数的时候解析器才会做此动作。然而你会发现 constructor 并不是不存在的。

既然存在,那这个 constructor 是从哪儿冒出来的呢?我们要回头分析这个对象字面量 {}。因为{}是创建对象的一种简写,所以{}相当于是 new Object()。那既然{}是 Object 的实例,自然而然他获得一个指向构造函数 Object()的 prototype 属性的一个引用proto,又因为 Object.prototype 上有一个指向 Object 本身的 constructor属性。所以可以看出这个constructor其实就是Object.prototype的constructo
一个解决办法就是手动恢复他的 constructor,下面代码非常好地解决了这个问题:

function F() {}
F.prototype = {
    constructor: F, /* reset constructor */
    _name: 'Eric',
    getName: function() {
    return this._name;
    }
};
var f = new F();
alert(f.constructor === F); // output true this time ^^

 

构造函数上怎么还有一个 constructor ?它又是哪儿来的?细心的会发现,像 JavaScript 内建的构造函数,如 Array, RegExp, String,Number, Object, Function 等等居然自己也有一个 constructor: 

经过测试发现,此物非彼物它和 prototype 上 constructor 不是同一个对象,他们是共存的:

alert(typeof Array.constructor != 'undefined');// output true
alert(typeof Array.prototype.constructor === Array); // output true

在JavaScript中,每个具有原型的对象都会自动获得constructor属性。除了arguments、Enumerator、Error、Global、Math、RegExp、Regular Expression等一些特殊对象之外,其他所有的JavaScript内置对象都具备constructor属性。例如:Array、Boolean、Date、Function、Number、Object、String等。所有主流浏览器均支持该属性

不过这件事情也是好理解的,因为 构造函数也是函数。是函数说明它就是 Function 构造函数的实例对象,自然他内部也有一个指向 Function.prototype 的内部引用proto啦。因此我们很容易得出结论,这个 constructor(构造函数上的constructor 不是 prototype 上的)其实就是 Function 构造函数的引用:

alert(Array.constructor === Function);// output true
alert(Function.constructor === Function); // output true

构造函数(Constructor)在对象创建或者实例化时候被调用的方法。通常使用该方法来初始化数据成员和所需资源。构造器Constructor在js不能被继承,因此不能重写Overriding,但可以被重载Overloading

 

在一个构造方法中可以使用super关键字来调用一个父类的构造方法。

如果没有显式指定构造方法,则会添加默认的 constructor 方法。

如果不指定一个构造函数(constructor)方法, 则使用一个默认的构造函数(constructor)。

class Polygon {
  // ..and an (optional) custom class constructor. If one is
  // not supplied, a default constructor is used instead:
  // constructor() { }
  constructor(height, width) {
    this.name = 'Polygon';
    this.height = height;
    this.width = width;
  }

  // Simple class instance methods using short-hand method
  // declaration
  sayName() {
    ChromeSamples.log('Hi, I am a ', this.name + '.');
  }

  sayHistory() {
    ChromeSamples.log('"Polygon" is derived from the Greek polus (many) ' +
      'and gonia (angle).');
  }

  // We will look at static and subclassed methods shortly
}

// Classes are used just like ES5 constructor functions:
let p = new Polygon(300, 400);
p.sayName();
ChromeSamples.log('The width of this polygon is ' + p.width);

调用

class Square extends Polygon {
    constructor(length) {
        // 在这里, 它调用了父类的构造函数, 并将 lengths 提供给 Polygon 的"width"和"height"
        super(length, length);
        // 注意: 在派生类中, 必须先调用 super() 才能使用 "this"。
        // 忽略这个,将会导致一个引用错误。
        this.name = 'Square';
    }
    get area() {
        return this.height * this.width;
    }
    set area(value) {
        // 注意:不可使用 this.area = value
        // 否则会导致循环call setter方法导致爆栈
        this._area = value;
    }
}

let s = new Square(5);

s.sayName();
ChromeSamples.log('The area of this square is ' + s.area);

 

js对象的constructor属性返回创建该对象的函数的引用。

// 字符串:String()
var str = "张三";
document.writeln(str.constructor); // function String() { [native code] }
document.writeln(str.constructor === String); // true
// 数组:Array()
var arr = [1, 2, 3];
document.writeln(arr.constructor); // function Array() { [native code] }
document.writeln(arr.constructor === Array); // true
// 数字:Number()
var num = 5;
document.writeln(num.constructor); // function Number() { [native code] }
document.writeln(num.constructor === Number); // true
// 自定义对象:Person()
function Person(){
this.name = "CodePlayer";
}
var p = new Person();
document.writeln(p.constructor); // function Person(){ this.name = "CodePlayer"; }
document.writeln(p.constructor === Person); // true
// JSON对象:Object()
var o = { "name" : "张三"};
document.writeln(o.constructor); // function Object() { [native code] }
document.writeln(o.constructor === Object); // true
// 自定义函数:Function()
function foo(){
alert("CodePlayer");
}
document.writeln(foo.constructor); // function Function() { [native code] }
document.writeln(foo.constructor === Function); // true
// 函数的原型:bar()
function bar(){
alert("CodePlayer");
}
document.writeln(bar.prototype.constructor); // function bar(){ alert("CodePlayer"); }
document.writeln(bar.prototype.constructor === bar); // true

 

构造函数定义实例的私有属性,原型模式定义共享属性和方法。

function Tv(color,size,brand) {
	  this.color=color;
	  this.size=size;
	  this.brand=brand;
	  this.play=function  () {
	     alert("玩游戏");
	  }
	
	 Tv.prototype.aaa={name:"张三"};

	}

	  Tv.prototype.look=function  () {
	     alert("看电视");
	  }
	  Tv.prototype.dvd=function  () {
	     alert("DVD");
	  }
}

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wespten

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值