JS和jquery的几个令人迷惑的问题之四-函数

14 篇文章 0 订阅
7 篇文章 0 订阅
1.函数
1.1.函数定义
1.1.1.definition expression vs. declaration statement
函数定义有两种方式
函数定义表达式和函数声明语句
definition expression vs. declaration statement
两者看起来差不多,程序员经常随意使用。其实还是有微妙的差别。

// 函数定义表达式-在赋值表达式中使用
var fnx=function(str)
{
  console.log(str+ ' from fnx');
};
//函数定义表达式-调用过程中使用定义表达式,<pre name="code" class="javascript">//这里的函数作为一个参数
data.sort(function(a,b){return a-b;})
//函数表达式-有时候定义后立即调用
var tensqured=(function(x){return x*x;}(10));

// 函数声明语句
function fn(str)
{
  console.log(str);
};
 一共发现三个区别.先说第一个,不一样的提前。我们知道js里面变量声明提前的说法。两种方式都创建了新的函数对象, 但函数声明语句的函数名是一个变量名, 变量指向函数对象, 和通过一般的通过var声明了一个变量一样 
 。前者函数定义表达式, 只有变量声明提前了,变量初始化代码仍然在原来的位置,而用函数声明语句创建的函数, 函数名称和函数体均被提前,所以我们可以在声明它之前就使用它。可以使用下面的一段代码去验证这种区别: 
 
console.log(typeof (fn)); // function
fn("abc"); // abc

console.log(typeof (fnx)); // undefined

if (fnx)
    fnx("abc"); // will not execute
else
    console.log("fnx is undefined"); // fnx is undefined

// 函数声明语句
function fn(str) {
    console.log(str);
};

// 函数定义表达式
var fnx = function(str) {
    console.log(str + " from fnx");
};

再说第二个区别-funcname是否必须
定义表达式一般没有省略了函数名字,有人说fnx不就是名字了吗?我说的是下列格式中的名字funcname
function funcname(parameters)
{
   statements;
}
在声明语句中funcname是必须的,而且在上下文范围内都是可见的。
在定义表达式中funcname是可选的,就算提供了funcname,也仅仅只是存在于函数体中,并指代该函数对象本身,这个名称在递归中很有用。

第三个区别-适用场合
就是定义表达式只适用于它作为一个大的表达式的一部分,比如在赋值和调用过程中定义函数。
定义表达式一般来说为了紧凑不提供funcname。
定义表达式特别适合用来定义那些只会用到一次的函数,例子如下:
<pre name="code" class="javascript">//函数定义表达式-调用过程中使用定义表达式,
//这里的函数作为一个参数data.sort(function(a,b){return a-b;})//函数表达式-有时候定义后立即调用var tensqured=(function(x){return x*x;}(10));
 而对于 
 函数声明语句来说,其实他不算真正的语句。可以出现在全局代码里面,或者内嵌在其他函数中, 
 但是它们不能出现在循环、条件判断、或者try/catch/finally以及with语句中。(EMCASCRIPT规范)1.2.函数调用函数调用  普通的函数调用  调用表达式可以进行普通的函数调用,也可以进行方法调用。方法调用   方法链,方法返回对象自己this,就可以使用方法链。构造函数调用  这个类似于java类的构造函数间接调用   call   apply   
 两个方法都运行显色指定调用所需的this值,也就是说,任何函数可以作为任何对象的方法来调动,哪怕这个函数不是那个对象的方法。1.2.1.this关键字this是关键字,不是变量,也不是属性名。不允许给this赋值。this没有作用域的限制。嵌套函数中的this如果是作为方法调用,this就是对象。如果嵌套函数作为普通函数调用,其this值不是全局对象就是undefined(strict mode)。 
 
var o = { // An object o.
  m: function() { // Method m of the object.
    var self = this; // Save the this value in a variable.
    console.log(this === o); // Prints "true": this is the object o.
    f(); // Now call the helper function f().
    function f() { // A nested function f
      console.log(this === o); // "false": this is global or undefined
      console.log(self === o); // "true": self is the outer this value.
    }
  }
};
o.m(); // Invoke the method m on the object o.

1.3.函数的形参和实参
1.3.1.可选形参
可选形参放在实参列表的最后。最佳实践是使用/*optional*/来强调形参是可选的。
// Append the names of the enumerable properties of object o to the
// array a, and return a. If a is omitted, create and return a new array.
function getPropertyNames(o, /* optional */ a) {
  if (a === undefined) a = []; // If undefined, use a new array
  for(var property in o) a.push(property);
    return a;
}
// This function can be invoked with 1 or 2 arguments:
var a = getPropertyNames(o); // Get o's properties into a new array
getPropertyNames(p,a); // append p's properties to that array


1.3.2.实参对象-可变长的实参列表
arguments表示实参对象的引用,是类数组对象。

function max(/* ... */) {
  var max = Number.NEGATIVE_INFINITY;
  // Loop through the arguments, looking for, and remembering, the biggest.
  for(var i = 0; i < arguments.length; i++)
    if (arguments[i] > max) max = arguments[i];
  // Return the biggest
  return max;
}
var largest = max(1, 10, 100, 2, 3, 1000, 4, 5, 10000, 6); // => 10000

callee和caller
实参对象还有两个属性:callee和caller
callee表示函数自己,caller指代当前正在执行的函数的函数。
通过caller属性可以访问调用栈。
callee属性有些时候很有用,比如匿名函数中通过callee来递归地调用自身。
1.3.3.将对象属性用做实参
使用对象作为实参,而不是很多个实参,这样的好处是不用记住这些好多个实参的次序。
// Copy length elements of the array from to the array to.
// Begin copying with element from_start in the from array
// and copy that element to to_start in the to array.
// It is hard to remember the order of the arguments.
function arraycopy(/* array */ from, /* index */ from_start,
                   /* array */ to, /* index */ to_start,
                   /* integer */ length)
{
  // code goes here
}
// This version is a little less efficient, but you don't have to
// remember the order of the arguments, and from_start and to_start
// default to 0.
function easycopy(args) {
  arraycopy(args.from,
    args.from_start || 0, // Note default value provided
    args.to,
    args.to_start || 0,
    args.length);
}
// Here is how you might invoke easycopy():
var a = [1,2,3,4], b = [];
easycopy({from: a, to: b, length: 4});

1.3.4.实参类型
js是一种非常灵活的弱类型语言。可以接受任意数量的实参,并可以递归的处理实参是数组的情况。
// Return the sum of the elements of array (or array-like object) a.
// The elements of a must all be numbers or null and undefined are ignored.
function sum(a) {
  if (isArrayLike(a)) {
    var total = 0;
    for(var i = 0; i < a.length; i++) { // Loop though all elements
      var element = a[i];
      if (element == null) continue; // Skip null and undefined
      if (isFinite(element)) total += element;
      else throw new Error("sum(): elements must be finite numbers");
    }//end of for loop
    return total;
  }
  else throw new Error("sum(): argument must be array-like");
}

1.4.作为值的函数
1.4.1.作为值的函数
函数不仅仅是一种语法,也是值。可以将函数赋值给变量,存储在对象的属性或者数组的元素中,作为参数传入另外一个函数。
Example 8-2. Using functions as data
// We define some simple functions here
function add(x,y) { return x + y; }
function subtract(x,y) { return x - y; }
function multiply(x,y) { return x * y; }
function divide(x,y) { return x / y; }
// Here's a function that takes one of the above functions
// as an argument and invokes it on two operands
function operate(operator, operand1, operand2) {
  return operator(operand1, operand2);
}
// We could invoke this function like this to compute the value (2+3) + (4*5):
var i = operate(add, operate(add, 2, 3), operate(multiply, 4, 5));
// For the sake of the example, we implement the simple functions again,
// this time using function literals within an object literal;
var operators = {
  add: function(x,y) { return x+y; },
  subtract: function(x,y) { return x-y; },
  multiply: function(x,y) { return x*y; },
  divide: function(x,y) { return x/y; },
  pow: Math.pow // Works for predefined functions too
};
// This function takes the name of an operator, looks up that operator
// in the object, and then invokes it on the supplied operands. Note
// the syntax used to invoke the operator function.
function operate2(operation, operand1, operand2) {
  if (typeof operators[operation] === "function")
    return operators[operation](operand1, operand2);
  else throw "unknown operator";
}
// Compute the value ("hello" + " " + "world") like this:
var j = operate2("add", "hello", operate2("add", " ", "world"));
// Using the predefined Math.pow() function:
var k = operate2("pow", 10, 2);
1.4.2.自定义函数属性
函数作为一种特殊的对象,也就是说函数可以拥有属性。
当函数需要一个“静态”变量的时候,我们就可以定义这样的函数的属性。
使用“静态”变量的好处是不会污染全局命名空间。
下面是这样做的两个例子:
// Initialize the counter property of the function object.
// Function declarations are hoisted so we really can
// do this assignment before the function declaration.
uniqueInteger.counter = 0;
// This function returns a different integer each time it is called.
// It uses a property of itself to remember the next value to be returned.
function uniqueInteger() {
  return uniqueInteger.counter++; // Increment and return counter property
}

// Compute factorials and cache results as properties of the function itself.
function factorial(n) {
  if (isFinite(n) && n>0 && n==Math.round(n)) { // Finite, positive ints only
    if (!(n in factorial)) // If no cached result
      factorial[n] = n * factorial(n-1); // Compute and cache it
    return factorial[n]; // Return the cached result
  }
  else return NaN; // If input was bad
}
factorial[1] = 1; // Initialize the cache to hold this base case.

1.5.作为命名空间的函数
现在开始进入魔术时刻
(function() { // mymodule function rewritten as an unnamed expression
  // Module code goes here.
}()); // end the function literal and invoke it now.
匿名函数的定义表达式后面立即跟上(),就是说立即调用,另外这个函数定义表达式外面还加上一对(),为什么要这么写呢?

据说是为了简化写法,他的等价写法如下:
function mymodule() {
  // Module code goes here.
  // Any variables used by the module are local to this function
  // instead of cluttering up the global namespace.
}
mymodule(); // But don't forget to invoke the function!
这样做的目的,是为了模块化,定义一个函数用做临时的命名空间,在这个命名空间内定义的变量都不会污染到全局命名空间。
下面的代码才是魔术代码,相信你同意这一点。
他的目的是当然不仅仅是为了展示命名空间技术。它定义了一个返回entend()函数的匿名函数,这就是在例6-2的时候提到的更加健壮的extend版本。
下面匿名函数里面的代码,检测了一个IE bug,如果出现了这个bug,就返回一个带补丁的函数版本,匿名函数命名空间用来隐藏了一组属性名。
另外,匿名函数立即执行,返回的实际上是一个函数对象。
Example 8-3. The extend() function, patched if necessary
// Define an extend function that copies the properties of its second and
// subsequent arguments onto its first argument.
// We work around an IE bug here: in many versions of IE, the for/in loop
// won't enumerate an enumerable property of o if the prototype of o has
// a nonenumerable property by the same name. This means that properties
// like toString are not handled correctly unless we explicitly check for them.
var extend = (function() { // Assign the return value of this function
  // First check for the presence of the bug before patching it.
  for(var p in {toString:null}) {
  // If we get here, then the for/in loop works correctly and we return
  // a simple version of the extend() function
    return function extend(o) {
      for(var i = 1; i < arguments.length; i++) {
        var source = arguments[i];
        for(var prop in source) o[prop] = source[prop];
      }
      return o;
    };
  }
// If we get here, it means that the for/in loop did not enumerate
// the toString property of the test object. So return a version
// of the extend() function that explicitly tests for the nonenumerable
// properties of Object.prototype.
  return function patched_extend(o) {
    for(var i = 1; i < arguments.length; i++) {
      var source = arguments[i];
// Copy all the enumerable properties
      for(var prop in source) o[prop] = source[prop];
// And now check the special-case properties
      for(var j = 0; j < protoprops.length; j++) {
        prop = protoprops[j];
        if (source.hasOwnProperty(prop)) o[prop] = source[prop];
      }
    }
    return o;
  };
// This is the list of special-case properties we check for
  var protoprops = ["toString", "valueOf", "constructor", "hasOwnProperty",
      "isPrototypeOf", "propertyIsEnumerable","toLocaleString"];
}());
其实这也是我现在细细学习js基础知识的原因,经常看到这种魔术代码,影响我的自信心。
我不知道js里面到底有多少潜力,或者说是有多少坑在前方潜伏。
我需要细细学习js基础知识,才敢于说自己在使用js的时候少犯错,另外看得懂别人的代码,或者说我看到的高手的代码都是这样子的。
今天终于看懂了,有收获,真高兴。
下面还有呢!!!
1.6.闭包 closures
函数对象和作用域关联,嵌套的函数对象和一个作用域链关联,变量和作用域的绑定,这种特性在计算机科学文献里面被称为闭包closures.
1.6.1.
闭包的某个强大特性:他们可以捕捉到局部变量(和参数),并一直保存下来。
作用域链描述为一个对象链表,而不是绑定的栈。
理解了闭包和垃圾回收的关系,有利于理解闭包的这个特性。

var scope = "global scope"; // A global variable
function checkscope() {
  var scope = "local scope"; // A local variable
  function f() { return scope; } // Return the value in scope here
  return f();
}
checkscope() // => "local scope"
下面这段代码,就说明了闭包的这个特性:
var scope = "global scope"; // A global variable
function checkscope() {
  var scope = "local scope"; // A local variable
  function f() { return scope; } // Return the value in scope here
  return f;
}
checkscope()() // What does this return?
这里返回的是f()这个函数对象,而不是f函数的执行结果。

返回的结果还是local scope

1.6.2.闭包的应用-私有化函数对象的“静态”属性
var uniqueInteger = (function() { // Define and invoke  定义匿名函数并且立即调用,执行得到的是一个拥有私有状态的函数对象
                       var counter = 0; // Private state of function below  函数的私有状态
                       return function() { return counter++; };
                     }());
如果你属性java的话,是不是觉得这个有点类似于java中的匿名内部类?
不是说lamda表达式类似于java中的匿名内部类吗?难道闭包也是有类似的作用?

私有变量不是只能用在一个单独的闭包内,还可以定义多个嵌套函数访问他。
function counter() {
  var n = 0;
  return {
    count: function() { return n++; },
    reset: function() { n = 0; }
  };
}
var c = counter(), d = counter(); // Create two counters
c.count() // => 0
d.count() // => 0: they count independently
c.reset() // reset() and count() methods share state
c.count() // => 0: because we reset c
d.count() // => 1: d was not reset
我们看到,每次调用counter()都会创建一个新的作用域和一个新的私有变量。这有些类似于java中的私有静态变量,但是又不完全是,因为静态变量在不同的类实例之间的共享的,也就是说他们是互相影响的。

下面是利用闭包实现的私有属性存取器方法
Example 8-4. Private property accessor methods using closures
// This function adds property accessor methods for a property with
// the specified name to the object o. The methods are named get<name>
// and set<name>. If a predicate function is supplied, the setter
// method uses it to test its argument for validity before storing it.
// If the predicate returns false, the setter method throws an exception.
//
// The unusual thing about this function is that the property value
// that is manipulated by the getter and setter methods is not stored in
// the object o. Instead, the value is stored only in a local variable
// in this function. The getter and setter methods are also defined
// locally to this function and therefore have access to this local variable.
// This means that the value is private to the two accessor methods, and it
// cannot be set or modified except through the setter method.
function addPrivateProperty(o, name, predicate) {
  var value; // This is the property value
// The getter method simply returns the value.
  o["get" + name] = function() { return value; };
// The setter method stores the value or throws an exception if
// the predicate rejects the value.
  o["set" + name] = function(v) {
    if (predicate && !predicate(v))
      throw Error("set" + name + ": invalid value " + v);
    else
      value = v;
  };
}

// The following code demonstrates the addPrivateProperty() method.
var o = {}; // Here is an empty object
// Add property accessor methods getName and setName()
// Ensure that only string values are allowed
addPrivateProperty(o, "Name", function(x) { return typeof x == "string"; });
o.setName("Frank"); // Set the property value
console.log(o.getName()); // Get the property value
o.setName(0); // Try to set a value of the wrong type
1.7.函数属性、方法和构造函数
1.7.1.函数属性:
  length
  prototype
// This function uses arguments.callee, so it won't work in strict mode.
function check(args) {
  var actual = args.length; // The actual number of arguments
  var expected = args.callee.length; // The expected number of arguments
  if (actual !== expected) // Throw an exception if they differ.
    throw Error("Expected " + expected + "args; got " + actual);
  }
function f(x, y, z) {
  check(arguments); // Check that the actual # of args matches expected #.
  return x + y + z; // Now do the rest of the function normally.
}
1.7.2.
函数方法:
   call()
   apply()
下面演示了monkey-patching,能够将指定的方法替换为一个新方法,新方法其实是包裹了原有方法,这种动态修改已有方法的做法就是monkey-patching.
// Replace the method named m of the object o with a version that logs
// messages before and after invoking the original method.
function trace(o, m) {
  var original = o[m]; // Remember original method in the closure.
  o[m] = function() { // Now define the new method.
    console.log(new Date(), "Entering:", m); // Log message.
    var result = original.apply(this, arguments); // Invoke original.
    console.log(new Date(), "Exiting:", m); // Log message.
    return result; // Return result.
  };
}
apply是call的数组版本(后面的参数是用了数组)
1.7.3.bind()方法
主要作用是将某个函数绑定到某个对象。
function f(y) { return this.x + y; } // This function needs to be bound
var o = { x : 1 }; // An object we'll bind to
var g = f.bind(o); // Calling g(x) invokes o.f(x)
g(2) // => 3

function.bind(o,args,...)
bind函数还可以绑定this,bind第二个参数后面的参数也会被用到。
var sum = function(x,y) { return x + y }; // Return the sum of 2 args
// Create a new function like sum, but with the this value bound to null 
// and the 1st argument bound to 1. This new function expects just one arg.
//创建一个新函数类似于sum,但是this的值绑定到null,
<pre name="code" class="javascript">//第一个参数x绑定到1,所以新函数只需要一个实参了(对应于sum中的形参y)
var succ = sum.bind(null, 1);
succ(2) // => 3: x is bound to 1, and we pass 2 for the y argument
function f(y,z) { return this.x + y + z }; // Another function that adds
var g = f.bind({x:1}, 2); // Bind this and y
g(3) // => 6: this.x is bound to 1, y is bound to 2 and z is 3
 1.7.4.toString()方法 
 
1.7.5.Function()构造函数
实际编程中很少使用
1.7.6.可调用的对象
 callable object
所有的函数都是可调用的,但并非所有可调用对象都是函数。
IE web(<=ie8)中的客户端方法(如Window.alert()和Document.getElementById()),使用了可调用的宿主对象,而不是内置函数对象。
本质上不是Function对象,IE9将他们实现为真正的真正的函数,因此这种可调用对象将会越来越少了。
下面这个函数可以用来检测x是否真正的函数对象。
function isFunction(x) {
  return Object.prototype.toString.call(x) === "[object Function]";
}
1.8.函数式编程
js并非lisp,haskell那样的函数式编程语言。
但是在js中可以像操控对象那样操控函数,所以可以使用函数式编程技术,比如数组方法中的map()和reduce()就是非常适合函数式编程风格。
其实已经有了函数式javascript库了。
1.8.1.使用函数处理数组
求数组元素的平均值和标准差,下面一段代码是传统的写法:
var data = [1,1,3,5,5]; // This is our array of numbers
// The mean is the sum of the elements divided by the number of elements
var total = 0;
for(var i = 0; i < data.length; i++) total += data[i];
var mean = total/data.length; // The mean of our data is 3
// To compute the standard deviation, we first sum the squares of
// the deviation of each element from the mean.
total = 0;
for(var i = 0; i < data.length; i++) {
  var deviation = data[i] - mean;
  total += deviation * deviation;
}
var stddev = Math.sqrt(total/(data.length-1)); // The standard deviation is 2

下面是使用map,reduce函数式编程风格
// First, define two simple functions
var sum = function(x,y) { return x+y; };
var square = function(x) { return x*x; };
// Then use those functions with Array methods to compute mean and stddev
var data = [1,1,3,5,5];
var mean = data.reduce(sum)/data.length;
var deviations = data.map(function(x) {return x-mean;});
var stddev = Math.sqrt(deviations.map(square).reduce(sum)/(data.length-1));
EMCASCRIPT5引入了map,reduce等等函数。下面是EMCASCRIPT3下面对这两个函数的模拟实现。
// Call the function f for each element of array a and return
// an array of the results. Use Array.prototype.map if it is defined.
var map = Array.prototype.map? function(a, f) { return a.map(f); } // Use map method if it exists
  : function(a,f) { // Otherwise, implement our own
    var results = [];
    for(var i = 0, len = a.length; i < len; i++) {
      if (i in a) results[i] = f.call(null, a[i], i, a);
    }
    return results;
  };

// Reduce the array a to a single value using the function f and
// optional initial value. Use Array.prototype.reduce if it is defined.
var reduce = Array.prototype.reduce? function(a, f, initial) { // If the reduce() method exists.如果reduce函数已经存在
      if (arguments.length > 2)
        return a.reduce(f, initial); // If an initial value was passed.如果传入一个初始值
      else return a.reduce(f); // Otherwise, no initial value.
  }
  : function(a, f, initial) { // This algorithm from the ES5 specification
      var i = 0, len = a.length, accumulator;
// Start with the specified initial value, or the first value in a
      if (arguments.length > 2) accumulator = initial;
      else { // Find the first defined index in the array
        if (len == 0) throw TypeError();
        while(i < len) {
           if (i in a) {
              accumulator = a[i++];
              break;
           }
           else i++;
        }
        if (i == len) throw TypeError();
      }
// Now call f for each remaining element in the array
      while(i < len) {
        if (i in a)
          accumulator = f.call(undefined, accumulator, a[i], i, a);
        i++;
      }
      return accumulator;
  };
//下面是使用上面定义的全局函数map和reduce来计算数组的平均值和标准差
var data = [1,1,3,5,5];
var sum = function(x,y) { return x+y; };
var square = function(x) { return x*x; };
var mean = reduce(data, sum)/data.length;
var deviations = map(data, function(x) {return x-mean;});
var stddev = Math.sqrt(reduce(map(deviations, square), sum)/(data.length-1));

1.8.2.高阶函数(higher-order function)
所谓高阶函数,就是把函数作为参数和返回值的函数。
接受一个或者多个函数作为函数的参数,并且返回一个新函数。


// This higher-order function returns a new function that passes its
// arguments to f and returns the logical negation of f's return value;
//高阶函数返回一个新函数,这个新函数把实参传递给f,将f的结果做逻辑非返回
function not(f) {
  return function() { // Return a new function
    var result = f.apply(this, arguments); // that calls f
    return !result; // and negates its result.
  };
}
var even = function(x) { // A function to determine if a number is even
  return x % 2 === 0;
};
var odd = not(even); // A new function that does the opposite
[1,1,3,5,5].every(odd); // => true: every element of the array is odd

// Return a function that expects an array argument and applies f to
// each element, returning the array of return values.
// Contrast this with the map() function from earlier.
function mapper(f) {
  return function(a) { return map(a, f); };
}
var increment = function(x) { return x+1; };
var incrementer = mapper(increment);
incrementer([1,2,3]) // => [2,3,4]
mapper和先前的map有和不同?
mapper可以将一个一般的函数映射为一个操作数组的函数。
而map本身就是操作数组的函数了。

下面是一个常见的高阶函数例子-计算f(g(...))
// Return a new function that computes f(g(...)).返回一个新函数,计算f(g(...))
// The returned function h passes all of its arguments to g, and then passes
// the return value of g to f, and then returns the return value of f.
// Both f and g are invoked with the same this value as h was invoked with.
function compose(f,g) {
  return function() {
// We use call for f because we're passing a single value and
// apply for g because we're passing an array of values.
     return f.call(this, g.apply(this, arguments));
  };
}
var square = function(x) { return x*x; };
var sum = function(x,y) { return x+y; };
var squareofsum = compose(square, sum);
squareofsum(2,3) // => 25

1.8.3.不完全函数(partial function)
partial application不完全调用
函数变换技巧:
将一个完整的函数调用拆分成多个函数调用,每次传入的都是完整实参的一部分,每次拆分开的函数叫做不完全函数,每次调用叫做不完全调用。
这种函数变换的特点是,每次不完全调用都返回一个函数,直到得到最终结果为止。
1.8.4.记忆
缓存

不完全函数和记忆估计暂时用不到细细研究,以后再说吧。
有时候不完全函数又叫做curring
http://www.cnblogs.com/rubylouvre/archive/2009/11/09/1598761.html


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值