diff --git "a/_posts/2017-09-01-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\344\270\200.md" "b/_posts/2017-09-01-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\344\270\200.md" index a7dde27..5cccd0b 100644 --- "a/_posts/2017-09-01-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\344\270\200.md" +++ "b/_posts/2017-09-01-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\344\270\200.md" @@ -7,13 +7,13 @@ tags: Ruby 元编程 ## Open Classes -  定义一个函数,去掉字符串中的标点符号和特殊字符,只保留字母、数字和空格: +定义一个函数,去掉字符串中的标点符号和特殊字符,只保留字母、数字和空格: ```ruby def to_alphanumeric(s) s.gsub /[^\w\s]/, '' end ``` -  用更加面向对象的方法,应该是打开 String 类,植入 to_alphanumeric() 方法: +用更加面向对象的方法,应该是打开 String 类,植入 to_alphanumeric() 方法: ```ruby class String def to_alphanumeric @@ -24,7 +24,7 @@ tags: Ruby 元编程 ## Inside Class Defintions -  在 Ruby 中,定义类的语句和其他语句没有本质区别,可以在类定义中放置任何语句: +在 Ruby 中,定义类的语句和其他语句没有本质区别,可以在类定义中放置任何语句: ```ruby 3.times do class C @@ -34,9 +34,9 @@ tags: Ruby 元编程 ``` >像执行其他代码一样,Ruby 执行了这些在类中定义的代码,注意这里并不是定义三个同名的类。 -  Ruby 的 **class** 关键字更像是一个作用域操作符而不是类型声明语句。它的确可以创建一个还不存在的类,不过也可以把这看成是一种副作用。对于 class 关键字,其核心任务是把你带到类的上下文中,让你可以在其中定义方法。 +Ruby 的 **class** 关键字更像是一个作用域操作符而不是类型声明语句。它的确可以创建一个还不存在的类,不过也可以把这看成是一种副作用。对于 class 关键字,其核心任务是把你带到类的上下文中,让你可以在其中定义方法。 -  我们总是可以重新打开已经存在的类并对它进行动态修改,即使是像 String 或 Array 这样标准库中的类也不例外。这种技术,被称为 **打开类** 技术。打开类技术的隐患在于,如果粗心地为某个类添加了某些方法,可能会无意中覆盖原有的某些方法,这就是 **猴子补丁** 这种说法的由来。 +我们总是可以重新打开已经存在的类并对它进行动态修改,即使是像 String 或 Array 这样标准库中的类也不例外。这种技术,被称为 **打开类** 技术。打开类技术的隐患在于,如果粗心地为某个类添加了某些方法,可能会无意中覆盖原有的某些方法,这就是 **猴子补丁** 这种说法的由来。 ## The Truth About Classes @@ -87,6 +87,6 @@ tags: Ruby 元编程 ``` >因此,一个类只不过是一个增强的 Moudle ,增加了三个方法---new, allocate 和 superclass 而已。这几个方法可以让你创建对象并可以把它们纳入到类体系架构中。除此之外,类和模块基本上是一样的。 -  正如普通的对象那样,也是通过引用来访问类,obj 和 MyClass 都是引用,唯一的区别在于,obj 是一个变量,而 MyClass 是一个常量。就像类是对象一样,类名也无非就是常量。 +正如普通的对象那样,也是通过引用来访问类,obj 和 MyClass 都是引用,唯一的区别在于,obj 是一个变量,而 MyClass 是一个常量。就像类是对象一样,类名也无非就是常量。 -  一个模块基本上就是一组实例方法,而类是一个增加了若干新功能(一个 superclass 方法和一个 new 方法)的模块。同时存在模块和类的主要原因在于 **清晰性**。通常,希望它应该在别处被包含(include)时,或者被当成命名空间时,应该选择使用模块;当希望它被实例化或者继承时,应该选择使用类。 +一个模块基本上就是一组实例方法,而类是一个增加了若干新功能(一个 superclass 方法和一个 new 方法)的模块。同时存在模块和类的主要原因在于 **清晰性**。通常,希望它应该在别处被包含(include)时,或者被当成命名空间时,应该选择使用模块;当希望它被实例化或者继承时,应该选择使用类。 diff --git "a/_posts/2017-09-02-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\344\272\214.md" "b/_posts/2017-09-02-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\344\272\214.md" index 4252ee2..7a5ab0b 100644 --- "a/_posts/2017-09-02-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\344\272\214.md" +++ "b/_posts/2017-09-02-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\344\272\214.md" @@ -37,13 +37,13 @@ tags: Ruby 元编程 ***关于 load 和 require*** -  比如在网上找到一个 motd.rb 文件用来在控制台上显示“当天的消息”, 且想把这段代码集成到最新的程序中去,那么可以使用 load 执行该文件来显示消息: load('motd.rb')。 +比如在网上找到一个 motd.rb 文件用来在控制台上显示“当天的消息”, 且想把这段代码集成到最新的程序中去,那么可以使用 load 执行该文件来显示消息: load('motd.rb')。 -  不过,使用 load 方法有一个副作用,motd.rb 文件很可能定义了变量和类。**尽管变量在加载完成后会落在当前作用域之外,但常量不会(落在当前作用域中)**。这样,motd.rb 可能会通过它的常量(尤其是类名)污染当前程序的命名空间。 +不过,使用 load 方法有一个副作用,motd.rb 文件很可能定义了变量和类。**尽管变量在加载完成后会落在当前作用域之外,但常量不会(落在当前作用域中)**。这样,motd.rb 可能会通过它的常量(尤其是类名)污染当前程序的命名空间。 -  可以通过使用第二个可选参数来强制其常量仅在自身范围内有效:load('motd', true)。通过这种方式加载的文件,Ruby 会创建一个匿名模块,使用它作为 **命名空间** 来容纳 motd.rb 中定义的所有常量,加载完成后,该模块会被销毁。 +可以通过使用第二个可选参数来强制其常量仅在自身范围内有效:load('motd', true)。通过这种方式加载的文件,Ruby 会创建一个匿名模块,使用它作为 **命名空间** 来容纳 motd.rb 中定义的所有常量,加载完成后,该模块会被销毁。 -  require 方法与 load 方法颇为类似,但是它的目的不同。通过 load 方法可以执行代码,而 require 则是用来导入类库。这就是 require 方法没有第二个可选参数的原因。在这些库中的类名通常是你导入这些类库时所希望得到的,因此没有理由在加载后销毁它们。 +require 方法与 load 方法颇为类似,但是它的目的不同。通过 load 方法可以执行代码,而 require 则是用来导入类库。这就是 require 方法没有第二个可选参数的原因。在这些库中的类名通常是你导入这些类库时所希望得到的,因此没有理由在加载后销毁它们。 ## What Happens When You Call a Method? @@ -53,15 +53,15 @@ tags: Ruby 元编程 ## Method Lookup -  为了查找一个方法,Ruby首先在 **接收者** 的类中查找,然后一层层地在祖先链中查找,直到找到这个方法为止。可以调用 ancestors 方法来获得一个类的祖先链。 +为了查找一个方法,Ruby首先在 **接收者** 的类中查找,然后一层层地在祖先链中查找,直到找到这个方法为止。可以调用 ancestors 方法来获得一个类的祖先链。 -  祖先链当中是包含模块的,当你在一个类(甚至可以是另一个模块)中包含(include)一个模块时,Ruby 会创建一个封装该模块的匿名类,并把这个匿名类插入到祖先链中,其位置正好在包含它的类的上方。 +祖先链当中是包含模块的,当你在一个类(甚至可以是另一个模块)中包含(include)一个模块时,Ruby 会创建一个封装该模块的匿名类,并把这个匿名类插入到祖先链中,其位置正好在包含它的类的上方。 -  Object 类包含了 **Kernel 模块**,因此 Kernel 就进入了每个对象的祖先链,这样在每个对象中可以随意调用 Kernel 模块的方法。如果给 Kernel 模块增加一个方法,这个 **内核方法** 就对所有对象可用,比如 print 方法。 +Object 类包含了 **Kernel 模块**,因此 Kernel 就进入了每个对象的祖先链,这样在每个对象中可以随意调用 Kernel 模块的方法。如果给 Kernel 模块增加一个方法,这个 **内核方法** 就对所有对象可用,比如 print 方法。 ## Method Execution -  比如调用一个名叫 my_method 的方法,我们先找到了这个方法,发现这个方法定义如下: +比如调用一个名叫 my_method 的方法,我们先找到了这个方法,发现这个方法定义如下: ```ruby def my_method temp = @x + 1 @@ -81,7 +81,7 @@ tags: Ruby 元编程 ``` >当开始运行 ruby 程序时,ruby 解释器会创建一个名为 main 的对象作为当前对象,这个对象有时被称为 **顶级上下文**,这个名字的由来是因为这时处在调用堆栈的顶层:这时要么没有调用任何方法,要么调用的所有方法都已经返回了。 -  self 的角色通常由最后一个接收到方法调用的对象来充当。不过,在类和模块定义中(并且在任何方法定义之外), self 的角色由这个类或模块担任: +self 的角色通常由最后一个接收到方法调用的对象来充当。不过,在类和模块定义中(并且在任何方法定义之外), self 的角色由这个类或模块担任: ```ruby class MyClass self #=> MyClass diff --git "a/_posts/2017-09-03-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\344\270\211.md" "b/_posts/2017-09-03-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\344\270\211.md" index ef677e1..fd597ad 100644 --- "a/_posts/2017-09-03-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\344\270\211.md" +++ "b/_posts/2017-09-03-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\344\270\211.md" @@ -7,7 +7,7 @@ tags: Ruby 元编程 ## What Does Private Method Really Mean? -  既然已经了解了 self,现在就可以重新审视 Ruby 的 private 关键字了。私有方法服从一个简单的规则:**不能明确指定一个接收者来调用一个私有方法**。因此,每次调用一个私有方法时,只能调用于隐含的接收者----self 上。 +既然已经了解了 self,现在就可以重新审视 Ruby 的 private 关键字了。私有方法服从一个简单的规则:**不能明确指定一个接收者来调用一个私有方法**。因此,每次调用一个私有方法时,只能调用于隐含的接收者----self 上。 ```ruby class C def public_method diff --git "a/_posts/2017-09-04-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\345\233\233.md" "b/_posts/2017-09-04-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\345\233\233.md" index 9665204..8330fc0 100644 --- "a/_posts/2017-09-04-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\345\233\233.md" +++ "b/_posts/2017-09-04-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\345\233\233.md" @@ -7,7 +7,7 @@ tags: Ruby 元编程 ## Calling Methods Dynamically -  当调用一个方法时,通常会使用点(.)标记符,代码如下: +当调用一个方法时,通常会使用点(.)标记符,代码如下: ```ruby class MyClass def my_method(my_arg) @@ -18,7 +18,7 @@ tags: Ruby 元编程 obj = MyClass.new obj.my_method(3) #=> 6 ``` -  可以使用 Object#send() 取代点标记符来调用 MyClass#my_method() 方法: +可以使用 Object#send() 取代点标记符来调用 MyClass#my_method() 方法: ```ruby obj.send(:my_method, 3) #=> 6 ``` @@ -28,19 +28,19 @@ tags: Ruby 元编程 ### 来自 Camping 的例子 -  动态派发的例子来自 Camping,这是一个极简主义的 Ruby web 框架。一个 Camping 应用程序将它的配置信息用键值对的方式存储在一个 YAML 格式的文件中。一个博客应用程序的配置文件可能像下面这样: +动态派发的例子来自 Camping,这是一个极简主义的 Ruby web 框架。一个 Camping 应用程序将它的配置信息用键值对的方式存储在一个 YAML 格式的文件中。一个博客应用程序的配置文件可能像下面这样: ```yaml admin : Bill title : Rubyland topic : Ruby and more ``` -  Camping 可以把这些键值对从文件中拷贝到自己的配置对象(这个对象的类型是 OpenStruct)中。假设你将文件的配置信息存入 conf 对象中,在理想状态下,存储配置信息的代码应该像下面这样: +Camping 可以把这些键值对从文件中拷贝到自己的配置对象(这个对象的类型是 OpenStruct)中。假设你将文件的配置信息存入 conf 对象中,在理想状态下,存储配置信息的代码应该像下面这样: ```ruby conf.admin = 'Bill' conf.title = 'Rubyland' conf.topic = 'Ruby and more' ``` -  但是 Camping 不可能事先知道特定应用中有哪些键值对,因此它无法知道应该去调用哪个方法。它只能在运行时才能根据 YAML 文件的内容发现给定的键值对。于是,Camping 求助于动态派发技术,为每个键值对都构造出一个赋值方法的名字,并且把这个方法发送给 conf 对象: +但是 Camping 不可能事先知道特定应用中有哪些键值对,因此它无法知道应该去调用哪个方法。它只能在运行时才能根据 YAML 文件的内容发现给定的键值对。于是,Camping 求助于动态派发技术,为每个键值对都构造出一个赋值方法的名字,并且把这个方法发送给 conf 对象: ```ruby if conf.rc and File.exists?(conf.rc) YAML.load_file(conf.rc).each do |k,v| @@ -51,17 +51,17 @@ tags: Ruby 元编程 ### 来自 Test::Unit 的例子: -  另外一个动态派发的例子来自 Test::Unit 标准库。Test::Unit 使用一个命名惯例来判定哪些方法是测试方法。一个 TestCase 对象会查找自己的公开方法,并选择其中名字以 test 开头的方法: +另外一个动态派发的例子来自 Test::Unit 标准库。Test::Unit 使用一个命名惯例来判定哪些方法是测试方法。一个 TestCase 对象会查找自己的公开方法,并选择其中名字以 test 开头的方法: ```ruby method_names = public_instance_methods(true) tests = method_names.delete_if {|method_name| method_name !~ /^test./} ``` -  现在这个 TestCase 对象得到了测试方法数组。后面,它会使用 send() 方法来调用数组中的每个方法。动态派发的这种特殊用法有时被称为 **模式派发**,因为它基于方法名的某种模式来过滤方法。 +现在这个 TestCase 对象得到了测试方法数组。后面,它会使用 send() 方法来调用数组中的每个方法。动态派发的这种特殊用法有时被称为 **模式派发**,因为它基于方法名的某种模式来过滤方法。 >TestCase实际使用的是 send() 方法的别名方法 `__send()__`。Object#send() 方法功能非常强大,**可以用 send() 调用任何方法,甚至调用私有方法**。 ## Defining Methods Dynamically -  可以利用 Module#define_method() 方法定义一个方法,只需要为其提供一个方法名和一个充当方法主体的块即可,代码如下: +可以利用 Module#define_method() 方法定义一个方法,只需要为其提供一个方法名和一个充当方法主体的块即可,代码如下: ```ruby class MyClass define_method :my_method do |my_arg| @@ -76,7 +76,7 @@ tags: Ruby 元编程 ## method_missing() -  在 Ruby 中,编译器并不强制方法调用时的行为,这意味着你甚至可以调用一个并不存在的方法: +在 Ruby 中,编译器并不强制方法调用时的行为,这意味着你甚至可以调用一个并不存在的方法: ```ruby class Lawyer; end @@ -85,7 +85,7 @@ tags: Ruby 元编程 ``` >当调用 talk_simple 方法时,Ruby 会到 nick 对象的类中查询它的实例方法。如果在那里找不到 talk_simple 方法,Ruby 会沿着祖先链向上搜寻进入 Object 类,并且最终来到 Kernel 模块。由于在哪里都没找到 talk_simple 方法,所以只好在 nick 上调用一个名为 method_missing 的方法,这个方法是 Kernel 的一个实例方法,而所有的对象都继承自 Kernel 模块。 -  可以直接调用 method_missing() 方法来进行实验,尽管这是一个私有方法,但是还是可以通过 send() 方法来做到: +可以直接调用 method_missing() 方法来进行实验,尽管这是一个私有方法,但是还是可以通过 send() 方法来做到: ```ruby nick.send :method_missing, :my_method diff --git "a/_posts/2017-09-05-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\344\272\224.md" "b/_posts/2017-09-05-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\344\272\224.md" index 31f1ab4..8eb57df 100644 --- "a/_posts/2017-09-05-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\344\272\224.md" +++ "b/_posts/2017-09-05-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\344\272\224.md" @@ -7,7 +7,7 @@ tags: Ruby 元编程 ## Overriding method_missing() -  你几乎不大可能亲自调用 method_missing() 方法。不过,你可以覆写它来截获无主的消息。每一个来到 method_missing() 办公桌上的消息都带着调用方法的名字,以及所有调用时传递的参数和块。 +你几乎不大可能亲自调用 method_missing() 方法。不过,你可以覆写它来截获无主的消息。每一个来到 method_missing() 办公桌上的消息都带着调用方法的名字,以及所有调用时传递的参数和块。 ```ruby class Lawyer def method_missing(method, *args) @@ -26,10 +26,10 @@ tags: Ruby 元编程 ## Ghost Methods -  当需要定义很多相似的方法时,可以通过响应 method_missing 方法来免去一些手工定义这些方法的工作。这点像是在告诉这个对象,“如果别人问你一些不理解的东西,就这样做”。 被 method_missing 方法处理的消息,从调用者的角度看,跟普通方法没什么区别,但是实际上接受者并没有相对应的方法,所以这被称为一个 **幽灵方法**。 +当需要定义很多相似的方法时,可以通过响应 method_missing 方法来免去一些手工定义这些方法的工作。这点像是在告诉这个对象,“如果别人问你一些不理解的东西,就这样做”。 被 method_missing 方法处理的消息,从调用者的角度看,跟普通方法没什么区别,但是实际上接受者并没有相对应的方法,所以这被称为一个 **幽灵方法**。 ### 来自 Ruport 的例子 -  Ruport 是一个 Ruby 报表库。你可以通过调用 Ruport::Data::Table 类来创建表格数据及把它们转换为不同的格式--比如文本格式: +Ruport 是一个 Ruby 报表库。你可以通过调用 Ruport::Data::Table 类来创建表格数据及把它们转换为不同的格式--比如文本格式: ```ruby require 'ruport' @@ -46,7 +46,7 @@ tags: Ruby 元编程 #=> | France | Chablis | #=> +--------------------+ ``` -  又比如你想仅仅选择法国红酒的数据,并把它们转换为逗号分隔的数据: +又比如你想仅仅选择法国红酒的数据,并把它们转换为逗号分隔的数据: ```ruby found = table.rows_with_country("France") found.each do |row| @@ -55,7 +55,7 @@ tags: Ruby 元编程 #=> France, Bordeaux #=> France, Chablis ``` -  你所做的只是调用了 Ruport::Data::Table 类中一个名为 rows_with_country 的方法,不过这个类的作者怎么能预见到你的数据中会有一个名为 country 的列呢?事实上,该作者对此一无所知。如果深入研究 Ruport 的代码,则会看到 rows_with_country 和 to_csv 都是幽灵方法: +你所做的只是调用了 Ruport::Data::Table 类中一个名为 rows_with_country 的方法,不过这个类的作者怎么能预见到你的数据中会有一个名为 country 的列呢?事实上,该作者对此一无所知。如果深入研究 Ruport 的代码,则会看到 rows_with_country 和 to_csv 都是幽灵方法: ```ruby class Table def method_missing(id, *arg, &block) @@ -65,10 +65,10 @@ tags: Ruby 元编程 end # ... ``` -  在这段代码中,对 rows_with_country() 的调用被转换为调用一个看起来比较传统的方法----rows_with(:country),它接受列名作为参数。同样,对 to_csv() 的调用被转换为对 as(:csv) 的调用。如果调用的方法名不是以这两种模式打头,那么 Ruport 会转而调用 Kernel#method_missing 方法,它会抛出一个 NoMethodError 错误,这就是 super 关键字所做的事情。 +在这段代码中,对 rows_with_country() 的调用被转换为调用一个看起来比较传统的方法----rows_with(:country),它接受列名作为参数。同样,对 to_csv() 的调用被转换为对 as(:csv) 的调用。如果调用的方法名不是以这两种模式打头,那么 Ruport 会转而调用 Kernel#method_missing 方法,它会抛出一个 NoMethodError 错误,这就是 super 关键字所做的事情。 ### 来自 OpenStruct 的例子 -  OpenStruct 类来自 Ruby 标准库,一个 OpenStruct 对象的属性用起来就像是 Ruby 的变量,如果想要一个新的属性,那么只需要给它赋个值就行了,然后它就奇迹般地存在了: +OpenStruct 类来自 Ruby 标准库,一个 OpenStruct 对象的属性用起来就像是 Ruby 的变量,如果想要一个新的属性,那么只需要给它赋个值就行了,然后它就奇迹般地存在了: ```ruby require 'ostruct' @@ -76,7 +76,7 @@ tags: Ruby 元编程 icecream.flavor = "strawberry" icecream.flavor #=> "strawberry" ``` -  这种工作方式背后的原因在于,一个 OpenStruct 对象的属性实际上是幽灵方法。 OpenStruct#method_missing 方法会捕捉对 flavor=() 方法的调用,再砍掉最后那个“=”以获得属性的名字,然后它把属性名和对应的值存放在一个哈希表中。当调用一个不以“=”结尾的方法时,method_missing 方法会在哈希表中查找相应的方法名,并返回对应的值。 OpenStruct 中的代码看起来有点复杂,因为它要处理一些诸如错误处理这样的特殊情况。不过,你可以很容易写一个简化版的开放结构类: +这种工作方式背后的原因在于,一个 OpenStruct 对象的属性实际上是幽灵方法。 OpenStruct#method_missing 方法会捕捉对 flavor=() 方法的调用,再砍掉最后那个“=”以获得属性的名字,然后它把属性名和对应的值存放在一个哈希表中。当调用一个不以“=”结尾的方法时,method_missing 方法会在哈希表中查找相应的方法名,并返回对应的值。 OpenStruct 中的代码看起来有点复杂,因为它要处理一些诸如错误处理这样的特殊情况。不过,你可以很容易写一个简化版的开放结构类: ```ruby class MyOpenStruct def initialize diff --git "a/_posts/2017-09-06-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\345\205\255.md" "b/_posts/2017-09-06-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\345\205\255.md" index 73f1844..78e1fce 100644 --- "a/_posts/2017-09-06-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\345\205\255.md" +++ "b/_posts/2017-09-06-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\345\205\255.md" @@ -7,7 +7,7 @@ tags: Ruby 元编程 ## Dynamic Proxies -  可以使用 Ruby 的 delegate 库来快速得到一个实用的 **动态代理**: +可以使用 Ruby 的 delegate 库来快速得到一个实用的 **动态代理**: ```ruby require 'delegate' @@ -35,7 +35,7 @@ tags: Ruby 元编程 end end ``` -  DelegateClass() 是一种拟态方法,这种方法创建并返回一个新的 Class。这个类会定义一个 method_missing() 方法,并把对它发生的调用转发到被封装的对象上,比如本例中的 Assistant 对象。Manager 类会继承这个 method_missing() 方法,因此它就成为被封装对象的一个代理。结果, Manager 就会把自己无法识别的方法转发给它封装的 Assistant: +DelegateClass() 是一种拟态方法,这种方法创建并返回一个新的 Class。这个类会定义一个 method_missing() 方法,并把对它发生的调用转发到被封装的对象上,比如本例中的 Assistant 对象。Manager 类会继承这个 method_missing() 方法,因此它就成为被封装对象的一个代理。结果, Manager 就会把自己无法识别的方法转发给它封装的 Assistant: ```ruby frank = Assistant.new("Frank") anne = Manager.new(frank) @@ -45,9 +45,9 @@ tags: Ruby 元编程 ``` ### const_missing() -  如果喜欢 Object#method_missing() 方法,则推荐关注 Module#const_missing() 方法。当引用一个不存在的常量时,Ruby 将把这个常量名作为一个符号传递给 const_missing() 方法。 +如果喜欢 Object#method_missing() 方法,则推荐关注 Module#const_missing() 方法。当引用一个不存在的常量时,Ruby 将把这个常量名作为一个符号传递给 const_missing() 方法。 -  可以在一个给定命名空间中定义 const_missing() 方法。如果在 Object 类中定义它,那么所有的对象(包括最顶层的 main 对象)都会继承这个方法: +可以在一个给定命名空间中定义 const_missing() 方法。如果在 Object 类中定义它,那么所有的对象(包括最顶层的 main 对象)都会继承这个方法: ```ruby def Object.const_missing(name) name.to_s.downcase.gsub(/_/, ' ') diff --git "a/_posts/2017-09-07-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\344\270\203.md" "b/_posts/2017-09-07-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\344\270\203.md" index 22dc9f5..6995bc7 100644 --- "a/_posts/2017-09-07-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\344\270\203.md" +++ "b/_posts/2017-09-07-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\344\270\203.md" @@ -42,7 +42,7 @@ tags: Ruby 元编程 ## Closures -  块不仅仅是一段浮动的代码,当代码运行时,它需要一个执行环境:局部变量、实例变量、self......这些东西是绑定在对象上的名字,简称为 **绑定**。块的要点在于它是完整的,它既包含代码,也包含一组绑定。 +块不仅仅是一段浮动的代码,当代码运行时,它需要一个执行环境:局部变量、实例变量、self......这些东西是绑定在对象上的名字,简称为 **绑定**。块的要点在于它是完整的,它既包含代码,也包含一组绑定。 * 当定义一个块时,**它会获取当时环境中的绑定**,并且当把它传给一个方法时,它会带着这些绑定一起进入该方法: ```ruby @@ -58,7 +58,7 @@ tags: Ruby 元编程 ## Scope -  下面的例子演示了在程序运行时作用域是怎样切换的,它会用 Kernel#local_variables() 方法来跟踪绑定的名字: +下面的例子演示了在程序运行时作用域是怎样切换的,它会用 Kernel#local_variables() 方法来跟踪绑定的名字: ```ruby v1 = 1 @@ -79,10 +79,10 @@ tags: Ruby 元编程 obj.my_method #=> [:v3] (这个 v3 跟上面那个 v3 无关) local_variables #=> [:v1, :obj] ``` -  在一些语言中,比如 Java 和 C#,有“内部作用域”的概念。在内部作用域中可以看到“外部作用域”中的变量。但 Ruby 中没有这种嵌套式的作用域,它的作用域是截然分开的:一旦进入一个新的作用域,原先的绑定就会替换为一组新的绑定。这意味着在程序进入 MyClass 后, v1 便超出作用域范围,从而就不可见了。 +在一些语言中,比如 Java 和 C#,有“内部作用域”的概念。在内部作用域中可以看到“外部作用域”中的变量。但 Ruby 中没有这种嵌套式的作用域,它的作用域是截然分开的:一旦进入一个新的作用域,原先的绑定就会替换为一组新的绑定。这意味着在程序进入 MyClass 后, v1 便超出作用域范围,从而就不可见了。 -  在定义 MyClass 的作用域中,程序定义了 v2 及一个方法。因为方法中的代码还没有被执行,所以直到类定义结束前,程序不会再打开一个新的作用域。在方法定义完成后,用 class 关键字打开的作用域会永远关闭,同时程序回到顶级作用域。 +在定义 MyClass 的作用域中,程序定义了 v2 及一个方法。因为方法中的代码还没有被执行,所以直到类定义结束前,程序不会再打开一个新的作用域。在方法定义完成后,用 class 关键字打开的作用域会永远关闭,同时程序回到顶级作用域。 -  当程序第一次进入 my_method() 方法时,它会打开一个新的作用域并定义一个局部变量 v3,接着程序退出这个方法,回到顶级作用域。当程序第二次调用 my_method() 方法时,它会打开另外一个新的作用域,并且定义另外一个新的 v3 变量。 +当程序第一次进入 my_method() 方法时,它会打开一个新的作用域并定义一个局部变量 v3,接着程序退出这个方法,回到顶级作用域。当程序第二次调用 my_method() 方法时,它会打开另外一个新的作用域,并且定义另外一个新的 v3 变量。 -  无论何时,只要程序切换了作用域,一些绑定就会被全新的绑定所取代。当然,并不是对所有的绑定都如此。如果一个对象调用同一个对象中的另外一个方法,那么实例变量在调用过程中始终存在于作用域中。尽管如此,绑定在切换作用域时往往会掉出作用域。尤其是在切换作用域时,局部变量总是会掉出的。 +无论何时,只要程序切换了作用域,一些绑定就会被全新的绑定所取代。当然,并不是对所有的绑定都如此。如果一个对象调用同一个对象中的另外一个方法,那么实例变量在调用过程中始终存在于作用域中。尽管如此,绑定在切换作用域时往往会掉出作用域。尤其是在切换作用域时,局部变量总是会掉出的。 diff --git "a/_posts/2017-09-08-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\345\205\253.md" "b/_posts/2017-09-08-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\345\205\253.md" index f8ba9a3..2899010 100644 --- "a/_posts/2017-09-08-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\345\205\253.md" +++ "b/_posts/2017-09-08-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\345\205\253.md" @@ -7,17 +7,17 @@ tags: Ruby 元编程 ## Scope Gate -  追踪作用域是一项枯燥的任务,所以,我们需要了解 **作用域门**,来更快地找到作用域。 +追踪作用域是一项枯燥的任务,所以,我们需要了解 **作用域门**,来更快地找到作用域。 -  准确地说,程序会在三个地方关闭前一个作用域,同时打开一个新的作用域: +准确地说,程序会在三个地方关闭前一个作用域,同时打开一个新的作用域: * 类定义 * 模块定义 * 方法 -  只要程序进入类或模块及方法的定义,就会发生作用域切换。这三个边界分别用 class、module、和 def 关键字作为标志,每一个关键字都充当了一个 **作用域门**。 +只要程序进入类或模块及方法的定义,就会发生作用域切换。这三个边界分别用 class、module、和 def 关键字作为标志,每一个关键字都充当了一个 **作用域门**。 ### 全局变量和顶级实例变量 -  全局变量可以在任何作用域中访问: +全局变量可以在任何作用域中访问: ```ruby def a_scope $var = "some value" @@ -30,7 +30,7 @@ tags: Ruby 元编程 a_scope another_scope #=> "some value" ``` -  全局变量的问题在于系统的任何部分都可以修改它们,因此,你会发现几乎没法追踪谁把它们改成了什么。正因为如此,基本的原则是:如非必要,尽可能少使用全局变量。有时可以用 **顶级实例变量** 来代替全局变量,它们是顶级对象 main 的实例变量: +全局变量的问题在于系统的任何部分都可以修改它们,因此,你会发现几乎没法追踪谁把它们改成了什么。正因为如此,基本的原则是:如非必要,尽可能少使用全局变量。有时可以用 **顶级实例变量** 来代替全局变量,它们是顶级对象 main 的实例变量: ```ruby @var = "The top-level @var" @@ -40,13 +40,13 @@ tags: Ruby 元编程 my_method #=> "the top-level @var" ``` -  如上所示,只要 main 对象扮演 self 的角色,就可以访问一个顶级实例变量,但当其他对象成为 self 时,顶级实例变量就退出作用域了。由于不像全局变量那么有全局性,一般认为顶级实例变量比全局变量更安全。 +如上所示,只要 main 对象扮演 self 的角色,就可以访问一个顶级实例变量,但当其他对象成为 self 时,顶级实例变量就退出作用域了。由于不像全局变量那么有全局性,一般认为顶级实例变量比全局变量更安全。 * class/module 与 def 之间还有一点微妙的差别:**在类和模块定义中的代码会被立即执行,但方法定义中的代码只有在方法被调用时才被执行**。 ## Flattening the Scope -  现在我们知道程序在哪里开始切换作用域,即在看到 class、module 或 def 关键字时开始切换作用域,但是如果希望让一个变量穿越作用域,该怎么做? +现在我们知道程序在哪里开始切换作用域,即在看到 class、module 或 def 关键字时开始切换作用域,但是如果希望让一个变量穿越作用域,该怎么做? ```ruby my_var = "Success" @@ -58,7 +58,7 @@ tags: Ruby 元编程 end end ``` -  作用域门是一道难以翻越的藩篱,在进入另一个作用域时,局部变量会立即失效。首先关于 class 这个作用域门,虽然不能让 my_var 穿越它,但是可以把 class 关键字替换为某个非作用域门的东西:方法。如果能用方法替换 class,就能在一个闭包中获得 my_var 的值,并把闭包传递给该方法。Class.new() 是 class 的完美替身。然后关于 def 这个作用域门,同样也需要用一个方法来替换这个关键字,可以用 Module#define_method() 方法来替代 def: +作用域门是一道难以翻越的藩篱,在进入另一个作用域时,局部变量会立即失效。首先关于 class 这个作用域门,虽然不能让 my_var 穿越它,但是可以把 class 关键字替换为某个非作用域门的东西:方法。如果能用方法替换 class,就能在一个闭包中获得 my_var 的值,并把闭包传递给该方法。Class.new() 是 class 的完美替身。然后关于 def 这个作用域门,同样也需要用一个方法来替换这个关键字,可以用 Module#define_method() 方法来替代 def: ```ruby my_var = "Success" @@ -75,7 +75,7 @@ tags: Ruby 元编程 #=> Success in the class definition! #=> Success in the method! ``` -  如果使用方法来替代作用域门,那么就可以让一个作用域看到另一个作用域中的变量,也就是两个作用域被挤压在一起,可以共享各自的变量,这被称为 **扁平作用域**。假定你想在一组方法之间共享一个变量,但是又不希望其他方法也能访问这个变量,就可以把这些方法定义在那个变量所在的扁平作用域中: +如果使用方法来替代作用域门,那么就可以让一个作用域看到另一个作用域中的变量,也就是两个作用域被挤压在一起,可以共享各自的变量,这被称为 **扁平作用域**。假定你想在一组方法之间共享一个变量,但是又不希望其他方法也能访问这个变量,就可以把这些方法定义在那个变量所在的扁平作用域中: ```ruby def define_methods shared = 0 diff --git "a/_posts/2017-09-09-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\344\271\235.md" "b/_posts/2017-09-09-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\344\271\235.md" index 44f79de..1f105f5 100644 --- "a/_posts/2017-09-09-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\344\271\235.md" +++ "b/_posts/2017-09-09-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\344\271\235.md" @@ -7,11 +7,11 @@ tags: Ruby 元编程 ## Scope Wrap-Up -  每个 Ruby 作用域都包含一组绑定,并且不同的作用域之间被 **作用域门** 分隔开来: **class**、**module** 和 **def**。如果要让一两个绑定穿越作用域门,那么可以用方法调用来替代作用域门:用一个闭包获取当前的绑定,并把这个闭包传递给该方法。你可以使用 Class.new() 方法代替 class,使用 Module.new() 代替 module,以及使用 Module#define_method() 代替 def,这就形成了一个 **扁平作用域**,它是闭包中的一个基本概念。如果在一个扁平作用域中定义了多个方法,则这些方法可以用一个作用域门进行保护,并共享绑定,这种技术称为 **共享作用域**。 +每个 Ruby 作用域都包含一组绑定,并且不同的作用域之间被 **作用域门** 分隔开来: **class**、**module** 和 **def**。如果要让一两个绑定穿越作用域门,那么可以用方法调用来替代作用域门:用一个闭包获取当前的绑定,并把这个闭包传递给该方法。你可以使用 Class.new() 方法代替 class,使用 Module.new() 代替 module,以及使用 Module#define_method() 代替 def,这就形成了一个 **扁平作用域**,它是闭包中的一个基本概念。如果在一个扁平作用域中定义了多个方法,则这些方法可以用一个作用域门进行保护,并共享绑定,这种技术称为 **共享作用域**。 ## instance_eval() -  Object#instance_eval() 方法可以在一个 **对象的上下文** 中执行一个块: +Object#instance_eval() 方法可以在一个 **对象的上下文** 中执行一个块: ```ruby class MyClass def initialize @@ -25,7 +25,7 @@ tags: Ruby 元编程 @v #=> 1 end ``` -  在运行时,该块的接收者会成为 self,因此它可以访问接收者的私有方法和实例变量,例如 @v。instance_eval() 方法甚至可以在不碰其他绑定的情况下修改 self 对象: +在运行时,该块的接收者会成为 self,因此它可以访问接收者的私有方法和实例变量,例如 @v。instance_eval() 方法甚至可以在不碰其他绑定的情况下修改 self 对象: ```ruby v = 2 obj.instance_eval { @v = v} @@ -34,7 +34,7 @@ tags: Ruby 元编程 * 可以把传递给 instance_eval() 方法的块称为一个 **上下文探针**,因为它就像一个深入到对象中的代码片段,对其进行操作。 ### instance_exec() -  instance_exec() 方法和instance_eval() 的功能相似,但它允许对块传入参数: +instance_exec() 方法和instance_eval() 的功能相似,但它允许对块传入参数: ```ruby class C def initialize @@ -46,7 +46,7 @@ tags: Ruby 元编程 ``` ### Clean Rooms -  有时你会创建一个对象,仅仅是为了在其中执行块,这样的对象称为 **洁净室**。 +有时你会创建一个对象,仅仅是为了在其中执行块,这样的对象称为 **洁净室**。 ```ruby class CleanRoom def complex_calculation @@ -65,24 +65,24 @@ tags: Ruby 元编程 end end ``` -  洁净室仅仅是一个用来执行块的环境,它通常还会暴露若干有用的方法供块调用。 +洁净室仅仅是一个用来执行块的环境,它通常还会暴露若干有用的方法供块调用。 ## Callable Objects -  从底层来看,使用块需要分为两步。第一步,将代码打包备用;第二步,调用块(通过 yield 语句)来执行代码。这种“先打包代码,以后调用”的机制并不是块的专利。在 Ruby 中,至少还有以下三种方法可以用来打包代码: +从底层来看,使用块需要分为两步。第一步,将代码打包备用;第二步,调用块(通过 yield 语句)来执行代码。这种“先打包代码,以后调用”的机制并不是块的专利。在 Ruby 中,至少还有以下三种方法可以用来打包代码: * 使用 proc。proc 基本上就是一个由块转换成的对象。 * 使用 lambda。它是 proc 的近亲。 * 使用方法。 ### Proc Objects -  尽管 Ruby 中绝大多数东西都是对象,但是块不是。一个 Proc 就是一个转换成对象的块,可以通过把块传给 Proc.new 方法来创建一个 Proc,以后就可以用 Proc#call() 方法来执行这个由块转换而来的对象,这种技术称为 **延迟执行**。 +尽管 Ruby 中绝大多数东西都是对象,但是块不是。一个 Proc 就是一个转换成对象的块,可以通过把块传给 Proc.new 方法来创建一个 Proc,以后就可以用 Proc#call() 方法来执行这个由块转换而来的对象,这种技术称为 **延迟执行**。 ```ruby inc = Proc.new {|x| x + 1} # ... inc.call(2) #=> 3 ``` -  Ruby 还提供了两个内核方法用于把块转换为 Proc:lambda() 和 proc()。一会就能看到 lambda() 、proc() 和 Proc.new() 这三种方式之间有一些细微的差别,但在大多数情况下,你可以随便挑一个最喜欢的方式来使用: +Ruby 还提供了两个内核方法用于把块转换为 Proc:lambda() 和 proc()。一会就能看到 lambda() 、proc() 和 Proc.new() 这三种方式之间有一些细微的差别,但在大多数情况下,你可以随便挑一个最喜欢的方式来使用: ```ruby dec = lambda {|x| x - 1} dec.class #=> Proc diff --git "a/_posts/2017-09-10-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\345\215\201.md" "b/_posts/2017-09-10-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\345\215\201.md" index 6a84588..16a203c 100644 --- "a/_posts/2017-09-10-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\345\215\201.md" +++ "b/_posts/2017-09-10-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\345\215\201.md" @@ -7,13 +7,13 @@ tags: Ruby 元编程 ## &操作符 -  块就像是方法的匿名参数,绝大多数情况下,在方法中可以通过 yield 语句直接运行一个块。但在下面两种情况中, yield 将力不从心。 +块就像是方法的匿名参数,绝大多数情况下,在方法中可以通过 yield 语句直接运行一个块。但在下面两种情况中, yield 将力不从心。 1. 想把这个块传递给另外一个方法。 2. 想把这个块转换为一个 Proc。 -  下面是把一个块传给另一个方法的例子: +下面是把一个块传给另一个方法的例子: ```ruby def math(a, b) yield(a, b) @@ -29,7 +29,7 @@ tags: Ruby 元编程 #=> Let's do the math: #=> 6 ``` -  如果想把这个块转换为一个 Proc 呢?实际上,如果在上面的代码中引用了 operation,就已经拥有了一个 Proc 对象。& 操作符的真正含义:这是一个 Proc 对象,我想把它当成一个块来使用。简单地去掉 & 操作符,就能再次得到一个 Proc 对象: +如果想把这个块转换为一个 Proc 呢?实际上,如果在上面的代码中引用了 operation,就已经拥有了一个 Proc 对象。& 操作符的真正含义:这是一个 Proc 对象,我想把它当成一个块来使用。简单地去掉 & 操作符,就能再次得到一个 Proc 对象: ```ruby def my_method(&the_proc) the_proc @@ -42,7 +42,7 @@ tags: Ruby 元编程 #=> Proc #=> Hello, Bill! ``` -  可以再次使用 & 操作符把 Proc 转换为块: +可以再次使用 & 操作符把 Proc 转换为块: ```ruby def my_method(greeting) puts "#{greeting}, #{yield}!" @@ -57,7 +57,7 @@ tags: Ruby 元编程 ## Procs vs. Lambdas -  在 proc 和 lambda 之间有两个重要的区别。第一个区别与 return 关键字相关,另一个区别则与参数检验相关。 +在 proc 和 lambda 之间有两个重要的区别。第一个区别与 return 关键字相关,另一个区别则与参数检验相关。 * 在 lambda 中, return 仅仅表示 **从这个 lambda 中返回**: ```ruby @@ -80,16 +80,16 @@ tags: Ruby 元编程 another_double #=> 10 ``` -  prco 和 lambda 第二个区别来自它们检查参数的方式。**lambda 的适应能力比 proc 差**,如果调用 lambda 时的参数数量不对,则它会失败,同时会抛出一个 ArgumentError 错误。而 proc 则会把传递进来的参数调整为自己期望的参数形式:如果参数比期望的要多,那么 proc 会忽略多余的参数,如果参数数量不足,那么对未指定的参数, proc 会赋予一个 nil 值。 +prco 和 lambda 第二个区别来自它们检查参数的方式。**lambda 的适应能力比 proc 差**,如果调用 lambda 时的参数数量不对,则它会失败,同时会抛出一个 ArgumentError 错误。而 proc 则会把传递进来的参数调整为自己期望的参数形式:如果参数比期望的要多,那么 proc 会忽略多余的参数,如果参数数量不足,那么对未指定的参数, proc 会赋予一个 nil 值。 * 整体而言,lambda 更直观,因为它更像是一个方法。它不仅对参数数量要求严格,而且在调用 return 时只从代码中返回。 ### 简洁 lambda -  简洁 lambda 操作符: +简洁 lambda 操作符: ```ruby p = ->(x) {x + 1} ``` -  上面的代码与下面的代码作用相同: +上面的代码与下面的代码作用相同: ```ruby p = lambda {|x| x + 1} ``` diff --git "a/_posts/2017-09-11-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\345\215\201\344\270\200.md" "b/_posts/2017-09-11-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\345\215\201\344\270\200.md" index 270a645..77e232a 100644 --- "a/_posts/2017-09-11-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\345\215\201\344\270\200.md" +++ "b/_posts/2017-09-11-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\345\215\201\344\270\200.md" @@ -7,7 +7,7 @@ tags: Ruby 元编程 ## Methods Revisited -  最后一个可调用对象是 **方法**,它跟 lambda 一样不过是可调用的对象而已: +最后一个可调用对象是 **方法**,它跟 lambda 一样不过是可调用的对象而已: ```ruby class MyClass def initialize(value) @@ -23,9 +23,9 @@ tags: Ruby 元编程 m = object.method :my_method m.call #=> 1 ``` -  通过调用 Object#method() 方法可以获得一个用 Method 对象表示的方法,以后可以用 Method#call() 对它进行调用。Method 对象类似于 lambda,但有一个重要的区别: lambda 在定义它的作用域中执行(它是一个闭包),而 Method 对象会在它自身所在对象的作用域中执行。 +通过调用 Object#method() 方法可以获得一个用 Method 对象表示的方法,以后可以用 Method#call() 对它进行调用。Method 对象类似于 lambda,但有一个重要的区别: lambda 在定义它的作用域中执行(它是一个闭包),而 Method 对象会在它自身所在对象的作用域中执行。 -  可以用 Method#unbind() 方法把一个方法跟它所绑定的对象相分离,该方法再返回一个 UnboundMethod 对象。UnboundMethod 对象不能被执行,但能被绑定到一个对象上,使之再次成为一个 Method 对象。 +可以用 Method#unbind() 方法把一个方法跟它所绑定的对象相分离,该方法再返回一个 UnboundMethod 对象。UnboundMethod 对象不能被执行,但能被绑定到一个对象上,使之再次成为一个 Method 对象。 ```ruby unbound = m.unbind another_object = MyClass.new(2) @@ -34,11 +34,11 @@ tags: Ruby 元编程 ``` >这种技术只有在 another_object 与原先这个方法的对象属于同一个类时才起作用,否则会得到一个异常。 -  可以调用 Method#to_proc() 方法把 Method 对象转换为 Proc 对象,也可以使用 define_method() 方法把块转换为方法(我们之前用过)。 +可以调用 Method#to_proc() 方法把 Method 对象转换为 Proc 对象,也可以使用 define_method() 方法把块转换为方法(我们之前用过)。 ## Callable Objects Wrap-Up -  可调用对象是可以执行的代码片段,而且它们有自己的作用域。可调用对象可以有以下几种方式: +可调用对象是可以执行的代码片段,而且它们有自己的作用域。可调用对象可以有以下几种方式: * **块**(虽然它们不是真正的对象,但是它们是可调用的):在定义它们的作用域中执行。 @@ -48,6 +48,6 @@ tags: Ruby 元编程 * **方法**:绑定于对象,在所绑定对象的作用域中执行。它们也可以与这个作用域解除绑定,再重新绑定到另一个对象的作用域上。 -  不同种类的可调用对象有细微的区别。在方法和 lambda 中,return 语句从可调用对象中返回。在块和 proc 中,return 语句从定义可调用对象的原始上下文中返回。另外,不同的可调用对象对传入参数数目不符合有不同的反应。其中方法的处理方式最严格, lambda 同样严格,而 proc 和块则要宽松一些。 +不同种类的可调用对象有细微的区别。在方法和 lambda 中,return 语句从可调用对象中返回。在块和 proc 中,return 语句从定义可调用对象的原始上下文中返回。另外,不同的可调用对象对传入参数数目不符合有不同的反应。其中方法的处理方式最严格, lambda 同样严格,而 proc 和块则要宽松一些。 -  尽管有这些区别,还是可以将一种可调用对象转换为另外一种可调用对象的,实现这样功能的方法包括 Proc.new() 方法、Method#to_proc() 方法和 & 操作符。 +尽管有这些区别,还是可以将一种可调用对象转换为另外一种可调用对象的,实现这样功能的方法包括 Proc.new() 方法、Method#to_proc() 方法和 & 操作符。 diff --git "a/_posts/2017-09-12-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\345\215\201\344\272\214.md" "b/_posts/2017-09-12-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\345\215\201\344\272\214.md" index fb45882..514d6c3 100644 --- "a/_posts/2017-09-12-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\345\215\201\344\272\214.md" +++ "b/_posts/2017-09-12-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\345\215\201\344\272\214.md" @@ -7,7 +7,7 @@ tags: Ruby 元编程 ## Class Defintions Demystified -  跟其他的面向对象的编程语言不同,在 Ruby 中,当使用 class 关键字时,并非是在指定对象未来的行为方式,相反,实际上是在运行普通的代码。事实上,可以在类定义中放入任何代码。而且要牢记,类只是一个增强的模块,因此我们在此学到的任何东西都可以应用于模块。当看到关于“类定义”内容的时候,也可以把它认为是“模块定义”。 +跟其他的面向对象的编程语言不同,在 Ruby 中,当使用 class 关键字时,并非是在指定对象未来的行为方式,相反,实际上是在运行普通的代码。事实上,可以在类定义中放入任何代码。而且要牢记,类只是一个增强的模块,因此我们在此学到的任何东西都可以应用于模块。当看到关于“类定义”内容的时候,也可以把它认为是“模块定义”。 ```ruby class MyClass puts 'Hello!' @@ -17,17 +17,17 @@ tags: Ruby 元编程 >跟方法和块一样,类定义也会返回最后一条语句的值。 ### The Current Class -  不管处在 Ruby 程序的哪个位置,总存在一个 当前对象:self。类似的,也总是有一个 **当前类** 或模块存在。当定义一个方法时,该方法将成为当前类的一个实例方法。 +不管处在 Ruby 程序的哪个位置,总存在一个 当前对象:self。类似的,也总是有一个 **当前类** 或模块存在。当定义一个方法时,该方法将成为当前类的一个实例方法。 -  尽管可以用 self 获得当前对象,但 Ruby 并没有相应的方式来获得当前类的引用。然而,跟踪当前类并不难,每当通过 class 关键字来打开一个类,这个类就成为当前类。 +尽管可以用 self 获得当前对象,但 Ruby 并没有相应的方式来获得当前类的引用。然而,跟踪当前类并不难,每当通过 class 关键字来打开一个类,这个类就成为当前类。 -  class 关键字有一个限制:它需要一个类的名字。但是在某些情况下,可能我们并不知道要打开的类的名字。设想一个以类为参数的方法,它给这个类添加了一个新的实例方法: +class 关键字有一个限制:它需要一个类的名字。但是在某些情况下,可能我们并不知道要打开的类的名字。设想一个以类为参数的方法,它给这个类添加了一个新的实例方法: ```ruby def add_method_to(a_class) # TODO: 在 a_class 上定义方法 m() end ``` -  这时,我们需要一种新的方式,它 **不需要使用 class 关键字就能修改当前类**,它就是 **class_eval()**。 +这时,我们需要一种新的方式,它 **不需要使用 class 关键字就能修改当前类**,它就是 **class_eval()**。 * Module#class_eval() 方法(或者它的别名 module_eval())会在一个已存在类的上下文中执行一个块: ```ruby @@ -46,7 +46,7 @@ tags: Ruby 元编程 * Module#class_eval() 实际上比 class 关键字更加灵活,它可以对任何代表类的变量使用 class_eval() 方法,而 class 只能使用常量。另外,class 会打开一个新的作用域,这样将丧失当前绑定的可见性,而 class_eval() 方法则使用 **扁平作用域**,这意味着可以引用 class_eval() 块外部作用域中的变量。 ### 当前类及其特殊情况 -  Ruby 解释器总是会追踪当前类: +Ruby 解释器总是会追踪当前类: ```ruby class MyClass def method_one @@ -58,7 +58,7 @@ tags: Ruby 元编程 obj.method_one obj.method_two #=> "Hello!" ``` -  在这里 method_two() 方法属于哪个类?或者说,在定义 method_two() 方法时,谁是当前对象?这种情况下,当前类不可能时 self,因为 self 根本不是类,实际上,这时当前类的角色由 self 的类来充当----MyClass。**同样的原则还可以应用于处于顶级作用域的时候**。这时,当前类是 Object----main 对象的类,这也就是为什么当在顶级作用域中定义方法的时候,这个方法会成为 Object 类实例方法的原因。 +在这里 method_two() 方法属于哪个类?或者说,在定义 method_two() 方法时,谁是当前对象?这种情况下,当前类不可能时 self,因为 self 根本不是类,实际上,这时当前类的角色由 self 的类来充当----MyClass。**同样的原则还可以应用于处于顶级作用域的时候**。这时,当前类是 Object----main 对象的类,这也就是为什么当在顶级作用域中定义方法的时候,这个方法会成为 Object 类实例方法的原因。 ## Current Class Wrap-up @@ -68,13 +68,13 @@ tags: Ruby 元编程 ## Class Instance Variables -  Ruby 解释器假定所有的实例变量都属于当前对象 self,当定义类时也是如此: +Ruby 解释器假定所有的实例变量都属于当前对象 self,当定义类时也是如此: ```ruby class MyClass @my_var = 1 end ``` -  在类定义的时候, self 的角色由类本身担任,因此实例变量 @my_var 属于这个类,注意 **类的实例变量不同于类的对象的实例变量**。 +在类定义的时候, self 的角色由类本身担任,因此实例变量 @my_var 属于这个类,注意 **类的实例变量不同于类的对象的实例变量**。 ```ruby class MyClass @my_var = 1 @@ -89,6 +89,6 @@ tags: Ruby 元编程 obj.read #=> 2 MyClass.read #=> 1 ``` -  上面的代码定义了两个实例变量,它们正好都叫 @my_var,但是它们分属于不同的作用域,并属于不同的对象。要牢记 **类也是对象**,而且需要自己在程序中追踪 self。其中一个 @my_var 变量定义于 obj 充当 self 的时刻,因此它是 obj 对象的实例变量。另外一个 @my_var 变量定义于 MyClass 充当 self 的时刻,因此它是 MyClass 的实例变量----也就是 **类实例变量**。 +上面的代码定义了两个实例变量,它们正好都叫 @my_var,但是它们分属于不同的作用域,并属于不同的对象。要牢记 **类也是对象**,而且需要自己在程序中追踪 self。其中一个 @my_var 变量定义于 obj 充当 self 的时刻,因此它是 obj 对象的实例变量。另外一个 @my_var 变量定义于 MyClass 充当 self 的时刻,因此它是 MyClass 的实例变量----也就是 **类实例变量**。 -  类实例变量是属于 Class 类对象的普通实例变量,类实例变量仅仅可以被类本身所访问,而不能被类的实例或子类所访问。 +类实例变量是属于 Class 类对象的普通实例变量,类实例变量仅仅可以被类本身所访问,而不能被类的实例或子类所访问。 diff --git "a/_posts/2017-09-13-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\345\215\201\344\270\211.md" "b/_posts/2017-09-13-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\345\215\201\344\270\211.md" index 5d93d82..aab795b 100644 --- "a/_posts/2017-09-13-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\345\215\201\344\270\211.md" +++ "b/_posts/2017-09-13-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\345\215\201\344\270\211.md" @@ -7,7 +7,7 @@ tags: Ruby 元编程 ## Class Variables -  如果希望在类中存储一个变量,那么除了 **类实例变量**,还可以使用以 @@ 开头的 **类变量**。类变量与类实例变量不同,它们可以被子类或类的实例所使用: +如果希望在类中存储一个变量,那么除了 **类实例变量**,还可以使用以 @@ 开头的 **类变量**。类变量与类实例变量不同,它们可以被子类或类的实例所使用: ```ruby class C @@v = 1 @@ -20,7 +20,7 @@ tags: Ruby 元编程 D.new.my_method #=> 1 ``` -  所有的类都是是 Class 类的实例,所以可以通过调用 Class.new() 方法来创建类。 Class.new() 方法还可以接受一个参数(所建新类的超类)以及一个块: +所有的类都是是 Class 类的实例,所以可以通过调用 Class.new() 方法来创建类。 Class.new() 方法还可以接受一个参数(所建新类的超类)以及一个块: ```ruby c = Class.new(Array) do def my_method @@ -28,18 +28,18 @@ tags: Ruby 元编程 end end ``` -  现在虽然有了一个引用类的变量,但是这个类还是匿名的。**类名不过是一个常量而已**,因此可以来给它赋值: +现在虽然有了一个引用类的变量,但是这个类还是匿名的。**类名不过是一个常量而已**,因此可以来给它赋值: ```ruby MyClass = c ``` -  这样,该常量就表示这个 class,同时这个 class 也表示这个常量: +这样,该常量就表示这个 class,同时这个 class 也表示这个常量: ```ruby c.name #=> "MyClass" ``` ## Singleton Methods -  Ruby 允许给单个对象增加一个方法。 +Ruby 允许给单个对象增加一个方法。 ```ruby str = "just a regular string" @@ -51,21 +51,21 @@ tags: Ruby 元编程 str.methods.grep(/title?/) #=> ["title?"] str.singleton_methods #=> ["title?"] ``` -  上面的代码为 str 添加了一个 title?() 方法,其他对象则不会得到这个方法----即使是其他的 String 对象。像这样只针对单个对象生效的方法,称为 **单件方法**。 +上面的代码为 str 添加了一个 title?() 方法,其他对象则不会得到这个方法----即使是其他的 String 对象。像这样只针对单个对象生效的方法,称为 **单件方法**。 ## The Truth About Class Methods -  类只是对象,而类名只是常量,在类上调用方法就跟在对象上调用方法一样: +类只是对象,而类名只是常量,在类上调用方法就跟在对象上调用方法一样: ```ruby an_object.a_method AClass.a_class_method ``` -  **类方法的实质就是----它们是一个类的单件方法**。如果比较单件方法的定义和类方法的定义,会发现它们是一样的: +**类方法的实质就是----它们是一个类的单件方法**。如果比较单件方法的定义和类方法的定义,会发现它们是一样的: ```ruby def obj.a_singleton_method;end def MyClass.another_class_method;end ``` -  如果在类定义中写入代码,由于这时 self 就是类本身,所以还可以利用这一点用 self 替换类名来定义类方法: +如果在类定义中写入代码,由于这时 self 就是类本身,所以还可以利用这一点用 self 替换类名来定义类方法: ```ruby class MyClass def self.yet_another_class_method;end @@ -74,7 +74,7 @@ tags: Ruby 元编程 ## Class Macros -  Ruby 对象并没有属性,如果希望有一些像属性的东西,就得定义两个 **拟态方法** ----一个读方法和一个写方法: +Ruby 对象并没有属性,如果希望有一些像属性的东西,就得定义两个 **拟态方法** ----一个读方法和一个写方法: ```ruby class MyClass def my_attribute=(value) @@ -89,10 +89,10 @@ tags: Ruby 元编程 obj.my_attribute = 'x' obj.my_attribute #=> "x" ``` -  写这样的方法很快就让人感到枯燥,其实你可以通过使用 Module#attr_*() 方法家族中的一员来定义访问器。Module#attr_reader() 可以生成一个读方法, Module#attr_writer() 可以生成一个写方法,而 Module#attr_accessor() 则可以同时生成两者: +写这样的方法很快就让人感到枯燥,其实你可以通过使用 Module#attr_*() 方法家族中的一员来定义访问器。Module#attr_reader() 可以生成一个读方法, Module#attr_writer() 可以生成一个写方法,而 Module#attr_accessor() 则可以同时生成两者: ```ruby class MyClass attr_accessor :my_attribute end ``` -  **所有的 attr*() 方法都定义在 Module 类中,因此不管 self 是类还是模块,都可以使用它们**。像 attr_accessor() 这样的方法被称为 **类宏**。虽然类宏看起来很像关键字,但是它们只是普通的方法,只是可以用在类定义中而已。 +**所有的 attr*() 方法都定义在 Module 类中,因此不管 self 是类还是模块,都可以使用它们**。像 attr_accessor() 这样的方法被称为 **类宏**。虽然类宏看起来很像关键字,但是它们只是普通的方法,只是可以用在类定义中而已。 diff --git "a/_posts/2017-09-14-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\345\215\201\345\233\233.md" "b/_posts/2017-09-14-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\345\215\201\345\233\233.md" index 3f9a075..8fe1930 100644 --- "a/_posts/2017-09-14-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\345\215\201\345\233\233.md" +++ "b/_posts/2017-09-14-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\345\215\201\345\233\233.md" @@ -7,7 +7,7 @@ tags: Ruby 元编程 ## Use Macros -  **可以使用类宏来声明一些已被弃用的旧方法名**: +**可以使用类宏来声明一些已被弃用的旧方法名**: ```ruby class Book def title # ... @@ -39,7 +39,7 @@ tags: Ruby 元编程 ## Eigenclass -  在之前我们学习了 Ruby 是怎样查找方法的: +在之前我们学习了 Ruby 是怎样查找方法的: ```ruby class MyClass def my_method; end @@ -48,26 +48,26 @@ tags: Ruby 元编程 obj = MyClass.new obj.my_method ``` -  在调用 my_method() 方法时, Ruby 直接向右一步进入 MyClass 类,并在那里找到了这个方法。现在,如果在 obj 上定义一个 **单件方法**: +在调用 my_method() 方法时, Ruby 直接向右一步进入 MyClass 类,并在那里找到了这个方法。现在,如果在 obj 上定义一个 **单件方法**: ```ruby def obj.my_singleton_method; end ``` -  单件方法不能存在于 obj 中,因为 obj 不是一个类,但是它也不能存在于 MyClass 中,否则所有 MyClass 的实例都能共享这个方法。当然,它也不能存在于 MyClass 的超类 Object 中。那么,单件方法究竟在什么地方呢? +单件方法不能存在于 obj 中,因为 obj 不是一个类,但是它也不能存在于 MyClass 中,否则所有 MyClass 的实例都能共享这个方法。当然,它也不能存在于 MyClass 的超类 Object 中。那么,单件方法究竟在什么地方呢? -  类方法是单件方法的特例----而且同样令人困惑: +类方法是单件方法的特例----而且同样令人困惑: ```ruby def MyClass.my_class_method; end ``` ## Eigenclasses Revealed -  当向一个对象索要它的类时,Ruby 并没有告诉你全部的真相,而是还有一个对象特有的隐藏类,这个类称为该对象的 **eigenclass**。像 Object#class() 这样的方法虽然会把 eigenclass 隐藏起来,但是可以采用迂回战术解决这个问题。**Ruby 有一种特殊的基于 class 关键字的语法,它可以让你进入该 eigenclass 的作用域**: +当向一个对象索要它的类时,Ruby 并没有告诉你全部的真相,而是还有一个对象特有的隐藏类,这个类称为该对象的 **eigenclass**。像 Object#class() 这样的方法虽然会把 eigenclass 隐藏起来,但是可以采用迂回战术解决这个问题。**Ruby 有一种特殊的基于 class 关键字的语法,它可以让你进入该 eigenclass 的作用域**: ```ruby class << an_object # ... end ``` -  如果想获得这个 eigenclass 的引用,则可以在离开该作用域时返回 self: +如果想获得这个 eigenclass 的引用,则可以在离开该作用域时返回 self: ```ruby obj = Object.new eigenclass = class << obj @@ -76,7 +76,7 @@ tags: Ruby 元编程 eigenclass.class #=> Class ``` -  eigenclass 是一个类----而且是很特殊的类。每个 eigenclass 只有一个实例(这就是它们也称为单件类的原因),并且不能被继承。更重要的是, **eigenclass 是一个对象的单件方法的存活之所**: +eigenclass 是一个类----而且是很特殊的类。每个 eigenclass 只有一个实例(这就是它们也称为单件类的原因),并且不能被继承。更重要的是, **eigenclass 是一个对象的单件方法的存活之所**: ```ruby def obj.my_singleton_method; end eigenclass.instance_methods.grep(/my/) #=> ["my_singleton_method"] diff --git "a/_posts/2017-09-15-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\345\215\201\344\272\224.md" "b/_posts/2017-09-15-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\345\215\201\344\272\224.md" index 40efd42..ecd3880 100644 --- "a/_posts/2017-09-15-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\345\215\201\344\272\224.md" +++ "b/_posts/2017-09-15-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\345\215\201\344\272\224.md" @@ -7,7 +7,7 @@ tags: Ruby 元编程 ## Eigenclass Practice -  前面我们说过 attr_accessor() 方法,它能为任何 **对象** 创建属性: +前面我们说过 attr_accessor() 方法,它能为任何 **对象** 创建属性: ```ruby class MyClass attr_accessor :a @@ -17,7 +17,7 @@ tags: Ruby 元编程 obj.a = 2 obj.a #=> 2 ``` -  但是,如果想给类创建属性呢?你可能会试图重新打开 Class 并且在那里定义属性: +但是,如果想给类创建属性呢?你可能会试图重新打开 Class 并且在那里定义属性: ```ruby class MyClass;end @@ -28,7 +28,7 @@ tags: Ruby 元编程 MyClass.b = 42 MyClass.b #=> 42 ``` -  但这样做会给所有的类都添加这个属性。如果希望添加专属于 MyClass 的属性,就需要使用另一种技术----在它的 eigenclass 中定义该属性: +但这样做会给所有的类都添加这个属性。如果希望添加专属于 MyClass 的属性,就需要使用另一种技术----在它的 eigenclass 中定义该属性: ```ruby class MyClass class << self @@ -41,7 +41,7 @@ tags: Ruby 元编程 ``` >一个属性实际上只是一对方法,如果在 eigenclass 中定义这些方法,则它们实际上会成为类方法。 -  当类包含模块时,能够扩展该类的实例方法。如果想扩展该类的类方法,则应该在它的 eigenclass 中包含模块: +当类包含模块时,能够扩展该类的实例方法。如果想扩展该类的类方法,则应该在它的 eigenclass 中包含模块: ```ruby module MyModule def my_method; 'hello'; end @@ -57,7 +57,7 @@ tags: Ruby 元编程 ``` >my_method() 方法是 MyClass 的 eigenclass 的一个实例方法,这样,my_method() 也是 MyClass 的一个类方法。这种技术称为 **类扩展**。 -  类方法其实是单件方法的特例,因此可以把这种技巧推广到任意对象。一般情况下,这种技术称为 **对象扩展**: +类方法其实是单件方法的特例,因此可以把这种技巧推广到任意对象。一般情况下,这种技术称为 **对象扩展**: ```ruby module MyModule def my_method; 'hello'; end @@ -72,7 +72,7 @@ tags: Ruby 元编程 obj.singleton_methods #=> [:my_method] ``` -  **类扩展** 和 **对象扩展** 的应用非常普遍,因此 Ruby 为它们专门提供了一个叫做 Object#extend() 的方法: +**类扩展** 和 **对象扩展** 的应用非常普遍,因此 Ruby 为它们专门提供了一个叫做 Object#extend() 的方法: ```ruby module MyModule def my_method; 'hello'; end diff --git "a/_posts/2017-09-16-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\345\215\201\345\205\255.md" "b/_posts/2017-09-16-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\345\215\201\345\205\255.md" index fbac682..13d887b 100644 --- "a/_posts/2017-09-16-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\345\215\201\345\205\255.md" +++ "b/_posts/2017-09-16-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\345\215\201\345\205\255.md" @@ -7,7 +7,7 @@ tags: Ruby 元编程 ## Aliases -  通过使用 alias 关键字,可以给 Ruby 方法取一个别名: +通过使用 alias 关键字,可以给 Ruby 方法取一个别名: ```ruby class MyClass def my_method; 'my_method()'; end @@ -19,24 +19,24 @@ tags: Ruby 元编程 obj.m #=> "my_method()" ``` -  注意 alias 是一个关键字,而非一个方法,这就是为什么两个方法名之间没有逗号的原因。Ruby 还提供了 Module#alias_method() 方法,它的功能与 alias 关键字相同。 +注意 alias 是一个关键字,而非一个方法,这就是为什么两个方法名之间没有逗号的原因。Ruby 还提供了 Module#alias_method() 方法,它的功能与 alias 关键字相同。 -  如果先给方法命名一个别名,然后 **重定义它**,则会把当前存在的这个方法名字跟定义的新方法绑定起来。只要老的方法还存在绑定的名字,**仍旧可以调用它**。 +如果先给方法命名一个别名,然后 **重定义它**,则会把当前存在的这个方法名字跟定义的新方法绑定起来。只要老的方法还存在绑定的名字,**仍旧可以调用它**。 -  如果新定义的方法会调用老方法,这种技巧被称为 **环绕别名**。可以通过三个步骤来使用环绕别名: +如果新定义的方法会调用老方法,这种技巧被称为 **环绕别名**。可以通过三个步骤来使用环绕别名: 1. 给方法定义一个别名。 2. 重定义这个方法。 3. 在新的方法中调用老的方法。 -  环绕别名有两个潜在的陷阱,尽管它们在实际编程中都不太常见: +环绕别名有两个潜在的陷阱,尽管它们在实际编程中都不太常见: * 首先,环绕别名是一种猴子补丁,因此,你可能会破坏已有代码。 * 第二个潜在问题与加载有关,你永远不该把一个环绕别名加载两次,除非你想在调用这个方法时期望程序崩溃。 -  Ruby 操作符实际上都是拟态方法,例如,整数的 + 操作符只是名为 Integer#+() 方法的语法糖而已。当写 1 + 1 时,解析器会把它转换为 1.+(1)。 +Ruby 操作符实际上都是拟态方法,例如,整数的 + 操作符只是名为 Integer#+() 方法的语法糖而已。当写 1 + 1 时,解析器会把它转换为 1.+(1)。 -  可以使用打开类技术,重定义 Integer#+() 方法: +可以使用打开类技术,重定义 Integer#+() 方法: ```ruby class Integer alias :old_plus :+ diff --git "a/_posts/2017-09-17-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\345\215\201\344\270\203.md" "b/_posts/2017-09-17-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\345\215\201\344\270\203.md" index fc0a00b..ea2f6b3 100644 --- "a/_posts/2017-09-17-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\345\215\201\344\270\203.md" +++ "b/_posts/2017-09-17-Ruby\345\205\203\347\274\226\347\250\213\350\257\273\344\271\246\347\254\224\350\256\260\345\215\201\344\270\203.md" @@ -7,7 +7,7 @@ tags: Ruby 元编程 ## Kernel#eval -  前面我们了解了 instance_eval() 方法和 class_eval() 方法,现在我们看看 eval() 方法(它也是一个内核方法)。从某种意义上说,Kernel#eval() 方法是这三个中最直接的,它不是使用块,而是直接使用包含 Ruby 代码的字符串----简称 **代码字符串**。Kernel#eval() 方法会执行字符串中的代码,并返回执行结果: +前面我们了解了 instance_eval() 方法和 class_eval() 方法,现在我们看看 eval() 方法(它也是一个内核方法)。从某种意义上说,Kernel#eval() 方法是这三个中最直接的,它不是使用块,而是直接使用包含 Ruby 代码的字符串----简称 **代码字符串**。Kernel#eval() 方法会执行字符串中的代码,并返回执行结果: ```ruby array = [10, 20] element = 30 @@ -15,7 +15,7 @@ tags: Ruby 元编程 ``` ### Strings of Code vs. Blocks -  instance_eval() 方法和 class_eval() 方法除了块,其实也可以接受代码字符串作为参数。字符串中的代码与块中的代码并没有太大的区别,代码字符串甚至可以像块那样访问局部变量: +instance_eval() 方法和 class_eval() 方法除了块,其实也可以接受代码字符串作为参数。字符串中的代码与块中的代码并没有太大的区别,代码字符串甚至可以像块那样访问局部变量: ```ruby array = ['a', 'b', 'c'] x = 'd' @@ -24,4 +24,4 @@ tags: Ruby 元编程 array #=> ["a", "d", "c"] ``` -  由于代码字符串和块非常相似,因此,在很多情况下,可以选择使用任意一种。但是只要能用块就尽量用块,因为代码字符串有一些缺点。 +由于代码字符串和块非常相似,因此,在很多情况下,可以选择使用任意一种。但是只要能用块就尽量用块,因为代码字符串有一些缺点。 diff --git "a/_posts/2017-10-01-JavaScript\345\237\272\346\234\254\346\225\260\346\215\256\347\261\273\345\236\213.md" "b/_posts/2017-10-01-JavaScript\345\237\272\346\234\254\346\225\260\346\215\256\347\261\273\345\236\213.md" index a348bda..03a4498 100644 --- "a/_posts/2017-10-01-JavaScript\345\237\272\346\234\254\346\225\260\346\215\256\347\261\273\345\236\213.md" +++ "b/_posts/2017-10-01-JavaScript\345\237\272\346\234\254\346\225\260\346\215\256\347\261\273\345\236\213.md" @@ -6,7 +6,7 @@ tags: JavaScript --- ## Number -  JavaScript **不区分整数和浮点数**,统一用 Number 表示,以下都是合法的 Number: +JavaScript **不区分整数和浮点数**,统一用 Number 表示,以下都是合法的 Number: ```javascript 1.2345e3; // 科学计数法表示1.2345x1000,等同于1234.5 NaN; // 表示Not a Number,当无法计算结果时用NaN表示 @@ -14,7 +14,7 @@ tags: JavaScript ``` ## 字符串 -  字符串是以单引号或双引号括起来的任意文本。ES6 新增一种多行字符串的表示方法,用 **反引号** 来表示。同时还新增一种嵌入式字符串,也是使用反引号,其中 ${} 内的变量会被替换(类似于ruby的 #{})。注意 **JavaScript 字符串是不可变的**,如果对字符串的某个索引赋值,不会有任何错误,但是也没有任何效果。 +字符串是以单引号或双引号括起来的任意文本。ES6 新增一种多行字符串的表示方法,用 **反引号** 来表示。同时还新增一种嵌入式字符串,也是使用反引号,其中 ${} 内的变量会被替换(类似于ruby的 #{})。注意 **JavaScript 字符串是不可变的**,如果对字符串的某个索引赋值,不会有任何错误,但是也没有任何效果。 ```javascript var s = 'Hello, world'; s.length; // 12 @@ -29,13 +29,13 @@ tags: JavaScript ``` ## 布尔值 -  一个布尔值只有 true、false 两种值。 +一个布尔值只有 true、false 两种值。 * JavaScript 的相等运算符 == 会自动转换数据类型再比较是否相等,而 === 不会先转换,所以要坚持使用 === 。 * NaN 这个特殊的 Number 与所有其他值都不相等包括它自己,唯一判断 NaN 的方法是使用 isNaN() 函数。 * JavaScript 把 null、undefined、0、NaN 和空字符串 '' 视为 false,其他值一概视为 true。 ## 数组 -  数组是一组按顺序排列的集合,数组元素可以是任意数据类型,**JavaScript 的数组是动态数组**。 +数组是一组按顺序排列的集合,数组元素可以是任意数据类型,**JavaScript 的数组是动态数组**。 ```javascript var arr = ['A', 'B', 'C', 'D']; arr.length; // 4 @@ -55,7 +55,7 @@ tags: JavaScript ``` ## 对象 -  JavaScript 的对象是一组由键-值组成的无序集合,也是 **动态类型**,例如: +JavaScript 的对象是一组由键-值组成的无序集合,也是 **动态类型**,例如: ```javascript var person = { name: 'Bob', @@ -71,12 +71,12 @@ tags: JavaScript delete person['school']; // 删除一个不存在的属性也不会报错 'name' in person; // true , 可以用 in 操作符检测对象是否拥有某一属性 ``` -  注意如果 in 判断一个属性存在,这个属性不一定是 person 的,它可能是 person 继承得到的,比如 toString 定义在 object 对象中,而所有的对象最终都会在原型链上指向 object。要判断一个属性是否是 person 自身拥有的而不是继承得到的,可以用 hasOwnProperty() 方法。 +注意如果 in 判断一个属性存在,这个属性不一定是 person 的,它可能是 person 继承得到的,比如 toString 定义在 object 对象中,而所有的对象最终都会在原型链上指向 object。要判断一个属性是否是 person 自身拥有的而不是继承得到的,可以用 hasOwnProperty() 方法。 ## Map 和 Set -  JavaScript 的默认对象表示方式 {} 可以被视为其他语言中的 Map 或 Dictionary 的数据结构,即一组键值对。但是 JavaScript 的对象有个小问题,就是键必须是字符串,但实际上 Number 或者其他数据类型作为键也是非常合理的。为了解决这个问题,最新的 ES6 规范引入了新的数据类型 Map 和 Set。 +JavaScript 的默认对象表示方式 {} 可以被视为其他语言中的 Map 或 Dictionary 的数据结构,即一组键值对。但是 JavaScript 的对象有个小问题,就是键必须是字符串,但实际上 Number 或者其他数据类型作为键也是非常合理的。为了解决这个问题,最新的 ES6 规范引入了新的数据类型 Map 和 Set。 -  Map 是一组键值对的结构,具有极快的查找速度。初始化 Map 需要一个二维数组,或者直接初始化一个空 Map。例如: +Map 是一组键值对的结构,具有极快的查找速度。初始化 Map 需要一个二维数组,或者直接初始化一个空 Map。例如: ```javascript var m = new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]); m.get('Michael'); // 95 @@ -86,15 +86,15 @@ tags: JavaScript m.get('Adam'); // undefined ``` -  Set 是一组 key 的集合,不存储 value。由于 key 不能重复,所以在 Set 中,没有重复的元素。要创建一个 Set,需要提供一个 Array 作为输入,或者直接创建一个空 Set。例如: +Set 是一组 key 的集合,不存储 value。由于 key 不能重复,所以在 Set 中,没有重复的元素。要创建一个 Set,需要提供一个 Array 作为输入,或者直接创建一个空 Set。例如: ```javascript var s = new Set([1, 2, 3, 3, '3']); s.add(4);// Set { 1, 2, 3, '3', 4 } s.delete(3); // true ``` -  遍历 Array 可以采用下标循环,遍历 Map 和 Set 就无法使用下标。为了统一集合类型,ES6 标准引入了新的 iterable 类型,Array、Map 和 Set 都属于 iterable 类型。具有 iterable 类型的集合可以通过新的 for...of 循环来遍历。 +遍历 Array 可以采用下标循环,遍历 Map 和 Set 就无法使用下标。为了统一集合类型,ES6 标准引入了新的 iterable 类型,Array、Map 和 Set 都属于 iterable 类型。具有 iterable 类型的集合可以通过新的 for...of 循环来遍历。 -  for...in 循环由于历史遗留问题,它遍历的实际上是对象的属性。一个 Array 数组实际上也是一个对象,它的每个元素的索引被视为一个属性。当我们手动给 Array 对象添加了额外的属性后,for...in 循环将带来意想不到的意外效果。for...of 循环则完全修复了这些问题,它只循环集合本身的元素。 +for...in 循环由于历史遗留问题,它遍历的实际上是对象的属性。一个 Array 数组实际上也是一个对象,它的每个元素的索引被视为一个属性。当我们手动给 Array 对象添加了额外的属性后,for...in 循环将带来意想不到的意外效果。for...of 循环则完全修复了这些问题,它只循环集合本身的元素。 -  更好的方式是直接使用 iterable 内置的 forEach() 方法,它接收一个函数,每次迭代就自动回调该函数: a.forEach(function (element, index, array) {...}); 。如果对某些参数不感兴趣,由于 JavaScript 的函数调用不要求参数必须一致,因此可以忽略它们。 +更好的方式是直接使用 iterable 内置的 forEach() 方法,它接收一个函数,每次迭代就自动回调该函数: a.forEach(function (element, index, array) {...}); 。如果对某些参数不感兴趣,由于 JavaScript 的函数调用不要求参数必须一致,因此可以忽略它们。 diff --git "a/_posts/2017-10-02-JavaScript\345\207\275\346\225\260\344\270\200.md" "b/_posts/2017-10-02-JavaScript\345\207\275\346\225\260\344\270\200.md" index 9c158f4..ad2e2d8 100644 --- "a/_posts/2017-10-02-JavaScript\345\207\275\346\225\260\344\270\200.md" +++ "b/_posts/2017-10-02-JavaScript\345\207\275\346\225\260\344\270\200.md" @@ -6,7 +6,7 @@ tags: JavaScript --- ## 定义函数 -  在 JavaScript 中,定义函数的方式如下: +在 JavaScript 中,定义函数的方式如下: ```javascript function abs(x) { if (x >= 0) { @@ -18,7 +18,7 @@ tags: JavaScript ``` >function 指出这是一个函数定义,abs 是函数的名称,(x) 括号内列出函数的参数,多个参数以 , 分隔。{ ... } 之间的代码是函数体,可以包含若干语句,甚至可以没有任何语句。如果没有 return 语句,函数执行完毕后也会返回结果,只是结果为 undefined。 -  由于 JavaScript 的函数也是一个对象,上述定义的 abs() 函数实际上是一个函数对象,而函数名 abs 可以视为指向该函数的变量。因此,第二种定义函数的方式如下: +由于 JavaScript 的函数也是一个对象,上述定义的 abs() 函数实际上是一个函数对象,而函数名 abs 可以视为指向该函数的变量。因此,第二种定义函数的方式如下: ```javascript var abs = function (x) { if (x >= 0) { @@ -31,7 +31,7 @@ tags: JavaScript >在这种方式下,function (x) { ... } 是一个匿名函数,它没有函数名。但是,这个匿名函数赋值给了变量 abs ,所以,通过变量 abs 就可以调用该函数。上述两种定义完全等价,注意第二种方式按照完整语法需要在函数体末尾加一个 ; ,表示赋值语句结束。 ## 调用函数 -  调用函数时,按顺序传入参数即可。由于 JavaScript **允许传入任意个参数而不影响调用**,因此传入的参数比定义的参数多和少都没有问题。要避免参数收到 undefined,可以对参数进行检查: +调用函数时,按顺序传入参数即可。由于 JavaScript **允许传入任意个参数而不影响调用**,因此传入的参数比定义的参数多和少都没有问题。要避免参数收到 undefined,可以对参数进行检查: ```javascript function abs(x) { if (typeof x !== 'number') { @@ -46,7 +46,7 @@ tags: JavaScript ``` ## arguments -  JavaScript 还有一个免费赠送的关键字 arguments,它只在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数。arguments 类似 Array 但它不是一个 Array。利用 arguments,你可以获得调用者传入的所有参数。也就是说,即使函数不定义任何参数,还是可以拿到参数的值: +JavaScript 还有一个免费赠送的关键字 arguments,它只在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数。arguments 类似 Array 但它不是一个 Array。利用 arguments,你可以获得调用者传入的所有参数。也就是说,即使函数不定义任何参数,还是可以拿到参数的值: ```javascript function abs() { if (arguments.length === 0) { @@ -59,7 +59,7 @@ tags: JavaScript abs(); // 0 abs(-9); // 9 ``` -  ES6 标准引入了 rest 参数,用法如下: +ES6 标准引入了 rest 参数,用法如下: ```javascript function foo(a, b, ...rest) { console.log('a = ' + a); @@ -72,10 +72,10 @@ tags: JavaScript // b = 2 // Array [ 3, 4, 5 ] ``` -  rest 参数只能写在最后,前面用 ... 标识,从运行结果可知,传入的参数先绑定 a、b,多余的参数以数组形式交给变量 rest,所以,不再需要 arguments 我们就获取了全部参数。如果传入的参数连正常定义的参数都没填满,也不要紧,rest 参数会接收一个空数组。 +rest 参数只能写在最后,前面用 ... 标识,从运行结果可知,传入的参数先绑定 a、b,多余的参数以数组形式交给变量 rest,所以,不再需要 arguments 我们就获取了全部参数。如果传入的参数连正常定义的参数都没填满,也不要紧,rest 参数会接收一个空数组。 ## 变量作用域 -  在 JavaScript 中,用 var 申明的变量是有作用域的。如果一个变量在函数体内部声明,则该变量的作用域为整个函数体,在函数体外不可引用该变量。不同函数内部的同名变量互相独立,互不影响。由于 JavaScript 的函数可以嵌套,此时,内部函数可以访问外部函数定义的变量,反过来则不行。JavaScript 的函数在查找变量时从自身函数定义开始,从“内”向“外”查找。如果内部函数定义了与外部函数重名的变量,则内部函数的变量将“屏蔽”外部函数的变量。 +在 JavaScript 中,用 var 申明的变量是有作用域的。如果一个变量在函数体内部声明,则该变量的作用域为整个函数体,在函数体外不可引用该变量。不同函数内部的同名变量互相独立,互不影响。由于 JavaScript 的函数可以嵌套,此时,内部函数可以访问外部函数定义的变量,反过来则不行。JavaScript 的函数在查找变量时从自身函数定义开始,从“内”向“外”查找。如果内部函数定义了与外部函数重名的变量,则内部函数的变量将“屏蔽”外部函数的变量。 * JavaScript 的函数定义有个特点,它会先扫描整个函数体的语句,把所有申明的变量“提升”到函数顶部: ```javascript @@ -89,10 +89,10 @@ tags: JavaScript ``` >上面代码运行并不报错,原因是变量 y 在稍后申明了。这正是因为 JavaScript 引擎自动 **提升了变量 y 的声明,但不会提升变量 y 的赋值**。由于 JavaScript 的这一怪异的“特性”,我们在函数内部定义变量时,请严格遵守“在函数内部首先申明所有变量”这一规则。 -  不在任何函数内定义的变量就具有全局作用域。实际上,JavaScript 默认有一个全局对象 window ,全局作用域的变量实际上被绑定到 window 的一个属性。由于函数定义有两种方式,以变量方式 var foo = function () {} 定义的函数实际上也是一个全局变量,因此,顶层函数的定义也被视为一个全局变量,并绑定到 window 对象。 +不在任何函数内定义的变量就具有全局作用域。实际上,JavaScript 默认有一个全局对象 window ,全局作用域的变量实际上被绑定到 window 的一个属性。由于函数定义有两种方式,以变量方式 var foo = function () {} 定义的函数实际上也是一个全局变量,因此,顶层函数的定义也被视为一个全局变量,并绑定到 window 对象。 -  全局变量会绑定到 window 上,不同的 JavaScript 文件如果使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突,并且很难被发现。减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中,也就是使用命名空间。 +全局变量会绑定到 window 上,不同的 JavaScript 文件如果使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突,并且很难被发现。减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中,也就是使用命名空间。 -  由于 JavaScript 的变量作用域实际上是函数内部,我们在 for 循环等语句块中是无法定义具有局部作用域的变量的。为了解决块级作用域,ES6 引入了新的关键字 let,用 let 替代 var 可以申明一个块级作用域的变量。 +由于 JavaScript 的变量作用域实际上是函数内部,我们在 for 循环等语句块中是无法定义具有局部作用域的变量的。为了解决块级作用域,ES6 引入了新的关键字 let,用 let 替代 var 可以申明一个块级作用域的变量。 -  由于 var 和 let 申明的是变量,如果要申明一个常量,在 ES6 之前是不行的,我们通常用全部大写的变量来表示“这是一个常量,不要修改它的值”。ES6标准引入了新的关键字 const 来定义常量,const 与 let 都具有块级作用域。 +由于 var 和 let 申明的是变量,如果要申明一个常量,在 ES6 之前是不行的,我们通常用全部大写的变量来表示“这是一个常量,不要修改它的值”。ES6标准引入了新的关键字 const 来定义常量,const 与 let 都具有块级作用域。 diff --git "a/_posts/2017-10-03-JavaScript\345\207\275\346\225\260\344\272\214.md" "b/_posts/2017-10-03-JavaScript\345\207\275\346\225\260\344\272\214.md" index dc0507d..11b5fb2 100644 --- "a/_posts/2017-10-03-JavaScript\345\207\275\346\225\260\344\272\214.md" +++ "b/_posts/2017-10-03-JavaScript\345\207\275\346\225\260\344\272\214.md" @@ -6,7 +6,7 @@ tags: JavaScript --- ## 方法 -  在一个对象中绑定函数,称为这个对象的方法。例如: +在一个对象中绑定函数,称为这个对象的方法。例如: ```javascript var xiaoming = { name: '小明', @@ -22,7 +22,7 @@ tags: JavaScript ``` >在一个方法内部,this 是一个特殊变量,它始终指向当前对象,也就是 xiaoming 这个变量。所以,this.birth 可以拿到 xiaoming 的 birth 属性。 -  也可以把函数和对象拆开写: +也可以把函数和对象拆开写: ```javascript function getAge() { var y = new Date().getFullYear(); @@ -40,7 +40,7 @@ tags: JavaScript ``` >如果以对象的方法形式调用,比如 xiaoming.age(),该函数的 this 指向被调用的对象,也就是 xiaoming,这是符合我们预期的;如果单独调用函数,比如 getAge(),此时,该函数的 this 指向全局对象,也就是 window。由于这是一个设计错误,ECMA 决定,在 strict 模式下让函数的 this 指向 undefined。 -  要指定函数的 this 指向哪个对象,可以用函数本身的 apply 方法,它接收两个参数,第一个参数就是需要绑定的 this 变量,第二个参数是 Array,表示函数本身的参数: +要指定函数的 this 指向哪个对象,可以用函数本身的 apply 方法,它接收两个参数,第一个参数就是需要绑定的 this 变量,第二个参数是 Array,表示函数本身的参数: ```javascript function getAge() { var y = new Date().getFullYear(); @@ -57,16 +57,16 @@ tags: JavaScript getAge.apply(xiaoming, []); // 25, this 指向 xiaoming, 参数为空 ``` -  另一个与 apply() 类似的方法是 call(),唯一区别是:apply() 把参数打包成 Array 再传入,call() 把参数按顺序传入。比如调用 Math.max(3, 5, 4),分别用 apply() 和 call() 实现如下: +另一个与 apply() 类似的方法是 call(),唯一区别是:apply() 把参数打包成 Array 再传入,call() 把参数按顺序传入。比如调用 Math.max(3, 5, 4),分别用 apply() 和 call() 实现如下: ```javascript Math.max.apply(null, [3, 5, 4]); // 5 Math.max.call(null, 3, 5, 4); // 5 ``` >对普通函数调用,我们通常把 this 绑定为 null。 -  利用 apply(),我们还可以动态改变函数的行为。JavaScript 的所有对象都是动态的,即使内置的函数,我们也可以重新指向新的函数。 +利用 apply(),我们还可以动态改变函数的行为。JavaScript 的所有对象都是动态的,即使内置的函数,我们也可以重新指向新的函数。 -  现在假定我们想统计一下代码一共调用了多少次 parseInt(),可以把所有的调用都找出来,然后手动加上 count += 1,不过这样做太傻了。最佳方案是用我们自己的函数替换掉默认的 parseInt(): +现在假定我们想统计一下代码一共调用了多少次 parseInt(),可以把所有的调用都找出来,然后手动加上 count += 1,不过这样做太傻了。最佳方案是用我们自己的函数替换掉默认的 parseInt(): ```javascript var count = 0; var oldParseInt = parseInt; // 保存原函数 diff --git "a/_posts/2017-10-04-JavaScript\345\207\275\346\225\260\344\270\211.md" "b/_posts/2017-10-04-JavaScript\345\207\275\346\225\260\344\270\211.md" index b94c1aa..f2200ad 100644 --- "a/_posts/2017-10-04-JavaScript\345\207\275\346\225\260\344\270\211.md" +++ "b/_posts/2017-10-04-JavaScript\345\207\275\346\225\260\344\270\211.md" @@ -6,9 +6,9 @@ tags: JavaScript --- ## 高阶函数 -  一个函数可以 **接收另一个函数作为参数**,这种函数就被称为高阶函数。 +一个函数可以 **接收另一个函数作为参数**,这种函数就被称为高阶函数。 -  map() 方法定义在 JavaScript 的 Array 中,我们调用 Array 的 map() 方法,传入我们自己的函数,就得到了一个新的 Array 作为结果: +map() 方法定义在 JavaScript 的 Array 中,我们调用 Array 的 map() 方法,传入我们自己的函数,就得到了一个新的 Array 作为结果: ```javascript function pow(x) { return x * x; @@ -17,24 +17,24 @@ tags: JavaScript var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]; arr.map(pow); // [1, 4, 9, 16, 25, 36, 49, 64, 81] ``` -  map() 作为高阶函数,实际上它把运算规则抽象了,因此,我们不但可以计算简单的 f(x) = x * x ,还可以计算任意复杂的函数,比如,把 Array 的所有数字转为字符串: +map() 作为高阶函数,实际上它把运算规则抽象了,因此,我们不但可以计算简单的 f(x) = x * x ,还可以计算任意复杂的函数,比如,把 Array 的所有数字转为字符串: ```javascript var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]; arr.map(String); // ['1', '2', '3', '4', '5', '6', '7', '8', '9'] ``` -  Array 的 reduce() 把一个函数作用在这个 Array 的 [x1, x2, x3...] 上,这个函数必须接收两个参数,reduce() 把结果继续和序列的下一个元素做累积计算,其效果就是: +Array 的 reduce() 把一个函数作用在这个 Array 的 [x1, x2, x3...] 上,这个函数必须接收两个参数,reduce() 把结果继续和序列的下一个元素做累积计算,其效果就是: ```javascript [x1, x2, x3, x4].reduce(f) = f(f(f(x1, x2), x3), x4) ``` -  比方说对一个 Array 求和,就可以用 reduce() 实现: +比方说对一个 Array 求和,就可以用 reduce() 实现: ```javascript var arr = [1, 3, 5, 7, 9]; arr.reduce(function (x, y) { return x + y; }); // 25 ``` -  要把[1, 3, 5, 7, 9]变换成整数13579,reduce() 也能派上用场: +要把[1, 3, 5, 7, 9]变换成整数13579,reduce() 也能派上用场: ```javascript var arr = [1, 3, 5, 7, 9]; arr.reduce(function (x, y) { @@ -42,9 +42,9 @@ tags: JavaScript }); // 13579 ``` -  filter 也是一个常用的操作,它用于把 Array 的某些元素过滤掉,然后返回剩下的元素。和 map() 类似,Array 的 filter() 也接收一个函数。和 map() 不同的是,filter() 把传入的函数依次作用于每个元素,然后根据返回值是 true 还是 false 决定保留还是丢弃该元素。 +filter 也是一个常用的操作,它用于把 Array 的某些元素过滤掉,然后返回剩下的元素。和 map() 类似,Array 的 filter() 也接收一个函数。和 map() 不同的是,filter() 把传入的函数依次作用于每个元素,然后根据返回值是 true 还是 false 决定保留还是丢弃该元素。 -  例如,在一个 Array 中,删掉偶数,只保留奇数,可以这么写: +例如,在一个 Array 中,删掉偶数,只保留奇数,可以这么写: ```javascript var arr = [1, 2, 4, 5, 6, 9, 10, 15]; var r = arr.filter(function (x) { @@ -52,7 +52,7 @@ tags: JavaScript }); r; // [1, 5, 9, 15] ``` -  把一个 Array 中的空字符串删掉,可以这么写: +把一个 Array 中的空字符串删掉,可以这么写: ```javascript var arr = ['A', '', 'B', null, undefined, 'C', ' ']; var r = arr.filter(function (s) { @@ -60,7 +60,7 @@ tags: JavaScript }); r; // ['A', 'B', 'C'] ``` -  filter() 接收的回调函数,其实可以有多个参数。通常我们仅使用第一个参数,表示 Array 的某个元素。回调函数还可以接收另外两个参数,表示元素的位置和数组本身: +filter() 接收的回调函数,其实可以有多个参数。通常我们仅使用第一个参数,表示 Array 的某个元素。回调函数还可以接收另外两个参数,表示元素的位置和数组本身: ```javascript var arr = ['A', 'B', 'C']; var r = arr.filter(function (element, index, self) { @@ -70,7 +70,7 @@ tags: JavaScript return true; }); ``` -  利用 filter,可以巧妙地去除 Array 的重复元素: +利用 filter,可以巧妙地去除 Array 的重复元素: ```javascript var r, @@ -81,9 +81,9 @@ tags: JavaScript ``` >去除重复元素依靠的是 indexOf 总是返回第一个元素的位置,后续的重复元素位置与 indexOf 返回的位置不相等,因此被 filter 滤掉了。 -  排序也是在程序中经常用到的算法,无论使用冒泡排序还是快速排序,排序的核心是比较两个元素的大小。如果是数字,我们可以直接比较,但如果是字符串或者两个对象的话,直接比较数学上的大小是没有意义的,因此,比较的过程必须通过函数抽象出来。通常规定,对于两个元素 x 和 y,如果认为 x < y,则返回-1,如果认为 x == y,则返回0,如果认为 x > y,则返回1,这样,排序算法就不用关心具体的比较过程,而是根据比较结果直接排序。 +排序也是在程序中经常用到的算法,无论使用冒泡排序还是快速排序,排序的核心是比较两个元素的大小。如果是数字,我们可以直接比较,但如果是字符串或者两个对象的话,直接比较数学上的大小是没有意义的,因此,比较的过程必须通过函数抽象出来。通常规定,对于两个元素 x 和 y,如果认为 x < y,则返回-1,如果认为 x == y,则返回0,如果认为 x > y,则返回1,这样,排序算法就不用关心具体的比较过程,而是根据比较结果直接排序。 -  JavaScript 的 Array 的 sort() 方法就是用于排序的,但是 Array 的 sort() 方法默认把所有元素先转换为 String 再排序。sort() 方法也是一个高阶函数,它还可以接收一个比较函数来实现自定义的排序。要按数字大小排序,我们可以这么写: +JavaScript 的 Array 的 sort() 方法就是用于排序的,但是 Array 的 sort() 方法默认把所有元素先转换为 String 再排序。sort() 方法也是一个高阶函数,它还可以接收一个比较函数来实现自定义的排序。要按数字大小排序,我们可以这么写: ```javascript var arr = [10, 20, 1, 2]; arr.sort(function (x, y) { @@ -96,4 +96,4 @@ tags: JavaScript return 0; }); // [1, 2, 10, 20] ``` -  最后需要注意的是,**sort() 方法会直接对 Array 进行修改**,它返回的结果仍是当前 Array 这个对象。 +最后需要注意的是,**sort() 方法会直接对 Array 进行修改**,它返回的结果仍是当前 Array 这个对象。 diff --git "a/_posts/2017-10-05-JavaScript\345\207\275\346\225\260\345\233\233.md" "b/_posts/2017-10-05-JavaScript\345\207\275\346\225\260\345\233\233.md" index 294a5d2..e8870cf 100644 --- "a/_posts/2017-10-05-JavaScript\345\207\275\346\225\260\345\233\233.md" +++ "b/_posts/2017-10-05-JavaScript\345\207\275\346\225\260\345\233\233.md" @@ -6,9 +6,9 @@ tags: JavaScript --- ## 闭包 -  高阶函数除了可以接受函数作为参数外,还可以 **把函数作为结果值** 返回。 +高阶函数除了可以接受函数作为参数外,还可以 **把函数作为结果值** 返回。 -  通常情况下,求和的函数是这样定义的: +通常情况下,求和的函数是这样定义的: ```javascript function sum(arr) { return arr.reduce(function (x, y) { @@ -18,7 +18,7 @@ tags: JavaScript sum([1, 2, 3, 4, 5]); // 15 ``` -  但是,如果不需要立刻求和,而是要在后面的代码中,根据需要再计算怎么办?可以不返回求和的结果,而是返回求和的函数! +但是,如果不需要立刻求和,而是要在后面的代码中,根据需要再计算怎么办?可以不返回求和的结果,而是返回求和的函数! ```javascript function lazy_sum(arr) { var sum = function () { @@ -35,7 +35,7 @@ tags: JavaScript var f2 = lazy_sum([1, 2, 3, 4, 5]); f1 === f2; // false ``` -  这个例子中,我们在函数 lazy_sum 中又定义了函数 sum,并且,内部函数 sum 可以引用外部函数 lazy_sum 的参数和局部变量,当 lazy_sum 返回函数 sum 时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。还需要注意的是,当我们调用 lazy_sum() 时,每次调用都会返回一个新的函数,即使传入相同的参数。注意到返回的函数在其定义内部引用了局部变量 arr,所以,当一个函数返回了一个函数后,其内部的局部变量还会被新函数引用: +这个例子中,我们在函数 lazy_sum 中又定义了函数 sum,并且,内部函数 sum 可以引用外部函数 lazy_sum 的参数和局部变量,当 lazy_sum 返回函数 sum 时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。还需要注意的是,当我们调用 lazy_sum() 时,每次调用都会返回一个新的函数,即使传入相同的参数。注意到返回的函数在其定义内部引用了局部变量 arr,所以,当一个函数返回了一个函数后,其内部的局部变量还会被新函数引用: ```javascript function count() { var arr = []; @@ -55,16 +55,16 @@ tags: JavaScript f2(); // 16 f3(); // 16 ``` -  3个函数的执行结果全部都是16!原因就在于返回的函数引用了变量 i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量 i 已经变成了4,因此最终结果为16。 +3个函数的执行结果全部都是16!原因就在于返回的函数引用了变量 i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量 i 已经变成了4,因此最终结果为16。 -  JavaScript 还有一种创建一个匿名函数并立刻执行的语法: +JavaScript 还有一种创建一个匿名函数并立刻执行的语法: ```javascript (function (x) { return x * x; })(3); // 9 ``` -  在面向对象的程序设计语言里,比如 Java 和 C++,要在对象内部封装一个私有变量,可以用 private 修饰一个成员变量。在没有 class 机制,只有函数的语言里,借助闭包,同样可以封装一个私有变量。我们用 JavaScript 创建一个计数器: +在面向对象的程序设计语言里,比如 Java 和 C++,要在对象内部封装一个私有变量,可以用 private 修饰一个成员变量。在没有 class 机制,只有函数的语言里,借助闭包,同样可以封装一个私有变量。我们用 JavaScript 创建一个计数器: ```javascript function create_counter(initial) { var x = initial || 0; @@ -83,7 +83,7 @@ tags: JavaScript ``` >在返回的对象中,实现了一个闭包,该闭包携带了局部变量 x,并且,从外部代码根本无法访问到变量 x。换句话说,**闭包就是携带状态的函数**,并且它的状态可以完全对外隐藏起来。 -  闭包还可以把多参数的函数变成单参数的函数。例如,要计算 x^y 可以用 Math.pow(x, y) 函数,不过考虑到经常计算 x^2或x^3,我们可以利用闭包创建新的函数 pow2 和 pow3 : +闭包还可以把多参数的函数变成单参数的函数。例如,要计算 x^y 可以用 Math.pow(x, y) 函数,不过考虑到经常计算 x^2或x^3,我们可以利用闭包创建新的函数 pow2 和 pow3 : ```javascript function make_pow(n) { return function (x) { diff --git "a/_posts/2017-10-06-JavaScript\345\207\275\346\225\260\344\272\224.md" "b/_posts/2017-10-06-JavaScript\345\207\275\346\225\260\344\272\224.md" index 053c209..89245fc 100644 --- "a/_posts/2017-10-06-JavaScript\345\207\275\346\225\260\344\272\224.md" +++ "b/_posts/2017-10-06-JavaScript\345\207\275\346\225\260\344\272\224.md" @@ -6,17 +6,17 @@ tags: JavaScript --- ## 箭头函数 -  ES6 标准新增了一种新的函数:Arrow Function(箭头函数)。为什么叫 Arrow Function?因为它的定义用的就是一个箭头: +ES6 标准新增了一种新的函数:Arrow Function(箭头函数)。为什么叫 Arrow Function?因为它的定义用的就是一个箭头: ```javascript x => x * x ``` -  上面的箭头函数相当于: +上面的箭头函数相当于: ```javascript function (x) { return x * x; } ``` -  箭头函数相当于匿名函数,并且简化了函数定义。箭头函数有两种格式,一种像上面的,只包含一个表达式,连 { ... } 和 return 都省略掉了。还有一种可以包含多条语句,这时候就不能省略 { ... } 和 return: +箭头函数相当于匿名函数,并且简化了函数定义。箭头函数有两种格式,一种像上面的,只包含一个表达式,连 { ... } 和 return 都省略掉了。还有一种可以包含多条语句,这时候就不能省略 { ... } 和 return: ```javascript x => { if (x > 0) { @@ -27,7 +27,7 @@ tags: JavaScript } } ``` -  如果参数不是一个,就需要用括号 () 括起来: +如果参数不是一个,就需要用括号 () 括起来: ```javascript // 两个参数: (x, y) => x * x + y * y @@ -44,7 +44,7 @@ tags: JavaScript return sum; } ``` -  如果要返回一个对象,由于和函数体的 { ... } 有语法冲突,所以: +如果要返回一个对象,由于和函数体的 { ... } 有语法冲突,所以: ```javascript // SyntaxError: x => { foo: x } @@ -52,9 +52,9 @@ tags: JavaScript x => ({ foo: x }) ``` -  箭头函数看上去是匿名函数的一种简写,但实际上,箭头函数和匿名函数有个明显的区别:箭头函数内部的 this 是词法作用域,由上下文确定。 +箭头函数看上去是匿名函数的一种简写,但实际上,箭头函数和匿名函数有个明显的区别:箭头函数内部的 this 是词法作用域,由上下文确定。 -  回顾前面的例子,由于 JavaScript 函数对 this 绑定的错误处理,下面的例子无法得到预期结果: +回顾前面的例子,由于 JavaScript 函数对 this 绑定的错误处理,下面的例子无法得到预期结果: ```javascript var obj = { birth: 1990, @@ -67,7 +67,7 @@ tags: JavaScript } }; ``` -  现在,箭头函数完全修复了 this 的指向,this 总是指向词法作用域,也就是外层调用者 obj: +现在,箭头函数完全修复了 this 的指向,this 总是指向词法作用域,也就是外层调用者 obj: ```javascript var obj = { birth: 1990, @@ -80,7 +80,7 @@ tags: JavaScript obj.getAge(); // 25 ``` -  由于 this 在箭头函数中已经按照词法作用域绑定了,所以,用 call() 或者 apply() 调用箭头函数时,无法对 this 进行绑定,即传入的第一个参数被忽略: +由于 this 在箭头函数中已经按照词法作用域绑定了,所以,用 call() 或者 apply() 调用箭头函数时,无法对 this 进行绑定,即传入的第一个参数被忽略: ```javascript var obj = { birth: 1990, diff --git "a/_posts/2017-10-07-JavaScript\345\207\275\346\225\260\345\205\255.md" "b/_posts/2017-10-07-JavaScript\345\207\275\346\225\260\345\205\255.md" index b9cfccf..49a3b21 100644 --- "a/_posts/2017-10-07-JavaScript\345\207\275\346\225\260\345\205\255.md" +++ "b/_posts/2017-10-07-JavaScript\345\207\275\346\225\260\345\205\255.md" @@ -6,9 +6,9 @@ tags: JavaScript --- ## generator -  generator(生成器)是 ES6 标准引入的新的数据类型。一个 generator 看上去像一个函数,但可以返回多次。 +generator(生成器)是 ES6 标准引入的新的数据类型。一个 generator 看上去像一个函数,但可以返回多次。 -  我们先复习函数的概念。一个函数是一段完整的代码,调用一个函数就是传入参数,然后返回结果: +我们先复习函数的概念。一个函数是一段完整的代码,调用一个函数就是传入参数,然后返回结果: ```javascript function foo(x) { return x + x; @@ -16,9 +16,9 @@ tags: JavaScript var r = foo(1); // 调用foo函数 ``` -  函数在执行过程中,如果没有遇到 return 语句(函数末尾如果没有 return,就是隐含的 return undefined; ),控制权无法交回被调用的代码。 +函数在执行过程中,如果没有遇到 return 语句(函数末尾如果没有 return,就是隐含的 return undefined; ),控制权无法交回被调用的代码。 -  generator 跟函数很像,定义如下: +generator 跟函数很像,定义如下: ```javascript function* foo(x) { yield x + 1; @@ -26,9 +26,9 @@ tags: JavaScript return x + 3; } ``` -  generator 和函数不同的是,generator 由 function* 定义(注意多出的 * 号),并且,除了 return 语句,还可以用 yield 返回多次。 +generator 和函数不同的是,generator 由 function* 定义(注意多出的 * 号),并且,除了 return 语句,还可以用 yield 返回多次。 -  要编写一个产生斐波那契数列的函数,可以这么写: +要编写一个产生斐波那契数列的函数,可以这么写: ```javascript function fib(max) { var @@ -49,7 +49,7 @@ tags: JavaScript fib(5); // [0, 1, 1, 2, 3] fib(10); // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34] ``` -  函数只能返回一次,所以必须返回一个 Array。但是,如果换成 generator,就可以一次返回一个数,不断返回多次。用 generator 改写如下: +函数只能返回一次,所以必须返回一个 Array。但是,如果换成 generator,就可以一次返回一个数,不断返回多次。用 generator 改写如下: ```javascript function* fib(max) { var @@ -69,9 +69,9 @@ tags: JavaScript fib(5); // fib {[[GeneratorStatus]]: "suspended", [[GeneratorReceiver]]: Window} ``` -  直接调用一个 generator 和调用函数不一样,fib(5) 仅仅是创建了一个 generator 对象,还没有去执行它。 +直接调用一个 generator 和调用函数不一样,fib(5) 仅仅是创建了一个 generator 对象,还没有去执行它。 -  调用 generator 对象有两个方法,一是不断地调用 generator 对象的 next() 方法: +调用 generator 对象有两个方法,一是不断地调用 generator 对象的 next() 方法: ```javascript var f = fib(5); f.next(); // {value: 0, done: false} @@ -82,13 +82,13 @@ tags: JavaScript ``` >next() 方法会执行 generator 的代码,然后,每次遇到 yield x;就返回一个对象 {value: x, done: true/false},然后“暂停”。返回的 value 就是 yield 的返回值,done 表示这个 generator 是否已经执行结束了。如果 done 为 true,则 value 就是 return 的返回值。当执行到 done 为 true 时,这个 generator 对象就已经全部执行完毕,不要再继续调用 next()了。 -  第二个方法是直接用 for ... of 循环迭代 generator 对象,这种方式不需要我们自己判断 done: +第二个方法是直接用 for ... of 循环迭代 generator 对象,这种方式不需要我们自己判断 done: ```javascript for (var x of fib(5)) { console.log(x); // 依次输出0, 1, 1, 2, 3 } ``` -  因为 generator 可以在执行过程中多次返回,所以它看上去就像一个可以记住执行状态的函数,利用这一点,写一个 generator 就可以实现需要用面向对象才能实现的功能。 +因为 generator 可以在执行过程中多次返回,所以它看上去就像一个可以记住执行状态的函数,利用这一点,写一个 generator 就可以实现需要用面向对象才能实现的功能。 -  generator 还有另一个巨大的好处,就是把异步回调代码变成“同步”代码。这个好处要等到后面学了 AJAX 以后才能体会到。 +generator 还有另一个巨大的好处,就是把异步回调代码变成“同步”代码。这个好处要等到后面学了 AJAX 以后才能体会到。 diff --git "a/_posts/2017-10-08-JavaScript\346\240\207\345\207\206\345\257\271\350\261\241\344\270\200.md" "b/_posts/2017-10-08-JavaScript\346\240\207\345\207\206\345\257\271\350\261\241\344\270\200.md" index 1fb5610..30117ea 100644 --- "a/_posts/2017-10-08-JavaScript\346\240\207\345\207\206\345\257\271\350\261\241\344\270\200.md" +++ "b/_posts/2017-10-08-JavaScript\346\240\207\345\207\206\345\257\271\350\261\241\344\270\200.md" @@ -6,7 +6,7 @@ tags: JavaScript --- ## 标准对象 -  在 JavaScript 的世界里,一切都是对象。但是某些对象还是和其他对象不太一样。为了区分对象的类型,我们用 typeof 操作符获取对象的类型,它总是返回一个字符串: +在 JavaScript 的世界里,一切都是对象。但是某些对象还是和其他对象不太一样。为了区分对象的类型,我们用 typeof 操作符获取对象的类型,它总是返回一个字符串: ```javascript typeof 123; // 'number' typeof NaN; // 'number' @@ -18,15 +18,15 @@ tags: JavaScript typeof []; // 'object' typeof {}; // 'object' ``` -  可见,number、string、boolean、function 和 undefined 有别于其他类型。特别注意 null 的类型是 object,Array 的类型也是 object,如果我们用 typeof 的话将无法区分出 null、Array 和通常意义上的 object——{}。 +可见,number、string、boolean、function 和 undefined 有别于其他类型。特别注意 null 的类型是 object,Array 的类型也是 object,如果我们用 typeof 的话将无法区分出 null、Array 和通常意义上的 object——{}。 -  除了这些类型外,JavaScript 还提供了包装对象。number、boolean 和 string 都有包装对象。没错,在 JavaScript 中,字符串也区分 string 类型和它的包装类型。包装对象用 new 创建: +除了这些类型外,JavaScript 还提供了包装对象。number、boolean 和 string 都有包装对象。没错,在 JavaScript 中,字符串也区分 string 类型和它的包装类型。包装对象用 new 创建: ```javascript var n = new Number(123); // 123,生成了新的包装类型 var b = new Boolean(true); // true,生成了新的包装类型 var s = new String('str'); // 'str',生成了新的包装类型 ``` -  虽然包装对象看上去和原来的值一模一样,显示出来也是一模一样,但他们的类型已经变为 object了!所以,包装对象和原始值用 === 比较会返回 false: +虽然包装对象看上去和原来的值一模一样,显示出来也是一模一样,但他们的类型已经变为 object了!所以,包装对象和原始值用 === 比较会返回 false: ```javascript typeof new Number(123); // 'object' new Number(123) === 123; // false @@ -39,7 +39,7 @@ tags: JavaScript ``` >所以闲的蛋疼也不要使用包装对象!尤其是针对 string 类型!!! -  如果我们在使用 Number、Boolean 和 String 时,没有写new,这时,Number()、Boolean() 和 String() 会被当做普通函数,把任何类型的数据转换为 number、boolean 和 string 类型(注意不是其包装类型): +如果我们在使用 Number、Boolean 和 String 时,没有写new,这时,Number()、Boolean() 和 String() 会被当做普通函数,把任何类型的数据转换为 number、boolean 和 string 类型(注意不是其包装类型): ```javascript var n = Number('123'); // 123,相当于parseInt()或parseFloat() typeof n; // 'number' @@ -54,7 +54,7 @@ tags: JavaScript typeof s; // 'string' ``` -  总结一下,有这么几条规则需要遵守: +总结一下,有这么几条规则需要遵守: * 不要使用 new Number()、new Boolean()、new String() 创建包装对象; * 用 parseInt() 或 parseFloat() 来转换任意类型到 number; @@ -75,7 +75,7 @@ tags: JavaScript * 不是任何对象都有 toString() 方法,null 和 undefined 就没有。 -  最后还有一点要注意的是,number 对象调用 toString() 需要特殊处理一下: +最后还有一点要注意的是,number 对象调用 toString() 需要特殊处理一下: ```javascript 123.toString(); // SyntaxError 123..toString(); // '123', 注意是两个点! diff --git "a/_posts/2017-10-09-JavaScript\346\240\207\345\207\206\345\257\271\350\261\241\344\272\214.md" "b/_posts/2017-10-09-JavaScript\346\240\207\345\207\206\345\257\271\350\261\241\344\272\214.md" index 0e0b35f..18a7208 100644 --- "a/_posts/2017-10-09-JavaScript\346\240\207\345\207\206\345\257\271\350\261\241\344\272\214.md" +++ "b/_posts/2017-10-09-JavaScript\346\240\207\345\207\206\345\257\271\350\261\241\344\272\214.md" @@ -6,7 +6,7 @@ tags: JavaScript --- ## Date -  在 JavaScript 中,Date 对象用来表示日期和时间。要获取系统当前时间,用: +在 JavaScript 中,Date 对象用来表示日期和时间。要获取系统当前时间,用: ```javascript var now = new Date(); now; // Wed Jun 24 2015 19:49:22 GMT+0800 (CST) @@ -20,7 +20,7 @@ tags: JavaScript now.getMilliseconds(); // 875, 毫秒数 now.getTime(); // 1435146562875, 以number形式表示的时间戳 ``` -  如果要创建一个指定日期和时间的 Date 对象,有两种方法: +如果要创建一个指定日期和时间的 Date 对象,有两种方法: ```javascript var d = new Date(2015, 5, 19, 20, 15, 30, 123); d; // Fri Jun 19 2015 20:15:30 GMT+0800 (CST) @@ -30,7 +30,7 @@ tags: JavaScript var d = new Date(1435146562875); d; // Wed Jun 24 2015 19:49:22 GMT+0800 (CST) ``` -  Date 对象表示的时间总是按浏览器所在时区显示的,不过我们既可以显示本地时间,也可以显示调整后的 UTC 时间: +Date 对象表示的时间总是按浏览器所在时区显示的,不过我们既可以显示本地时间,也可以显示调整后的 UTC 时间: ```javascript var d = new Date(1435146562875); d.toLocaleString(); // '2015/6/24 下午7:49:22',本地时间(北京时区+8:00),显示的字符串与操作系统设定的格式有关 @@ -38,7 +38,7 @@ tags: JavaScript ``` ## RegExp -  JavaScript 有两种方式创建一个正则表达式,第一种方式是直接通过 `/正则表达式/` 写出来,第二种方式是通过 `new RegExp('正则表达式')` 创建一个RegExp对象。两种写法是一样的: +JavaScript 有两种方式创建一个正则表达式,第一种方式是直接通过 `/正则表达式/` 写出来,第二种方式是通过 `new RegExp('正则表达式')` 创建一个RegExp对象。两种写法是一样的: ```javascript var re1 = /ABC\-001/; var re2 = new RegExp('ABC\\-001'); @@ -48,7 +48,7 @@ tags: JavaScript ``` >注意,如果使用第二种写法,因为字符串的转义问题,字符串的两个 \\ 实际上是一个 \ 。 -  先看看如何判断正则表达式是否匹配: +先看看如何判断正则表达式是否匹配: ```javascript var re = /^\d{3}\-\d{3,8}$/; re.test('010-12345'); // true @@ -57,7 +57,7 @@ tags: JavaScript ``` >RegExp对象的test()方法用于测试给定的字符串是否符合条件。 -  用正则表达式切分字符串: +用正则表达式切分字符串: ```javascript 'a b c'.split(' '); // ['a', 'b', '', '', 'c'] 'a b c'.split(/\s+/); // ['a', 'b', 'c'] @@ -65,7 +65,7 @@ tags: JavaScript 'a,b;; c d'.split(/[\s\,\;]+/); // ['a', 'b', 'c', 'd'] ``` -  除了简单地判断是否匹配之外,正则表达式还有提取子串的强大功能。用 () 表示的就是要提取的分组(Group)。比如: +除了简单地判断是否匹配之外,正则表达式还有提取子串的强大功能。用 () 表示的就是要提取的分组(Group)。比如: ```javascript var re = /^(\d{3})-(\d{3,8})$/; re.exec('010-12345'); // ['010-12345', '010', '12345'] @@ -73,10 +73,10 @@ tags: JavaScript ``` >如果正则表达式中定义了组,就可以在 RegExp 对象上用 exec() 方法提取出子串来。exec() 方法在匹配成功后,会返回一个 Array,第一个元素是正则表达式匹配到的整个字符串,后面的字符串表示匹配成功的子串。 -  正则匹配默认是贪婪匹配,也就是匹配尽可能多的字符,如果需要非贪婪匹配,需要添加 ?。JavaScript的正则表达式还有几个特殊的标志,最常用的是g,表示全局匹配,还可以指定i标志,表示忽略大小写,m标志,表示执行多行匹配。 +正则匹配默认是贪婪匹配,也就是匹配尽可能多的字符,如果需要非贪婪匹配,需要添加 ?。JavaScript的正则表达式还有几个特殊的标志,最常用的是g,表示全局匹配,还可以指定i标志,表示忽略大小写,m标志,表示执行多行匹配。 ## JSON -  JSON 是 JavaScript Object Notation 的缩写,它是一种数据交换格式。在 JSON 中,一共就这么几种数据类型: +JSON 是 JavaScript Object Notation 的缩写,它是一种数据交换格式。在 JSON 中,一共就这么几种数据类型: * number:和 JavaScript 的 number 完全一致; * boolean:就是 JavaScript 的 true 或 false; * string:就是 JavaScript 的 string; @@ -84,10 +84,10 @@ tags: JavaScript * array:就是 JavaScript 的 Array 表示方式—— []; * object:就是 JavaScript 的 { ... } 表示方式。 -  JSON 还规定了字符集必须是 UTF-8,表示多语言就没有问题了。为了统一解析,JSON 的字符串规定必须用双引号,Object 的键也必须用双引号。 +JSON 还规定了字符集必须是 UTF-8,表示多语言就没有问题了。为了统一解析,JSON 的字符串规定必须用双引号,Object 的键也必须用双引号。 -  几乎所有编程语言都有解析 JSON 的库,而在 JavaScript 中,我们可以直接使用 JSON,因为 JavaScript 内置了 JSON 的解析。把任何 JavaScript 对象变成 JSON,就是把这个对象 **序列化** 成一个 JSON 格式的字符串,这样才能够通过网络传递给其他计算机。如果我们收到一个 JSON 格式的字符串,只需要把它 **反序列化** 成一个 JavaScript 对象,就可以在 JavaScript 中直接使用这个对象了。 +几乎所有编程语言都有解析 JSON 的库,而在 JavaScript 中,我们可以直接使用 JSON,因为 JavaScript 内置了 JSON 的解析。把任何 JavaScript 对象变成 JSON,就是把这个对象 **序列化** 成一个 JSON 格式的字符串,这样才能够通过网络传递给其他计算机。如果我们收到一个 JSON 格式的字符串,只需要把它 **反序列化** 成一个 JavaScript 对象,就可以在 JavaScript 中直接使用这个对象了。 -  比如现在我们有一个 xiaoming 对象,那么可以使用 `JSON.stringify(xiaoming)` 来把它序列化成 JSON 格式的字符串。如果我们还想要精确控制如何序列化 xiaoming ,可以给 xiaoming 定义一个 toJSON() 的方法,直接返回 JSON 应该序列化的数据。 +比如现在我们有一个 xiaoming 对象,那么可以使用 `JSON.stringify(xiaoming)` 来把它序列化成 JSON 格式的字符串。如果我们还想要精确控制如何序列化 xiaoming ,可以给 xiaoming 定义一个 toJSON() 的方法,直接返回 JSON 应该序列化的数据。 -  拿到一个 JSON 格式的字符串,我们可以直接用 `JSON.parse()` 把它反序列化成一个JavaScript对象。 +拿到一个 JSON 格式的字符串,我们可以直接用 `JSON.parse()` 把它反序列化成一个JavaScript对象。 diff --git "a/_posts/2017-10-10-JavaScript\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213\344\270\200.md" "b/_posts/2017-10-10-JavaScript\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213\344\270\200.md" index 6328f7a..c6ae270 100644 --- "a/_posts/2017-10-10-JavaScript\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213\344\270\200.md" +++ "b/_posts/2017-10-10-JavaScript\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213\344\270\200.md" @@ -6,7 +6,7 @@ tags: JavaScript --- ## 原型prototype -  在 JavaScript 中没有类和实例的概念,而是通过原型(prototype)来实现面向对象编程。例如: +在 JavaScript 中没有类和实例的概念,而是通过原型(prototype)来实现面向对象编程。例如: ```javascript var Student = { name: 'Robot', @@ -25,9 +25,9 @@ tags: JavaScript xiaoming.name; // '小明' xiaoming.run(); // 小明 is running ... ``` -  JavaScript 的原型链和 Java 的 Class 区别就在,它没有“Class”的概念,所有对象都是实例,所谓继承关系不过是把一个对象的原型指向另一个对象而已。 +JavaScript 的原型链和 Java 的 Class 区别就在,它没有“Class”的概念,所有对象都是实例,所谓继承关系不过是把一个对象的原型指向另一个对象而已。 -  在编写 JavaScript 代码时,不要直接用 `obj.__proto__` 去改变一个对象的原型,并且,低版本的IE也无法使用`__proto__`。`Object.create()`方法可以传入一个原型对象,并创建一个基于该原型的新对象,但是新对象什么属性都没有,因此,我们可以编写一个函数来创建 xiaoming: +在编写 JavaScript 代码时,不要直接用 `obj.__proto__` 去改变一个对象的原型,并且,低版本的IE也无法使用`__proto__`。`Object.create()`方法可以传入一个原型对象,并创建一个基于该原型的新对象,但是新对象什么属性都没有,因此,我们可以编写一个函数来创建 xiaoming: ```javascript // 原型对象: var Student = { @@ -52,16 +52,16 @@ tags: JavaScript ``` ## 创建对象 -  JavaScript 对每个创建的对象都会设置一个原型,指向它的原型对象。当我们用 obj.xxx 访问一个对象的属性时,JavaScript 引擎先在当前对象上查找该属性,如果没有找到,就到其原型对象上找,如果还没有找到,就一直上溯到 Object.prototype 对象,最后,如果还没有找到,就只能返回 undefined。 +JavaScript 对每个创建的对象都会设置一个原型,指向它的原型对象。当我们用 obj.xxx 访问一个对象的属性时,JavaScript 引擎先在当前对象上查找该属性,如果没有找到,就到其原型对象上找,如果还没有找到,就一直上溯到 Object.prototype 对象,最后,如果还没有找到,就只能返回 undefined。 -  例如,创建一个 Array 对象: +例如,创建一个 Array 对象: ```javascript var arr = [1, 2, 3]; ``` -  其原型链是:arr ----> Array.prototype ----> Object.prototype ----> null 。Array.prototype 定义了 indexOf()、shift() 等方法,因此你可以在所有的 Array 对象上直接调用这些方法。 +其原型链是:arr ----> Array.prototype ----> Object.prototype ----> null 。Array.prototype 定义了 indexOf()、shift() 等方法,因此你可以在所有的 Array 对象上直接调用这些方法。 ## 构造函数 -  除了直接用 { ... } 创建一个对象外,JavaScript 还可以用一种构造函数的方法来创建对象。它的用法是,先定义一个构造函数: +除了直接用 { ... } 创建一个对象外,JavaScript 还可以用一种构造函数的方法来创建对象。它的用法是,先定义一个构造函数: ```javascript function Student(name) { this.name = name; @@ -70,17 +70,17 @@ tags: JavaScript } } ``` -  这确实是一个普通函数,但是在 JavaScript 中,可以用关键字 new 来调用这个函数,并返回一个对象: +这确实是一个普通函数,但是在 JavaScript 中,可以用关键字 new 来调用这个函数,并返回一个对象: ```javascript var xiaoming = new Student('小明'); xiaoming.name; // '小明' xiaoming.hello(); // Hello, 小明! ``` -  **注意**,如果不写 new,这就是一个普通函数,它返回 undefined。但是,如果写了 new,它就变成了一个构造函数,它绑定的 this 指向新创建的对象,并默认返回 this,也就是说,不需要在最后写 return this;。 +**注意**,如果不写 new,这就是一个普通函数,它返回 undefined。但是,如果写了 new,它就变成了一个构造函数,它绑定的 this 指向新创建的对象,并默认返回 this,也就是说,不需要在最后写 return this;。 -  新创建的 xiaoming 的原型链是:xiaoming ----> Student.prototype ----> Object.prototype ----> null 。也就是说,xiaoming 的原型指向函数 Student 的原型。如果你又创建了 xiaohong、xiaojun,那么这些对象的原型与 xiaoming 是一样的。 +新创建的 xiaoming 的原型链是:xiaoming ----> Student.prototype ----> Object.prototype ----> null 。也就是说,xiaoming 的原型指向函数 Student 的原型。如果你又创建了 xiaohong、xiaojun,那么这些对象的原型与 xiaoming 是一样的。 -  不过还有一个小问题,注意观察: +不过还有一个小问题,注意观察: ```javascript xiaoming.name; // '小明' xiaohong.name; // '小红' @@ -88,9 +88,9 @@ tags: JavaScript xiaohong.hello; // function: Student.hello() xiaoming.hello === xiaohong.hello; // false ``` -  xiaoming 和 xiaohong 各自的 name 不同,这是对的,否则我们无法区分谁是谁了。xiaoming 和 xiaohong 各自的 hello 是一个函数,但它们是两个不同的函数,虽然函数名称和代码都是相同的!如果我们通过 new Student() 创建了很多对象,这些对象的 hello 函数实际上只需要共享同一个函数就可以了,这样可以节省很多内存。 +xiaoming 和 xiaohong 各自的 name 不同,这是对的,否则我们无法区分谁是谁了。xiaoming 和 xiaohong 各自的 hello 是一个函数,但它们是两个不同的函数,虽然函数名称和代码都是相同的!如果我们通过 new Student() 创建了很多对象,这些对象的 hello 函数实际上只需要共享同一个函数就可以了,这样可以节省很多内存。 -  要让创建的对象共享一个 hello 函数,根据对象的属性查找原则,我们只要把 hello 函数移动到 xiaoming、xiaohong 这些对象共同的原型上就可以了,也就是 Student.prototype。修改代码如下: +要让创建的对象共享一个 hello 函数,根据对象的属性查找原则,我们只要把 hello 函数移动到 xiaoming、xiaohong 这些对象共同的原型上就可以了,也就是 Student.prototype。修改代码如下: ```javascript function Student(name) { this.name = name; diff --git "a/_posts/2017-10-11-JavaScript\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213\344\272\214.md" "b/_posts/2017-10-11-JavaScript\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213\344\272\214.md" index f0951c2..7e857f6 100644 --- "a/_posts/2017-10-11-JavaScript\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213\344\272\214.md" +++ "b/_posts/2017-10-11-JavaScript\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213\344\272\214.md" @@ -6,9 +6,9 @@ tags: JavaScript --- ## 原型继承 -  在传统的基于 Class 的语言如 Java、C++ 中,继承的本质是扩展一个已有的 Class,并生成新的 Subclass。由于这类语言严格区分类和实例,继承实际上是类型的扩展。但是,JavaScript 由于采用原型继承,我们无法直接扩展一个 Class,因为根本不存在 Class 这种类型。 +在传统的基于 Class 的语言如 Java、C++ 中,继承的本质是扩展一个已有的 Class,并生成新的 Subclass。由于这类语言严格区分类和实例,继承实际上是类型的扩展。但是,JavaScript 由于采用原型继承,我们无法直接扩展一个 Class,因为根本不存在 Class 这种类型。 -  但是办法还是有的。我们先回顾 Student 构造函数: +但是办法还是有的。我们先回顾 Student 构造函数: ```javascript function Student(props) { this.name = props.name || 'Unnamed'; @@ -18,7 +18,7 @@ tags: JavaScript alert('Hello, ' + this.name + '!'); } ``` -  现在,我们要基于 Student 扩展出 PrimaryStudent,可以先定义出 PrimaryStudent: +现在,我们要基于 Student 扩展出 PrimaryStudent,可以先定义出 PrimaryStudent: ```javascript function PrimaryStudent(props) { // 调用 Student 构造函数,绑定 this 变量: @@ -26,9 +26,9 @@ tags: JavaScript this.grade = props.grade || 1; } ``` -  但是,调用了 Student 构造函数不等于继承了 Student,PrimaryStudent 创建的对象的原型链是:new PrimaryStudent() ----> PrimaryStudent.prototype ----> Object.prototype ----> null,必须想办法把原型链修改为:new PrimaryStudent() ----> PrimaryStudent.prototype ----> Student.prototype ----> Object.prototype ----> null。这样,原型链对了,继承关系就对了。新的基于 PrimaryStudent 创建的对象不但能调用 PrimaryStudent.prototype 定义的方法,也可以调用 Student.prototype 定义的方法。 +但是,调用了 Student 构造函数不等于继承了 Student,PrimaryStudent 创建的对象的原型链是:new PrimaryStudent() ----> PrimaryStudent.prototype ----> Object.prototype ----> null,必须想办法把原型链修改为:new PrimaryStudent() ----> PrimaryStudent.prototype ----> Student.prototype ----> Object.prototype ----> null。这样,原型链对了,继承关系就对了。新的基于 PrimaryStudent 创建的对象不但能调用 PrimaryStudent.prototype 定义的方法,也可以调用 Student.prototype 定义的方法。 -  我们必须借助一个中间对象来实现正确的原型链,这个中间对象的原型要指向 Student.prototype。为了实现这一点,参考道爷(就是发明 JSON 的那个道格拉斯)的代码,中间对象可以用一个空函数 F 来实现: +我们必须借助一个中间对象来实现正确的原型链,这个中间对象的原型要指向 Student.prototype。为了实现这一点,参考道爷(就是发明 JSON 的那个道格拉斯)的代码,中间对象可以用一个空函数 F 来实现: ```javascript // PrimaryStudent 构造函数: function PrimaryStudent(props) { @@ -72,7 +72,7 @@ tags: JavaScript ``` ## class继承 -  在前面我们看到了 JavaScript 的对象模型是基于原型实现的,缺点是继承的实现需要编写大量代码,并且需要正确实现原型链。有没有更简单的写法?有!新的关键字 class 从 ES6 开始正式被引入到 JavaScript 中,class 的目的就是让定义类更简单。如果用新的 class 关键字来编写前面的 Student,可以这样写: +在前面我们看到了 JavaScript 的对象模型是基于原型实现的,缺点是继承的实现需要编写大量代码,并且需要正确实现原型链。有没有更简单的写法?有!新的关键字 class 从 ES6 开始正式被引入到 JavaScript 中,class 的目的就是让定义类更简单。如果用新的 class 关键字来编写前面的 Student,可以这样写: ```javascript class Student { constructor(name) { @@ -84,9 +84,9 @@ class Student { } } ``` -  比较一下就可以发现,class 的定义包含了构造函数 constructor 和定义在原型对象上的函数 hello()(注意没有 function 关键字),这样就避免了 Student.prototype.hello = function () {...}这样分散的代码。 +比较一下就可以发现,class 的定义包含了构造函数 constructor 和定义在原型对象上的函数 hello()(注意没有 function 关键字),这样就避免了 Student.prototype.hello = function () {...}这样分散的代码。 -  用 class 定义对象的另一个巨大的好处是继承更方便了。想一想我们从 Student 派生一个 PrimaryStudent 需要编写的代码量。现在,原型继承的中间对象,原型对象的构造函数等等都不需要考虑了,直接通过 extends 来实现: +用 class 定义对象的另一个巨大的好处是继承更方便了。想一想我们从 Student 派生一个 PrimaryStudent 需要编写的代码量。现在,原型继承的中间对象,原型对象的构造函数等等都不需要考虑了,直接通过 extends 来实现: ```javascript class PrimaryStudent extends Student { constructor(name, grade) { @@ -99,6 +99,6 @@ class Student { } } ``` -  注意 PrimaryStudent 的定义也是 class 关键字实现的,而 extends 则表示原型链对象来自 Student。子类的构造函数可能会与父类不太相同,例如,PrimaryStudent 需要 name 和 grade 两个参数,并且需要通过 super(name) 来调用父类的构造函数,否则父类的 name 属性无法正常初始化。PrimaryStudent 已经自动获得了父类 Student 的 hello 方法,我们又在子类中定义了新的 myGrade 方法。 +注意 PrimaryStudent 的定义也是 class 关键字实现的,而 extends 则表示原型链对象来自 Student。子类的构造函数可能会与父类不太相同,例如,PrimaryStudent 需要 name 和 grade 两个参数,并且需要通过 super(name) 来调用父类的构造函数,否则父类的 name 属性无法正常初始化。PrimaryStudent 已经自动获得了父类 Student 的 hello 方法,我们又在子类中定义了新的 myGrade 方法。 -  ES6 引入的 class 和原有的 JavaScript 原型继承有什么区别呢?实际上它们没有任何区别,class 的作用就是让 JavaScript 引擎去实现原来需要我们自己编写的原型链代码。简而言之,用 class 的好处就是极大地简化了原型链代码。但是现在用还早了点,因为不是所有的主流浏览器都支持 ES6 的 class。如果一定要现在就用上,就需要一个工具把 class 代码转换为传统的 prototype 代码,可以试试 Babel 这个工具。 +ES6 引入的 class 和原有的 JavaScript 原型继承有什么区别呢?实际上它们没有任何区别,class 的作用就是让 JavaScript 引擎去实现原来需要我们自己编写的原型链代码。简而言之,用 class 的好处就是极大地简化了原型链代码。但是现在用还早了点,因为不是所有的主流浏览器都支持 ES6 的 class。如果一定要现在就用上,就需要一个工具把 class 代码转换为传统的 prototype 代码,可以试试 Babel 这个工具。 diff --git "a/_posts/2017-10-12-JavaScript\346\223\215\344\275\234DOM\344\270\200.md" "b/_posts/2017-10-12-JavaScript\346\223\215\344\275\234DOM\344\270\200.md" index ef0136f..70e9e61 100644 --- "a/_posts/2017-10-12-JavaScript\346\223\215\344\275\234DOM\344\270\200.md" +++ "b/_posts/2017-10-12-JavaScript\346\223\215\344\275\234DOM\344\270\200.md" @@ -8,10 +8,10 @@ tags: JavaScript ## 浏览器对象 ### window -  window 对象不但充当全局作用域,而且表示浏览器窗口。window 对象有 innerWidth 和 innerHeight 属性,可以获取浏览器窗口的内部宽度和高度(内部宽高是指除去菜单栏、工具栏、边框等占位元素后,用于显示网页的净宽高)。对应的,还有一个 outerWidth 和 outerHeight 属性,可以获取浏览器窗口的整个宽高。 +window 对象不但充当全局作用域,而且表示浏览器窗口。window 对象有 innerWidth 和 innerHeight 属性,可以获取浏览器窗口的内部宽度和高度(内部宽高是指除去菜单栏、工具栏、边框等占位元素后,用于显示网页的净宽高)。对应的,还有一个 outerWidth 和 outerHeight 属性,可以获取浏览器窗口的整个宽高。 ### navigator -  navigator 对象表示浏览器的信息,最常用的属性包括: +navigator 对象表示浏览器的信息,最常用的属性包括: * navigator.appName:浏览器名称; * navigator.appVersion:浏览器版本; * navigator.language:浏览器设置的语言; @@ -19,13 +19,13 @@ tags: JavaScript * navigator.userAgent:浏览器设定的User-Agent字符串。 ### screen -  screen 对象表示屏幕的信息,常用的属性有: +screen 对象表示屏幕的信息,常用的属性有: * screen.width:屏幕宽度,以像素为单位; * screen.height:屏幕高度,以像素为单位; * screen.colorDepth:返回颜色位数,如8、16、24。 ### location -  location 对象表示当前页面的 URL 信息。例如,一个完整的 URL:http://www.example.com:8080/path/index.html?a=1&b=2#TOP 。它可以用 location.href 获取。要获得 URL 各个部分的值,可以这么写: +location 对象表示当前页面的 URL 信息。例如,一个完整的 URL:http://www.example.com:8080/path/index.html?a=1&b=2#TOP 。它可以用 location.href 获取。要获得 URL 各个部分的值,可以这么写: ```javascript location.protocol; // 'http' location.host; // 'www.example.com' @@ -34,26 +34,26 @@ tags: JavaScript location.search; // '?a=1&b=2' location.hash; // 'TOP' ``` -  要加载一个新页面,可以调用 location.assign()。如果要重新加载当前页面,调用 location.reload()方法非常方便。 +要加载一个新页面,可以调用 location.assign()。如果要重新加载当前页面,调用 location.reload()方法非常方便。 ### document -  document 对象表示当前页面。由于 HTML 在浏览器中以 DOM 形式表示为树形结构,document 对象就是整个 DOM 树的根节点。比如 document 的 title 属性是从 HTML 文档中的 xxx 读取的,而且可以动态改变。要查找 DOM 树的某个节点,需要从 document 对象开始查找。最常用的查找是根据 ID 和 Tag Name,用 document 对象提供的 getElementById() 和 getElementsByTagName() 可以按ID获得一个 DOM 节点和按 Tag 名称获得一组 DOM 节点。 +document 对象表示当前页面。由于 HTML 在浏览器中以 DOM 形式表示为树形结构,document 对象就是整个 DOM 树的根节点。比如 document 的 title 属性是从 HTML 文档中的 xxx 读取的,而且可以动态改变。要查找 DOM 树的某个节点,需要从 document 对象开始查找。最常用的查找是根据 ID 和 Tag Name,用 document 对象提供的 getElementById() 和 getElementsByTagName() 可以按ID获得一个 DOM 节点和按 Tag 名称获得一组 DOM 节点。 -  document 对象还有一个 cookie 属性,可以获取当前页面的 cookie。cookie 是由服务器发送的 key-value 标示符。因为 HTTP 协议是无状态的,但是服务器要区分到底是哪个用户发过来的请求,就可以用 cookie 来区分。当一个用户成功登录后,服务器发送一个 cookie 给浏览器,例如 user=ABC123XYZ(加密的字符串)...,此后,浏览器访问该网站时,会在请求头附上这个 cookie,服务器根据 cookie 即可区分出用户。cookie 还可以存储网站的一些设置,例如,页面显示的语言等等。由于 JavaScript 能读取到页面的 cookie,而用户的登录信息通常也存在 cookie 中,这就造成了巨大的安全隐患。为了解决这个问题,服务器在设置 cookie 时可以使用 httpOnly,设定了 httpOnly 的 cookie 将不能被 JavaScript 读取。这个行为由浏览器实现,主流浏览器均支持 httpOnly 选项,IE 从 IE6 SP1 开始支持。为了确保安全,服务器端在设置 cookie时,应该始终坚持使用 httpOnly。 +document 对象还有一个 cookie 属性,可以获取当前页面的 cookie。cookie 是由服务器发送的 key-value 标示符。因为 HTTP 协议是无状态的,但是服务器要区分到底是哪个用户发过来的请求,就可以用 cookie 来区分。当一个用户成功登录后,服务器发送一个 cookie 给浏览器,例如 user=ABC123XYZ(加密的字符串)...,此后,浏览器访问该网站时,会在请求头附上这个 cookie,服务器根据 cookie 即可区分出用户。cookie 还可以存储网站的一些设置,例如,页面显示的语言等等。由于 JavaScript 能读取到页面的 cookie,而用户的登录信息通常也存在 cookie 中,这就造成了巨大的安全隐患。为了解决这个问题,服务器在设置 cookie 时可以使用 httpOnly,设定了 httpOnly 的 cookie 将不能被 JavaScript 读取。这个行为由浏览器实现,主流浏览器均支持 httpOnly 选项,IE 从 IE6 SP1 开始支持。为了确保安全,服务器端在设置 cookie时,应该始终坚持使用 httpOnly。 ### history -  history 对象保存了浏览器的历史记录,JavaScript 可以调用 history 对象的 back() 或 forward (),相当于用户点击了浏览器的“后退”或“前进”按钮。这个对象属于历史遗留对象,对于现代 Web 页面来说,由于大量使用 AJAX 和页面交互,简单粗暴地调用 history.back() 可能会让用户感到非常愤怒。新手开始设计 Web 页面时喜欢在登录页登录成功时调用 history.back(),试图回到登录前的页面。这是一种错误的方法。任何情况,你都不应该使用 history 这个对象了。 +history 对象保存了浏览器的历史记录,JavaScript 可以调用 history 对象的 back() 或 forward (),相当于用户点击了浏览器的“后退”或“前进”按钮。这个对象属于历史遗留对象,对于现代 Web 页面来说,由于大量使用 AJAX 和页面交互,简单粗暴地调用 history.back() 可能会让用户感到非常愤怒。新手开始设计 Web 页面时喜欢在登录页登录成功时调用 history.back(),试图回到登录前的页面。这是一种错误的方法。任何情况,你都不应该使用 history 这个对象了。 ## 操作DOM -  由于 HTML 文档被浏览器解析后就是一棵 DOM 树,要改变 HTML 的结构,就需要通过 JavaScript 来操作 DOM。操作一个 DOM 节点实际上就是这么几个操作: +由于 HTML 文档被浏览器解析后就是一棵 DOM 树,要改变 HTML 的结构,就需要通过 JavaScript 来操作 DOM。操作一个 DOM 节点实际上就是这么几个操作: * 更新:更新该 DOM 节点的内容,相当于更新了该 DOM 节点表示的 HTML 的内容; * 遍历:遍历该 DOM 节点下的子节点,以便进行进一步操作; * 添加:在该 DOM 节点下新增一个子节点,相当于动态增加了一个 HTML 节点; * 删除:将该节点从 HTML 中删除,相当于删掉了该 DOM 节点的内容以及它包含的所有子节点。 -  在操作一个 DOM 节点前,我们需要通过各种方式先拿到这个 DOM 节点。最常用的方法是 document.getElementById() 和 document.getElementsByTagName(),以及 CSS 选择器 document.getElementsByClassName()。 +在操作一个 DOM 节点前,我们需要通过各种方式先拿到这个 DOM 节点。最常用的方法是 document.getElementById() 和 document.getElementsByTagName(),以及 CSS 选择器 document.getElementsByClassName()。 -  由于 ID 在 HTML 文档中是唯一的,所以 document.getElementById() 可以直接定位唯一的一个 DOM 节点。document.getElementsByTagName() 和 document.getElementsByClassName() 总是返回一组 DOM 节点。要精确地选择 DOM,可以先定位父节点,再从父节点开始选择,以缩小范围。例如: +由于 ID 在 HTML 文档中是唯一的,所以 document.getElementById() 可以直接定位唯一的一个 DOM 节点。document.getElementsByTagName() 和 document.getElementsByClassName() 总是返回一组 DOM 节点。要精确地选择 DOM,可以先定位父节点,再从父节点开始选择,以缩小范围。例如: ```javascript // 返回 ID 为 'test' 的节点: var test = document.getElementById('test'); @@ -67,7 +67,7 @@ tags: JavaScript var first = test.firstElementChild; var last = test.lastElementChild; ``` -  第二种方法是使用 querySelector() 和 querySelectorAll(),需要了解 selector 语法,然后使用条件来获取节点,更加方便: +第二种方法是使用 querySelector() 和 querySelectorAll(),需要了解 selector 语法,然后使用条件来获取节点,更加方便: ```javascript // 通过 querySelector 获取 ID 为 q1 的节点: var q1 = document.querySelector('#q1'); @@ -75,7 +75,7 @@ tags: JavaScript var ps = q1.querySelectorAll('div.highlighted > p'); ``` -  严格地讲,我们这里的 DOM 节点是指 Element,但是 DOM 节点实际上是 Node,在 HTML 中,Node 包括 Element、Comment、CDATA_SECTION 等很多种,以及根节点 Document 类型,但是,绝大多数时候我们只关心 Element,也就是实际控制页面结构的 Node,其他类型的 Node 忽略即可。根节点 Document 已经自动绑定为全局变量 document。 +严格地讲,我们这里的 DOM 节点是指 Element,但是 DOM 节点实际上是 Node,在 HTML 中,Node 包括 Element、Comment、CDATA_SECTION 等很多种,以及根节点 Document 类型,但是,绝大多数时候我们只关心 Element,也就是实际控制页面结构的 Node,其他类型的 Node 忽略即可。根节点 Document 已经自动绑定为全局变量 document。 ### 删除DOM 删除一个 DOM 节点是操作 DOM 中最简单的,要删除一个节点,首先要获得该节点本身以及它的父节点,然后,调用父节点的 removeChild 把自己删掉: @@ -88,17 +88,17 @@ tags: JavaScript var removed = parent.removeChild(self); removed === self; // true ``` -  注意到删除后的节点虽然不在文档树中了,但其实它还在内存中,可以随时再次被添加到别的位置。当你遍历一个父节点的子节点并进行删除操作时,要注意,children 属性是一个只读属性,并且它在子节点变化时会实时更新。例如,对于如下 HTML 结构: +注意到删除后的节点虽然不在文档树中了,但其实它还在内存中,可以随时再次被添加到别的位置。当你遍历一个父节点的子节点并进行删除操作时,要注意,children 属性是一个只读属性,并且它在子节点变化时会实时更新。例如,对于如下 HTML 结构: ```html

First

Second

``` -  当我们用如下代码删除子节点时: +当我们用如下代码删除子节点时: ```javascript var parent = document.getElementById('parent'); parent.removeChild(parent.children[0]); parent.removeChild(parent.children[1]); // <-- 浏览器报错 ``` -  浏览器报错:parent.children[1] 不是一个有效的节点。原因就在于,当 `

First

` 节点被删除后,parent.children 的节点数量已经从2变为了1,索引[1]已经不存在了。因此,删除多个节点时,要注意 children 属性时刻都在变化。 +浏览器报错:parent.children[1] 不是一个有效的节点。原因就在于,当 `

First

` 节点被删除后,parent.children 的节点数量已经从2变为了1,索引[1]已经不存在了。因此,删除多个节点时,要注意 children 属性时刻都在变化。 diff --git "a/_posts/2017-10-13-JavaScript\346\223\215\344\275\234DOM\344\272\214.md" "b/_posts/2017-10-13-JavaScript\346\223\215\344\275\234DOM\344\272\214.md" index 06c90f5..e7b62dd 100644 --- "a/_posts/2017-10-13-JavaScript\346\223\215\344\275\234DOM\344\272\214.md" +++ "b/_posts/2017-10-13-JavaScript\346\223\215\344\275\234DOM\344\272\214.md" @@ -6,9 +6,9 @@ tags: JavaScript --- ## 更新DOM -  拿到一个 DOM 节点后,我们可以对它进行更新。可以直接修改节点的文本,方法有两种。 +拿到一个 DOM 节点后,我们可以对它进行更新。可以直接修改节点的文本,方法有两种。 -  一种是修改 innerHTML 属性,这个方式非常强大,不但可以修改一个 DOM 节点的文本内容,还可以直接通过 HTML 片段修改 DOM 节点内部的子树: +一种是修改 innerHTML 属性,这个方式非常强大,不但可以修改一个 DOM 节点的文本内容,还可以直接通过 HTML 片段修改 DOM 节点内部的子树: ```javascript // 获取

...

var p = document.getElementById('p-id'); @@ -20,7 +20,7 @@ tags: JavaScript ``` >用 innerHTML 时要注意,是否需要写入 HTML。如果写入的字符串是通过网络拿到了,要注意对字符编码来避免 XSS 攻击。 -  第二种是修改 innerText 或 textContent 属性,这样可以自动对字符串进行 HTML 编码,保证无法设置任何 HTML 标签: +第二种是修改 innerText 或 textContent 属性,这样可以自动对字符串进行 HTML 编码,保证无法设置任何 HTML 标签: ```javascript // 获取

...

var p = document.getElementById('p-id'); @@ -31,7 +31,7 @@ tags: JavaScript ``` >innerText 和 textContent 的区别在于读取属性时,innerText 不返回隐藏元素的文本,而 textContent 返回所有文本。另外注意 IE<9 不支持 textContent。 -  修改 CSS 也是经常需要的操作。DOM 节点的 style 属性对应所有的 CSS,可以直接获取或设置。因为 CSS 允许 font-size 这样的名称,但它并非 JavaScript 有效的属性名,所以需要在 JavaScript 中改写为驼峰式命名 fontSize: +修改 CSS 也是经常需要的操作。DOM 节点的 style 属性对应所有的 CSS,可以直接获取或设置。因为 CSS 允许 font-size 这样的名称,但它并非 JavaScript 有效的属性名,所以需要在 JavaScript 中改写为驼峰式命名 fontSize: ```javascript // 获取

...

var p = document.getElementById('p-id'); @@ -42,9 +42,9 @@ tags: JavaScript ``` ## 插入DOM -  当我们获得了某个 DOM 节点,想在这个 DOM 节点内插入新的 DOM,应该如何做?如果这个 DOM 节点是空的,例如,`
`,那么,直接使用 `innerHTML = 'child'`就可以修改 DOM 节点的内容,相当于“插入”了新的 DOM节点。如果这个 DOM 节点不是空的,那就不能这么做,因为 innerHTML 会直接替换掉原来的所有子节点。 +当我们获得了某个 DOM 节点,想在这个 DOM 节点内插入新的 DOM,应该如何做?如果这个 DOM 节点是空的,例如,`
`,那么,直接使用 `innerHTML = 'child'`就可以修改 DOM 节点的内容,相当于“插入”了新的 DOM节点。如果这个 DOM 节点不是空的,那就不能这么做,因为 innerHTML 会直接替换掉原来的所有子节点。 -  有两个办法可以插入新的节点。一个是使用 appendChild,把一个子节点添加到父节点的最后一个子节点。例如有如下 html 结构: +有两个办法可以插入新的节点。一个是使用 appendChild,把一个子节点添加到父节点的最后一个子节点。例如有如下 html 结构: ```html

JavaScript

@@ -54,14 +54,14 @@ tags: JavaScript

Scheme

``` -  要把 `

JavaScript

` 添加到 `
` 的最后一项: +要把 `

JavaScript

` 添加到 `
` 的最后一项: ```javascript var js = document.getElementById('js'), list = document.getElementById('list'); list.appendChild(js); ``` -  现在,HTML 结构变成了这样: +现在,HTML 结构变成了这样: ```html
@@ -71,7 +71,7 @@ tags: JavaScript

JavaScript

``` -  因为我们插入的 js 节点已经存在于当前的文档树,因此这个节点首先会从原先的位置删除,再插入到新的位置。然而,更多的时候我们会从零创建一个新的节点,然后插入到指定位置,那么可以这样做: +因为我们插入的 js 节点已经存在于当前的文档树,因此这个节点首先会从原先的位置删除,再插入到新的位置。然而,更多的时候我们会从零创建一个新的节点,然后插入到指定位置,那么可以这样做: ```javascript var list = document.getElementById('list'), @@ -80,7 +80,7 @@ tags: JavaScript haskell.innerText = 'Haskell'; list.appendChild(haskell); ``` -  这样我们就动态添加了一个新的节点: +这样我们就动态添加了一个新的节点: ```html
@@ -90,7 +90,7 @@ tags: JavaScript

Haskell

``` -  如果我们要把子节点插入到指定的位置怎么办?可以使用 parentElement.insertBefore(newElement, referenceElement);,子节点会插入到 referenceElement 之前。还是以上面的 HTML 为例,假定我们要把 Haskell 插入到 Python 之前: +如果我们要把子节点插入到指定的位置怎么办?可以使用 parentElement.insertBefore(newElement, referenceElement);,子节点会插入到 referenceElement 之前。还是以上面的 HTML 为例,假定我们要把 Haskell 插入到 Python 之前: ```javascript var list = document.getElementById('list'), diff --git "a/_posts/2017-10-14-JavaScript\346\223\215\344\275\234\350\241\250\345\215\225.md" "b/_posts/2017-10-14-JavaScript\346\223\215\344\275\234\350\241\250\345\215\225.md" index 1b1fc6d..570d7e7 100644 --- "a/_posts/2017-10-14-JavaScript\346\223\215\344\275\234\350\241\250\345\215\225.md" +++ "b/_posts/2017-10-14-JavaScript\346\223\215\344\275\234\350\241\250\345\215\225.md" @@ -6,7 +6,7 @@ tags: JavaScript --- ## 操作表单 -  用 JavaScript 操作表单和操作 DOM 是类似的,因为表单本身也是 DOM 树。不过表单的输入框、下拉框等可以接收用户输入,所以用 JavaScript 来操作表单,可以获得用户输入的内容,或者对一个输入框设置新的内容。HTML 表单的输入控件主要有以下几种: +用 JavaScript 操作表单和操作 DOM 是类似的,因为表单本身也是 DOM 树。不过表单的输入框、下拉框等可以接收用户输入,所以用 JavaScript 来操作表单,可以获得用户输入的内容,或者对一个输入框设置新的内容。HTML 表单的输入控件主要有以下几种: * 文本框,对应的 ``,用于输入文本; * 口令框,对应的 ``,用于输入口令; * 单选框,对应的 ``,用于选择一项; @@ -15,13 +15,13 @@ tags: JavaScript * 隐藏文本,对应的 ``,用户不可见,但表单提交时会把隐藏文本发送到服务器。 ## 获取值 -  如果我们获得了一个 `` 节点的引用,就可以直接调用 value 获得对应的用户输入值: +如果我们获得了一个 `` 节点的引用,就可以直接调用 value 获得对应的用户输入值: ```javascript // var input = document.getElementById('email'); input.value; // '用户输入的值' ``` -  这种方式可以应用于 text、password、hidden 以及 select。但是,对于单选框和复选框,value 属性返回的永远是 HTML 预设的值,而我们需要获得的实际是用户是否“勾上了”选项,所以应该用 checked 判断: +这种方式可以应用于 text、password、hidden 以及 select。但是,对于单选框和复选框,value 属性返回的永远是 HTML 预设的值,而我们需要获得的实际是用户是否“勾上了”选项,所以应该用 checked 判断: ```javascript // // @@ -34,7 +34,7 @@ tags: JavaScript ``` ## 设置值 -  设置值和获取值类似,对于 text、password、hidden 以及 select,直接设置 value 就可以,对于单选框和复选框,设置 checked 为 true 或 false 即可。 +设置值和获取值类似,对于 text、password、hidden 以及 select,直接设置 value 就可以,对于单选框和复选框,设置 checked 为 true 或 false 即可。 ```javascript // var input = document.getElementById('email'); @@ -42,16 +42,16 @@ tags: JavaScript ``` ## HTML5控件 -  HTML5 新增了大量标准控件,常用的包括 date、datetime、datetime-local、color 等,它们都使用 ``标签: +HTML5 新增了大量标准控件,常用的包括 date、datetime、datetime-local、color 等,它们都使用 ``标签: ```html ``` -  不支持 HTML5 的浏览器无法识别新的控件,会把它们当做 type="text" 来显示。支持 HTML5 的浏览器将获得格式化的字符串。例如,type="date" 类型的 input 的 value 将保证是一个有效的 YYYY-MM-DD 格式的日期,或者空字符串。 +不支持 HTML5 的浏览器无法识别新的控件,会把它们当做 type="text" 来显示。支持 HTML5 的浏览器将获得格式化的字符串。例如,type="date" 类型的 input 的 value 将保证是一个有效的 YYYY-MM-DD 格式的日期,或者空字符串。 ## 提交表单 -  最后,JavaScript 可以以两种方式来处理表单的提交(AJAX方式在后面介绍)。方式一是通过 `
` 元素的 submit() 方法提交一个表单,例如,响应一个 `