javascript 学习第二章 语言核心-基础
阿明
撰写于 2024年 05月 18 日

1.3 学习链接

3.10 变量作用域

      var scope = 'global'; //声明一个全局变量
      function checkscope() {
        var scope = 'local'; //声明一个同名的局部变景
        return scope; //返回局部变量的值,而不是全局变量的值
      }
      checkscope(); // =>"local"
        scope = 'global'; //声明一个全局变量,甚至不用var来声明
      function checkscope2() {
        scope = 'local'; //糟糕!我们刚修改了全局变量
        myscope = 'local'; //这里显式地声明了一个新的全局变量
        return [scope, myscope]; //返回两个值
      }

      checkscope2(); //=>["local","local"]:产生了副作用
      scope; // =>"local":全局变量修改了
      myscope; //=>"local":全局命名空间搞乱了
      var scope = 'global scope'; //全局变量
      function checkscope() {
        var scope = 'local scope'; //局部变量
        function nested() {
          var scope = 'nested scope'; //嵌套作用域内的局部变量
          return scope; //返回当前作用域内的值
        }
        return nested();
      }
      checkscope(); //=>“嵌套作用域"

3.10.1 函数作用域和声明提前

在一些类似C语言的编程语言中,花括号内的每一段代码都具有各自的作用域,而且变量在声明它们的代码段之外是不可见的,我们称为块级作用域(blockscope)而JavaScript中没有块级作用域。JavaScript取而代之地使用了函数作用域(functionscope):变量在声明它们的函数体以及这个函数体嵌套的任意函数体内都是有定义的。

//例1

        function test(o) {
        var i = 0; //i在整个函数体内均是有定义的
        if (typeof o == 'object') {
          var j = 0; //j在函数体内是有定义的,不仅仅是在这个代码段内
          for (var k = 0; k < 10; k++) {
            //k在函数体内是有定义的,不仅仅是在循环内
            console.log(k); //输出数字0~9
          }
          console.log(k); // k已经定义了,输出10
        }
        console.log(j); //j已经定义了,但可能没有初始化
      }

//例2.1

     var scope = 'global';
      function f() {
        console.log(scope); //输出"undefined",而不是"global"
        var scope = 'local'; // 变量在这里赋初始值,但变量本身在函数体内任何地方均是有定义的
        console.log(scope); //输出"local”
      }

//例2.2

       function f() {
        var scope; //在函数顶部声明了局部变量
        console.log(scope); //变量存在,但其值是"undefined"
        scope = 'local'; //这里将其初始化并赋值
        console.log(scope); //这里它具有了我们所期望的值
      }

3.10.2 作为属性的变量

当声明一个JavaScript全局变量时,实际上是定义了全局对象的一个属性(见3.5节)。当使用var声明一个变量时,创建的这个属性是不可配置的(见6.7节),也就是说这个变量无法通过delete运算符删除。可能你已经注意到了,如果你没有使用严格模式并给一个未声明的变量赋值的话,JavaScript会自动创建一个全局变量。以这种方式创建的变量是全局对象的正常的可配值属性,并可以删除它们:

      var truevar = 1; //声明一个不可删除的全局变量
      fakevar = 2; // 创建全局对象的一个可删除的属性
      this.fakevar2 = 3; //同上

      delete truevar; //=>false:变量并没有被删除
      delete fakevar; //=>true:变量被删除
      delete this.fakevar2; //=>true:变量被删除

3.10.3作用域

JavaScript 的作用域链是指在执行代码时,JavaScript 引擎在查找变量或函数标识符的过程中所采用的一种机制。作用域链是由代码中嵌套的各个作用域组成的,每个作用域都有一个指向父级作用域的引用,这样就形成了一个链式结构。当在某个作用域中访问一个变量或函数时,JavaScript 引擎首先查找当前作用域,然后沿着作用域链向上逐级查找,直到找到该变量或函数为止,如果在所有作用域中都没有找到,则会抛出引用错误。

下面是一个简单的示例来说明作用域链的概念:

      // 全局作用域
      let globalVariable = 'global';
      function outerFunction() {
        // outerFunction 作用域
        let outerVariable = 'outer';
        function innerFunction() {
          // innerFunction 作用域
          let innerVariable = 'inner';
          console.log(innerVariable); // 输出: "inner"
          console.log(outerVariable); // 输出: "outer"
          console.log(globalVariable); // 输出: "global"
        }
        innerFunction();
      }
      outerFunction();

在这个示例中,innerFunction() 内部作用域可以访问到内部变量 innerVariable、外部变量 outerVariable 和全局变量 globalVariable。当在 innerFunction() 中访问变量时,JavaScript 引擎首先在当前作用域(即 innerFunction)中查找变量,如果找不到,则会沿着作用域链向上查找,直到找到为止。

作用域链的构建是在函数创建时确定的,而不是在函数执行时确定的。这意味着作用域链是静态的,并且不会随着函数的调用而改变。

4.9.3 in运算符

 var point = { x: 1, y: 1 }; // 定义一个对象

   'x' in point; // =>true:对象有一个名为"x"的属性

   'z' in point; // =>false:对象中不存在名为"z"的属性

   'toString' in point; //=>true:对象继承了toString()方法

   var data = [7, 8, 9]; //拥有三个元素的数组

   '0' in data; // =>true:数组包含元素"0”

   1 in data; //=>true:数字转换为字符串

   3 in data; //=>false:没有索引为3的元素

4.10 逻辑表达式

4.10 .1 &&

  var o = { x: 1 };
      var p = null;
      o && o.x; // =>1:0 是真值,因此返回值为o.x
      p && p.x; //=>nu11:p是假值,因此将其返回,而并不去计算p.x

//等价
if(a == b) stop();//只有在a=-b的时候才调用stop()
(a== b)&& stop();
//同上

4.10.2 ||

 // 如果max width已经定义了,直接使用它;否则在preferences对象中查找max width//如果没有定义它,则使用一个写死的常量
      var max = max_width || preferences.max_width || 500;
      //这种惯用法通常用在函数体内,用来给参数提供默认值:

      //将o的成员属性复制到p中,并返回p
      function copy(o, p) {
        p = p || {}; //如果向参数p没有传入任何对象,则使用一个新创建的对象
        //函数体内的主逻辑
      }

4.10.3 !

      //对于p和q取任意值,这两个等式都永远成立
      !(p && q) === !p || !q;
      !(p || q) === !p && !q;

4.12表达式计算eval

  var geval = eval; //使用别名调用eval将是全局eva1
      var x = 'global',
        y = 'global'; //两个全局变量
      function f() {
        // 函数内执行的是局部eva1
        var x = 'local'; // 定义局部变量
        eval("x +='changed';"); //直接eval更改了局部变量的值
        return x; // 返回更改后的局部变量
      }
      function g() {
        // 这个函数内执行了全局eval
        var y = 'local'; // 定义局部变量
        geval("y +='changed';"); //间接调用改变了全局变量的值
        return y; //返回未更改的局部变量
      }
      console.log(f(), x); //更改了局部变量:输出"local changed global":
      console.log(g(), y); // 更改了全局变量:输出"local globalchanged":
  'use strict';

      function strictFunction() {
        var x = 10;

        // 在严格模式下使用 eval()
        eval('var y = 20; console.log(x + y);'); // 在当前作用域中可以访问 x,并且可以定义 y

        console.log(typeof y); // 输出 "undefined"
      }

      strictFunction();

prompt使用

let userInput = prompt("请输入你的名字:");
if (userInput !== null) {
    console.log("你好," + userInput + "!");
    alert(userInput);
} else {
    console.log("你取消了输入。");
}

5.7其他语句类型

6.7 属性的特性

var o = {}; // 创建一个空对象

// 添加一个不可枚举的数据属性 x,并赋值为 1
Object.defineProperty(o, "x", {
    value: 1,
    writable: true,
    enumerable: false,
    configurable: true
});

// 属性是存在的,但不可枚举
console.log(o.x); // => 1
console.log(Object.keys(o)); // => []

// 现在对属性 x 做修改,让它变为只读
Object.defineProperty(o, "x", { writable: false });

// 试图更改这个属性的值
o.x = 2; // 操作失败但不报错,而在严格模式中抛出类型错误异常
o.x; // => 1
Object.defineProperty(o, "x", { value: 2 });
o.x; // => 2
Object.defineProperty(o, "x", {
    get: function() {
        return o;
    }
});
o.x; // => 0
/* 这段代码是一个例子,演示了如何给 Object.prototype 添加一个不可枚举的方法 extend()。这个方法用于将作为参数传入的对象的属性复制到调用它的对象中,并且复制属性的所有特性。*/
Object.defineProperty(Object.prototype, "extend", {
    writable: true,
    enumerable: false,
    configurable: true,
    value: function(o) {
        // 得到所有的自有属性,包括不可枚举属性
        var names = Object.getOwnPropertyNames(o);
        // 遍历它们
        for (var i = 0; i < names.length; i++) {
            // 如果属性已经存在,则跳过
            if (names[i] in this) continue;
            // 获得 o 中的属性的描述符
            var desc = Object.getOwnPropertyDescriptor(o, names[i]);
            // 用它给 this 创建一个属性
            Object.defineProperty(this, names[i], desc);
        }
    }
});

// 定义一个对象 person
var person = {
    name: "John",
    age: 30
};

// 定义一个另一个对象 info
var info = {
    gender: "male",
    occupation: "engineer"
};

// 调用 extend 方法,将 info 对象的属性复制到 person 对象中
person.extend(info);

// 打印 person 对象,可以看到 info 对象的属性已经被复制到了 person 对象中
console.log(person); 

//在 ECMAScript 5 标准被采纳之前,一些 JavaScript 实现提供了非标准的 API 来支持对象直接量语法中的 getter 和 setter,老api不建议使用 。
var obj = {};

// 使用 __defineGetter__() 方法定义属性 x 的 getter 方法
obj.__defineGetter__('x', function() {
    console.log('Getter for x called');
    return this._x;
});

// 使用 __defineSetter__() 方法定义属性 x 的 setter 方法
obj.__defineSetter__('x', function(value) {
    console.log('Setter for x called');
    this._x = value;
});

// 使用 __lookupGetter__() 方法获取属性 x 的 getter 方法
var getter = obj.__lookupGetter__('x');
console.log(getter); // 输出定义的 getter 方法

// 使用 __lookupSetter__() 方法获取属性 x 的 setter 方法
var setter = obj.__lookupSetter__('x');
console.log(setter); // 输出定义的 setter 方法

// 使用定义的 getter 和 setter 方法来设置和获取属性 x 的值
obj.x = 42; // 设置属性 x 的值,触发 setter 方法
console.log(obj.x); // 获取属性 x 的值,触发 getter 方法

6.8.1 原型属性

var p = {x: 1};
var o = Object.create(p);
console.log(p.isPrototypeOf(o)); // true: o inherits from p
console.log(Object.prototype.isPrototypeOf(o)); // true: p inherits from Object.prototype

6.8.2 类属性

/* 定义个函数 */
function classof(o) {
    if (o === null) return "Null";
    if (o === undefined) return "Undefined";
    return Object.prototype.toString.call(o).slice(8, -1);
}

console.log(classof(null)); // Output: "Null"
console.log(classof(undefined)); // Output: "Undefined"
console.log(classof({})); // Output: "Object"
console.log(classof([])); // Output: "Array"
console.log(classof(new Date())); // Output: "Date"
classof(1)// Expected: "Number"
classof("")// Expected: "String"
classof(false)// Expected: "Boolean"
classof(/./)// Expected: "RegExp"
classof(window)// Expected: "Window" (This is a client-side host object)

function f(){}
classof(new f());// Expected: "Object" (This is because `f` is a constructor function)

6.8.3 可扩展性

var o = Object.seal(Object.create(Object.freeze({x: 1}), {y: {value: 2, writable: true}}));

/* 创建一个封闭对象,其中包括一个冻结的原型和一个不可枚举的属性。解释一下这段代码:
Object.freeze({x: 1}):创建一个冻结的对象,其属性 x 的值为 1。
Object.create(Object.freeze({x: 1}), {y: {value: 2, writable: true}}):创建一个新的对象 o,其原型为冻结对象,并添加一个不可枚举属性 y,其值为 2,并且可写。
Object.seal(...):封闭对象 o,使其属性不可配置。
因此,o 是一个封闭对象,其原型为冻结对象 {x: 1},并且有一个不可枚举的属性 y,其值为 2,由于属性的 writable 设置为 true,所以可以修改。
*/

冻结(freeze)一个对象会使得该对象及其所有的属性都不可修改。这意味着无法添加新属性、删除现有属性或修改现有属性的值。冻结对象是不可变的,即使对对象执行的操作也不会改变它的状态。

封闭(seal)一个对象会阻止添加新属性并将所有现有属性标记为不可配置。这意味着可以修改现有属性的值,但不能删除或添加新的属性。封闭对象的属性仍然可以修改(如果它们是可写的),但不能改变其可配置性。

在你提供的代码中,o 是一个封闭对象,其原型是冻结的。这意味着 o 本身以及它的属性 y 都不可配置,但是 y 的值可以修改(因为它是可写的)。然而,原型对象 {x: 1} 是冻结的,因此无法对其进行任何修改。

6.9 序列化对象

o = {x: 1, y: {z: [false, null, ""]}};

s = JSON.stringify(o);
// 将对象 o 转换为 JSON 字符串,存储在变量 s 中

p = JSON.parse(s);
// 将 JSON 字符串 s 解析为对象,存储在变量 p 中,这样 p 就是 o 的深拷贝

8 函数

  • JavaScript函数是参数化的:函数的定义会包括一个称为形参(parameter)的标识符列表,这些参数在函数体中像局部变量一样工作。函数调用会为形参提供实参的值。函数使用它们实参的值来计算返回值,成为该函数调用表达式的值。除了实参之外,每次调用还会拥有另一个值--本次调用的上下文--这就是this关键字的值。
  • 参数有形参(parameter)和实参(argument)的区别,形参相当于函数中定义的变量,实参是在运行时的函数调用时传入的参数。
  • 如果函数挂载在一个对象上,作为对象的一个属性,就称它为对象的方法。当通过这个对象来调用函数时,该对象就是此次调用的上下文(context),也就是该函数的this的值。用于初始化一个新创建的对象的函数称为构造函数(constructor)。6.1节会对构造函数有进一步的讲解,第9章还会再谈到它。

8.5 作为命名空间的函数

/* 当执行 var extend = (function() { ... })(); 后,立即执行函数内部的逻辑会被执行,而返回的函数(或者说是返回的函数对象)就被赋值给了 extend 变量。这样,extend 就成为了一个函数,可以被调用,并且支持参数复制的功能。 */
var extend = (function() {
    for (var p in { toString: null }) {
        // 如果代码执行到这里,说明没有 bug,返回简单版本的 extend 函数
        return function extend(o) {
            for (var i = 1; i < arguments.length; i++) {
                var source = arguments[i];
                for (var prop in source) {
                    o[prop] = source[prop];
                }
            }
            return o;
        };
    }
    // 如果存在 bug,则返回另一个版本的 extend 函数
    return function patchedExtend(o) {
        for (var i = 1; i < arguments.length; i++) {
            var source = arguments[i];
            // 复制所有的可枚举属性
            for (var prop in source) {
                o[prop] = source[prop];
            }
        }
        // 现在检查特殊属性
        for (var j = 0; j < protoprops.length; j++) {
            var prop = protoprops[j];
            if (source.hasOwnProperty(prop)) {
                o[prop] = source[prop];
            }
        }
        return o;
    };

    // 这个列表列出了需要检查的特殊属性
    var protoprops = ["toString", "valueOf", "constructor", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", "toLocaleString"];
})();

// 定义一个目标对象
var target = {};

// 定义一个源对象
var source1 = { name: "Alice", age: 30 };
var source2 = { gender: "Female" };

// 使用 extend 函数将源对象的属性复制到目标对象中
extend(target, source1, source2);

// 现在 target 对象包含了 source1 和 source2 的所有属性
console.log(target); // { name: "Alice", age: 30, gender: "Female" }

8.6 闭包

​ 闭包中的临时变量不能公用一个,否则会覆盖,闭包中的this,如果使用最好复制一下,不然可能会出现意想不到的后果。

8.7 函数属性、方法和构造函数

function trace(o, m) {
    // 在闭包中保存原始方法
    var original = o[m];
    o[m] = function() {
        // 定义新的方法
        console.log(new Date(), "Entering:", m);
        var result = original.apply(this, arguments);
        console.log(new Date(), "Exiting:", m);
        return result;
    };
    // 返回结果
}

// 示例使用方法
var obj = {
    method: function() {
        console.log("Original method");
    }
};

// 添加追踪功能
trace(obj, "method");

// 调用对象方法
obj.method();
  // 示例对象
      var obj = { x: 10 };

      // 示例函数
      function func(y, z) {
        return this.x + y + z;
      }

      // 如果环境中不支持原生的bind方法,我们可以使用自定义的bind方法
      if (!Function.prototype.bind) {
        Function.prototype.bind = function (o /*,args */) {
          // 将this和arguments的值保存至变量中
          var self = this,
            boundArgs = arguments;

          // bind()方法的返回值是一个函数
          return function () {
            // 创建一个实参列表,将传入bind()的第二个及后续的实参都传入这个
            var args = [],
              i;

            for (i = 1; i < boundArgs.length; i++) args.push(boundArgs[i]);

            for (i = 0; i < arguments.length; i++) args.push(arguments[i]);

            // 现在将self作为o的方法来调用,传入这些实参
            return self.apply(o, args);
          };
        };
      }

      // 使用自定义的bind方法将func函数绑定到obj对象上,并指定部分参数为5
      var boundFunc = func.bind(obj, 5);

      // 调用绑定后的函数,并传入另一个参数3
      var result = boundFunc(3);

      console.log(result); // 输出 18

8.8 函数式编程

8.8.2 高阶函数

function mapper(f) {
    return function(a) {
        var result = [];
        for (var i = 0; i < a.length; i++) {
            result.push(f(a[i]));
        }
        return result;
    };
}

var increment = function(x) {
    return x + 1;
};

var incrementer = mapper(increment);
console.log(incrementer([1, 2, 3])); // => [2, 3, 4]
  • 这个函数 mapper(f) 返回了一个新的函数,这个新的函数接受一个数组作为参数 a,然后使用 for 循环遍历数组中的每个元素,对每个元素执行函数 f,并将执行结果添加到 result 数组中,最后返回 result 数组。
function compose(f, g) {
    return function() {
        // 调用 g 函数并将所有参数传递给它
        var gResult = g.apply(this, arguments);
        // 调用 f 函数并将 g 函数的返回值作为参数传递给它
        return f.call(this, gResult);
    };
}

var square = function(x) {
    return x * x;
};

var sum = function(x, y) {
    return x + y;
};

var squareofsum = compose(square, sum);

console.log(squareofsum(2, 3)); // 输出 25
  • 在这个例子中,我们定义了两个函数 squaresum。然后我们使用 compose 函数将它们组合起来,创建了一个新的函数 squareofsum,这个新的函数首先将两个参数传递给 sum 函数,然后将 sum 函数的返回值作为参数传递给 square 函数,最后返回 square 函数的返回值。因此,调用 squareofsum(2, 3) 返回的结果是 square(sum(2, 3)),即 (2 + 3) * (2 + 3) = 25

8.8.3 不完全函数

  • 就是把函数拆成一段段的执行
 // 实现一个工具函数,将类数组对象(或对象)转换为真正的数组
      function array(a, n) {
        return Array.prototype.slice.call(a, n || 0);
      }

      // partialLeft: 创建一个偏函数,固定左侧的参数
      function partialLeft(f /*,...*/) {
        var args = arguments;
        return function () {
          var a = array(args, 1);
          a = a.concat(array(arguments));
          return f.apply(this, a);
        };
      }

      // partialRight: 创建一个偏函数,固定右侧的参数
      function partialRight(f /*,...*/) {
        var args = arguments;
        return function () {
          debugger;
          var a = array(arguments);
          a = a.concat(array(args, 1));
          return f.apply(this, a);
        };
      }

      // partial: 创建一个更加通用的偏函数,可以填充参数列表中的 undefined 值
      function partial(f /*,...*/) {
        var args = arguments;
        return function () {
          var a = array(args, 1),
            i = 0,
            j = 0;
          for (; i < a.length; i++) {
            if (a[i] === undefined) {
              a[i] = arguments[j++];
            }
          }
          a = a.concat(array(arguments, j));
          return f.apply(this, a);
        };
      }
      // 这个函数带有三个实参
      var f = function (x, y, z) {
        return x * (y - z);
      };

      // 注意这三个不完全调用之间的区别
      partialLeft(f, 2)(3, 4); // => -2: 绑定第一个实参: 2 * (3 - 4)
      partialRight(f, 2)(3, 4); // => 6: 绑定最后一个实参: 3 * (4 - 2)
      partial(f, undefined, 2)(3, 4); // => -6: 绑定中间的实参: 3 * (2 - 4)

      // 创建一个偏函数,固定左侧的参数为 1
      var increment = partialLeft(sum, 1);

      // 创建一个偏函数,固定右侧的参数为 1/3
      var cuberoot = partialRight(Math.pow, 1 / 3);

      // 创建一个偏函数,将字符串的第一个字符作为新函数的参数
      String.prototype.first = partial(String.prototype.charAt, 0);

      // 创建一个偏函数,将字符串的最后一个字符作为新函数的参数
      String.prototype.last = partial(String.prototype.substr, -1, 1);

      var not = partialLeft(compose, function (x) {
        return !x;
      });

      var even = function (x) {
        return x % 2 === 0;
      };

      var odd = not(even);

      var isNumber = not(isNaN);

      // 我们要处理的数据
      var data = [1, 1, 3, 5, 5];

      // 两个初等函数
      var sum = function (x, y) {
        return x + y;
      };

      var product = function (x, y) {
        return x * y;
      };

      // 定义其他函数
      var neg = partial(product, -1);
      var square = partial(Math.pow, undefined, 2);
      var sqrt = partial(Math.pow, undefined, 0.5);
      var reciprocal = partial(Math.pow, undefined, -1);

      // 现在计算平均值和标准差,所有的函数调用都不带运算符
      // 这段代码看起来很像lisp代码 这个是伪代码

      var mean = product(reduce(data, sum), reciprocal(data.length));
      var stddev = sqrt(
        product(
          reduce(map(data, compose(square, partial(sum, neg(mean)))), sum),
          reciprocal(sum(data.length, -1))
        )
      );

8.8.4 记忆

function memorize(f) {
    var cache = {};

    return function() {
        var key = arguments.length + Array.prototype.join.call(arguments, ",");
        
        if (key in cache) {
            return cache[key];
        } else {
            return cache[key] = f.apply(this, arguments);
        }
    };
}
  • 通过这样的实现,我们为任意函数添加了记忆功能,以提高函数的性能。记忆功能会在函数的每次调用时检查参数是否已经被缓存过,如果是,则直接返回缓存的结果,避免了重复计算
// 返回两个整数的最大公约数
// 使用欧几里德算法:http://en.wikipedia.org/wiki/Euclidean_algorithm
function gcd(a, b) {
    //这里省略对a和b的类型检查
    //临时变量用来存储交换数值
    var t;

    // 确保 a >= b
    if (a < b) {
        t = b;
        b = a;
        a = t;
    }
    while (b !== 0) {
        t = b;
        b = a % b;
        a = t;
    }
    //这是求最大公约数的欧几里德算法
    return a;
}

var gcdmemo = memorize(gcd);
gcdmemo(85, 187); //=>17

//注意,当我们写一个递归函数时,往往需要实现记忆功能
//我们更希望调用实现了记忆功能的递归函数,而不是原递归函数
var factorial = memorize(function(n) {
    return (n <= 1) ? 1 : n * factorial(n - 1);
});

factorial(5); //=>120.对于4~1的值也有缓存

9.5 类和类型

// 定义一个名为 speak 的函数,它接受一个可以“说话”的对象作为参数
function speak(animal) {
    if (animal && typeof animal.speak === 'function') {
        animal.speak(); // 调用对象的 speak 方法
    } else {
        console.log("This object cannot speak.");
    }
}

// 定义一个 duck 对象,它具有 speak 方法
var duck = {
    speak: function() {
        console.log("Quack!");
    }
};

// 定义一个 dog 对象,它也具有 speak 方法
var dog = {
    speak: function() {
        console.log("Woof!");
    }
};

// 调用 speak 函数,分别传入 duck 和 dog 对象
speak(duck); // 输出 "Quack!"
speak(dog);  // 输出 "Woof!"
  • 鸭式辩型(Duck Typing)是一种动态类型的编程风格,它关注对象的行为而不是对象的类型。这个术语来自于一句俗语:“如果它看起来像鸭子,叫起来像鸭子,那么它就是鸭子。”

    在鸭式辩型中,一个对象的类型或类并不重要,重要的是它具有的方法或属性。如果一个对象具有特定的方法或属性,那么它就可以被视为具有特定类型的对象,无论它是如何实现的。

10. 正则表达式的模式匹配

var regex = /Java(?!script)([A-Z]\w*)/;

console.log(regex.test("JavaBeans")); // 输出 true,因为 "Java" 后跟一个大写字母和任意多个字母或数字
console.log(regex.test("Javanese")); // 输出 false,因为 "Java" 后面跟着 "nese",不符合后面不能跟着 "script" 的条件
console.log(regex.test("JavaScript")); // 输出 true,因为 "Java" 后跟着 "Script",但符合后面不能跟着 "script" 的条件
console.log(regex.test("Javascripter")); // 输出 false,因为 "Java" 后跟着 "scripter",不符合后面不能跟着 "script" 的条件

var url = /(\w+):\/\/([\w.]+)\/(\S*)/;
var text = "Visit my blog at http://www.example.com/~david";
var result = text.match(url);

if (result !== null) {
    var fullurl = result[0]; // 完整URL,包含 "http://www.example.com/~david"
    var protocol = result[1]; // 协议部分,包含 "http"
    var host = result[2]; // 主机部分,包含 "www.example.com"
    var path = result[3]; // 路径部分,包含 "/~david"
    
    console.log("Full URL:", fullurl);
    console.log("Protocol:", protocol);
    console.log("Host:", host);
    console.log("Path:", path);
} else {
    console.log("No match found.");
}

"1, 2, 3, 4, 5".split(/\s*,\s*/); // 返回 ["1", "2", "3", "4", "5"]

11. javascript的子集和扩展

javascript 学习第二章 语言核心-基础

1.3 学习链接

3.10 变量作用域

      var scope = 'global'; //声明一个全局变量
      function checkscope() {
        var scope = 'local'; //声明一个同名的局部变景
        return scope; //返回局部变量的值,而不是全局变量的值
      }
      checkscope(); // =>"local"
        scope = 'global'; //声明一个全局变量,甚至不用var来声明
      function checkscope2() {
        scope = 'local'; //糟糕!我们刚修改了全局变量
        myscope = 'local'; //这里显式地声明了一个新的全局变量
        return [scope, myscope]; //返回两个值
      }

      checkscope2(); //=>["local","local"]:产生了副作用
      scope; // =>"local":全局变量修改了
      myscope; //=>"local":全局命名空间搞乱了
      var scope = 'global scope'; //全局变量
      function checkscope() {
        var scope = 'local scope'; //局部变量
        function nested() {
          var scope = 'nested scope'; //嵌套作用域内的局部变量
          return scope; //返回当前作用域内的值
        }
        return nested();
      }
      checkscope(); //=>“嵌套作用域"

3.10.1 函数作用域和声明提前

在一些类似C语言的编程语言中,花括号内的每一段代码都具有各自的作用域,而且变量在声明它们的代码段之外是不可见的,我们称为块级作用域(blockscope)而JavaScript中没有块级作用域。JavaScript取而代之地使用了函数作用域(functionscope):变量在声明它们的函数体以及这个函数体嵌套的任意函数体内都是有定义的。

//例1

        function test(o) {
        var i = 0; //i在整个函数体内均是有定义的
        if (typeof o == 'object') {
          var j = 0; //j在函数体内是有定义的,不仅仅是在这个代码段内
          for (var k = 0; k < 10; k++) {
            //k在函数体内是有定义的,不仅仅是在循环内
            console.log(k); //输出数字0~9
          }
          console.log(k); // k已经定义了,输出10
        }
        console.log(j); //j已经定义了,但可能没有初始化
      }

//例2.1

     var scope = 'global';
      function f() {
        console.log(scope); //输出"undefined",而不是"global"
        var scope = 'local'; // 变量在这里赋初始值,但变量本身在函数体内任何地方均是有定义的
        console.log(scope); //输出"local”
      }

//例2.2

       function f() {
        var scope; //在函数顶部声明了局部变量
        console.log(scope); //变量存在,但其值是"undefined"
        scope = 'local'; //这里将其初始化并赋值
        console.log(scope); //这里它具有了我们所期望的值
      }

3.10.2 作为属性的变量

当声明一个JavaScript全局变量时,实际上是定义了全局对象的一个属性(见3.5节)。当使用var声明一个变量时,创建的这个属性是不可配置的(见6.7节),也就是说这个变量无法通过delete运算符删除。可能你已经注意到了,如果你没有使用严格模式并给一个未声明的变量赋值的话,JavaScript会自动创建一个全局变量。以这种方式创建的变量是全局对象的正常的可配值属性,并可以删除它们:

      var truevar = 1; //声明一个不可删除的全局变量
      fakevar = 2; // 创建全局对象的一个可删除的属性
      this.fakevar2 = 3; //同上

      delete truevar; //=>false:变量并没有被删除
      delete fakevar; //=>true:变量被删除
      delete this.fakevar2; //=>true:变量被删除

3.10.3作用域

JavaScript 的作用域链是指在执行代码时,JavaScript 引擎在查找变量或函数标识符的过程中所采用的一种机制。作用域链是由代码中嵌套的各个作用域组成的,每个作用域都有一个指向父级作用域的引用,这样就形成了一个链式结构。当在某个作用域中访问一个变量或函数时,JavaScript 引擎首先查找当前作用域,然后沿着作用域链向上逐级查找,直到找到该变量或函数为止,如果在所有作用域中都没有找到,则会抛出引用错误。

下面是一个简单的示例来说明作用域链的概念:

      // 全局作用域
      let globalVariable = 'global';
      function outerFunction() {
        // outerFunction 作用域
        let outerVariable = 'outer';
        function innerFunction() {
          // innerFunction 作用域
          let innerVariable = 'inner';
          console.log(innerVariable); // 输出: "inner"
          console.log(outerVariable); // 输出: "outer"
          console.log(globalVariable); // 输出: "global"
        }
        innerFunction();
      }
      outerFunction();

在这个示例中,innerFunction() 内部作用域可以访问到内部变量 innerVariable、外部变量 outerVariable 和全局变量 globalVariable。当在 innerFunction() 中访问变量时,JavaScript 引擎首先在当前作用域(即 innerFunction)中查找变量,如果找不到,则会沿着作用域链向上查找,直到找到为止。

作用域链的构建是在函数创建时确定的,而不是在函数执行时确定的。这意味着作用域链是静态的,并且不会随着函数的调用而改变。

4.9.3 in运算符

 var point = { x: 1, y: 1 }; // 定义一个对象

   'x' in point; // =>true:对象有一个名为"x"的属性

   'z' in point; // =>false:对象中不存在名为"z"的属性

   'toString' in point; //=>true:对象继承了toString()方法

   var data = [7, 8, 9]; //拥有三个元素的数组

   '0' in data; // =>true:数组包含元素"0”

   1 in data; //=>true:数字转换为字符串

   3 in data; //=>false:没有索引为3的元素

4.10 逻辑表达式

4.10 .1 &&

  var o = { x: 1 };
      var p = null;
      o && o.x; // =>1:0 是真值,因此返回值为o.x
      p && p.x; //=>nu11:p是假值,因此将其返回,而并不去计算p.x

//等价
if(a == b) stop();//只有在a=-b的时候才调用stop()
(a== b)&& stop();
//同上

4.10.2 ||

 // 如果max width已经定义了,直接使用它;否则在preferences对象中查找max width//如果没有定义它,则使用一个写死的常量
      var max = max_width || preferences.max_width || 500;
      //这种惯用法通常用在函数体内,用来给参数提供默认值:

      //将o的成员属性复制到p中,并返回p
      function copy(o, p) {
        p = p || {}; //如果向参数p没有传入任何对象,则使用一个新创建的对象
        //函数体内的主逻辑
      }

4.10.3 !

      //对于p和q取任意值,这两个等式都永远成立
      !(p && q) === !p || !q;
      !(p || q) === !p && !q;

4.12表达式计算eval

  var geval = eval; //使用别名调用eval将是全局eva1
      var x = 'global',
        y = 'global'; //两个全局变量
      function f() {
        // 函数内执行的是局部eva1
        var x = 'local'; // 定义局部变量
        eval("x +='changed';"); //直接eval更改了局部变量的值
        return x; // 返回更改后的局部变量
      }
      function g() {
        // 这个函数内执行了全局eval
        var y = 'local'; // 定义局部变量
        geval("y +='changed';"); //间接调用改变了全局变量的值
        return y; //返回未更改的局部变量
      }
      console.log(f(), x); //更改了局部变量:输出"local changed global":
      console.log(g(), y); // 更改了全局变量:输出"local globalchanged":
  'use strict';

      function strictFunction() {
        var x = 10;

        // 在严格模式下使用 eval()
        eval('var y = 20; console.log(x + y);'); // 在当前作用域中可以访问 x,并且可以定义 y

        console.log(typeof y); // 输出 "undefined"
      }

      strictFunction();

prompt使用

let userInput = prompt("请输入你的名字:");
if (userInput !== null) {
    console.log("你好," + userInput + "!");
    alert(userInput);
} else {
    console.log("你取消了输入。");
}

5.7其他语句类型

6.7 属性的特性

var o = {}; // 创建一个空对象

// 添加一个不可枚举的数据属性 x,并赋值为 1
Object.defineProperty(o, "x", {
    value: 1,
    writable: true,
    enumerable: false,
    configurable: true
});

// 属性是存在的,但不可枚举
console.log(o.x); // => 1
console.log(Object.keys(o)); // => []

// 现在对属性 x 做修改,让它变为只读
Object.defineProperty(o, "x", { writable: false });

// 试图更改这个属性的值
o.x = 2; // 操作失败但不报错,而在严格模式中抛出类型错误异常
o.x; // => 1
Object.defineProperty(o, "x", { value: 2 });
o.x; // => 2
Object.defineProperty(o, "x", {
    get: function() {
        return o;
    }
});
o.x; // => 0
/* 这段代码是一个例子,演示了如何给 Object.prototype 添加一个不可枚举的方法 extend()。这个方法用于将作为参数传入的对象的属性复制到调用它的对象中,并且复制属性的所有特性。*/
Object.defineProperty(Object.prototype, "extend", {
    writable: true,
    enumerable: false,
    configurable: true,
    value: function(o) {
        // 得到所有的自有属性,包括不可枚举属性
        var names = Object.getOwnPropertyNames(o);
        // 遍历它们
        for (var i = 0; i < names.length; i++) {
            // 如果属性已经存在,则跳过
            if (names[i] in this) continue;
            // 获得 o 中的属性的描述符
            var desc = Object.getOwnPropertyDescriptor(o, names[i]);
            // 用它给 this 创建一个属性
            Object.defineProperty(this, names[i], desc);
        }
    }
});

// 定义一个对象 person
var person = {
    name: "John",
    age: 30
};

// 定义一个另一个对象 info
var info = {
    gender: "male",
    occupation: "engineer"
};

// 调用 extend 方法,将 info 对象的属性复制到 person 对象中
person.extend(info);

// 打印 person 对象,可以看到 info 对象的属性已经被复制到了 person 对象中
console.log(person); 

//在 ECMAScript 5 标准被采纳之前,一些 JavaScript 实现提供了非标准的 API 来支持对象直接量语法中的 getter 和 setter,老api不建议使用 。
var obj = {};

// 使用 __defineGetter__() 方法定义属性 x 的 getter 方法
obj.__defineGetter__('x', function() {
    console.log('Getter for x called');
    return this._x;
});

// 使用 __defineSetter__() 方法定义属性 x 的 setter 方法
obj.__defineSetter__('x', function(value) {
    console.log('Setter for x called');
    this._x = value;
});

// 使用 __lookupGetter__() 方法获取属性 x 的 getter 方法
var getter = obj.__lookupGetter__('x');
console.log(getter); // 输出定义的 getter 方法

// 使用 __lookupSetter__() 方法获取属性 x 的 setter 方法
var setter = obj.__lookupSetter__('x');
console.log(setter); // 输出定义的 setter 方法

// 使用定义的 getter 和 setter 方法来设置和获取属性 x 的值
obj.x = 42; // 设置属性 x 的值,触发 setter 方法
console.log(obj.x); // 获取属性 x 的值,触发 getter 方法

6.8.1 原型属性

var p = {x: 1};
var o = Object.create(p);
console.log(p.isPrototypeOf(o)); // true: o inherits from p
console.log(Object.prototype.isPrototypeOf(o)); // true: p inherits from Object.prototype

6.8.2 类属性

/* 定义个函数 */
function classof(o) {
    if (o === null) return "Null";
    if (o === undefined) return "Undefined";
    return Object.prototype.toString.call(o).slice(8, -1);
}

console.log(classof(null)); // Output: "Null"
console.log(classof(undefined)); // Output: "Undefined"
console.log(classof({})); // Output: "Object"
console.log(classof([])); // Output: "Array"
console.log(classof(new Date())); // Output: "Date"
classof(1)// Expected: "Number"
classof("")// Expected: "String"
classof(false)// Expected: "Boolean"
classof(/./)// Expected: "RegExp"
classof(window)// Expected: "Window" (This is a client-side host object)

function f(){}
classof(new f());// Expected: "Object" (This is because `f` is a constructor function)

6.8.3 可扩展性

var o = Object.seal(Object.create(Object.freeze({x: 1}), {y: {value: 2, writable: true}}));

/* 创建一个封闭对象,其中包括一个冻结的原型和一个不可枚举的属性。解释一下这段代码:
Object.freeze({x: 1}):创建一个冻结的对象,其属性 x 的值为 1。
Object.create(Object.freeze({x: 1}), {y: {value: 2, writable: true}}):创建一个新的对象 o,其原型为冻结对象,并添加一个不可枚举属性 y,其值为 2,并且可写。
Object.seal(...):封闭对象 o,使其属性不可配置。
因此,o 是一个封闭对象,其原型为冻结对象 {x: 1},并且有一个不可枚举的属性 y,其值为 2,由于属性的 writable 设置为 true,所以可以修改。
*/

冻结(freeze)一个对象会使得该对象及其所有的属性都不可修改。这意味着无法添加新属性、删除现有属性或修改现有属性的值。冻结对象是不可变的,即使对对象执行的操作也不会改变它的状态。

封闭(seal)一个对象会阻止添加新属性并将所有现有属性标记为不可配置。这意味着可以修改现有属性的值,但不能删除或添加新的属性。封闭对象的属性仍然可以修改(如果它们是可写的),但不能改变其可配置性。

在你提供的代码中,o 是一个封闭对象,其原型是冻结的。这意味着 o 本身以及它的属性 y 都不可配置,但是 y 的值可以修改(因为它是可写的)。然而,原型对象 {x: 1} 是冻结的,因此无法对其进行任何修改。

6.9 序列化对象

o = {x: 1, y: {z: [false, null, ""]}};

s = JSON.stringify(o);
// 将对象 o 转换为 JSON 字符串,存储在变量 s 中

p = JSON.parse(s);
// 将 JSON 字符串 s 解析为对象,存储在变量 p 中,这样 p 就是 o 的深拷贝

8 函数

  • JavaScript函数是参数化的:函数的定义会包括一个称为形参(parameter)的标识符列表,这些参数在函数体中像局部变量一样工作。函数调用会为形参提供实参的值。函数使用它们实参的值来计算返回值,成为该函数调用表达式的值。除了实参之外,每次调用还会拥有另一个值--本次调用的上下文--这就是this关键字的值。
  • 参数有形参(parameter)和实参(argument)的区别,形参相当于函数中定义的变量,实参是在运行时的函数调用时传入的参数。
  • 如果函数挂载在一个对象上,作为对象的一个属性,就称它为对象的方法。当通过这个对象来调用函数时,该对象就是此次调用的上下文(context),也就是该函数的this的值。用于初始化一个新创建的对象的函数称为构造函数(constructor)。6.1节会对构造函数有进一步的讲解,第9章还会再谈到它。

8.5 作为命名空间的函数

/* 当执行 var extend = (function() { ... })(); 后,立即执行函数内部的逻辑会被执行,而返回的函数(或者说是返回的函数对象)就被赋值给了 extend 变量。这样,extend 就成为了一个函数,可以被调用,并且支持参数复制的功能。 */
var extend = (function() {
    for (var p in { toString: null }) {
        // 如果代码执行到这里,说明没有 bug,返回简单版本的 extend 函数
        return function extend(o) {
            for (var i = 1; i < arguments.length; i++) {
                var source = arguments[i];
                for (var prop in source) {
                    o[prop] = source[prop];
                }
            }
            return o;
        };
    }
    // 如果存在 bug,则返回另一个版本的 extend 函数
    return function patchedExtend(o) {
        for (var i = 1; i < arguments.length; i++) {
            var source = arguments[i];
            // 复制所有的可枚举属性
            for (var prop in source) {
                o[prop] = source[prop];
            }
        }
        // 现在检查特殊属性
        for (var j = 0; j < protoprops.length; j++) {
            var prop = protoprops[j];
            if (source.hasOwnProperty(prop)) {
                o[prop] = source[prop];
            }
        }
        return o;
    };

    // 这个列表列出了需要检查的特殊属性
    var protoprops = ["toString", "valueOf", "constructor", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", "toLocaleString"];
})();

// 定义一个目标对象
var target = {};

// 定义一个源对象
var source1 = { name: "Alice", age: 30 };
var source2 = { gender: "Female" };

// 使用 extend 函数将源对象的属性复制到目标对象中
extend(target, source1, source2);

// 现在 target 对象包含了 source1 和 source2 的所有属性
console.log(target); // { name: "Alice", age: 30, gender: "Female" }

8.6 闭包

​ 闭包中的临时变量不能公用一个,否则会覆盖,闭包中的this,如果使用最好复制一下,不然可能会出现意想不到的后果。

8.7 函数属性、方法和构造函数

function trace(o, m) {
    // 在闭包中保存原始方法
    var original = o[m];
    o[m] = function() {
        // 定义新的方法
        console.log(new Date(), "Entering:", m);
        var result = original.apply(this, arguments);
        console.log(new Date(), "Exiting:", m);
        return result;
    };
    // 返回结果
}

// 示例使用方法
var obj = {
    method: function() {
        console.log("Original method");
    }
};

// 添加追踪功能
trace(obj, "method");

// 调用对象方法
obj.method();
  // 示例对象
      var obj = { x: 10 };

      // 示例函数
      function func(y, z) {
        return this.x + y + z;
      }

      // 如果环境中不支持原生的bind方法,我们可以使用自定义的bind方法
      if (!Function.prototype.bind) {
        Function.prototype.bind = function (o /*,args */) {
          // 将this和arguments的值保存至变量中
          var self = this,
            boundArgs = arguments;

          // bind()方法的返回值是一个函数
          return function () {
            // 创建一个实参列表,将传入bind()的第二个及后续的实参都传入这个
            var args = [],
              i;

            for (i = 1; i < boundArgs.length; i++) args.push(boundArgs[i]);

            for (i = 0; i < arguments.length; i++) args.push(arguments[i]);

            // 现在将self作为o的方法来调用,传入这些实参
            return self.apply(o, args);
          };
        };
      }

      // 使用自定义的bind方法将func函数绑定到obj对象上,并指定部分参数为5
      var boundFunc = func.bind(obj, 5);

      // 调用绑定后的函数,并传入另一个参数3
      var result = boundFunc(3);

      console.log(result); // 输出 18

8.8 函数式编程

8.8.2 高阶函数

function mapper(f) {
    return function(a) {
        var result = [];
        for (var i = 0; i < a.length; i++) {
            result.push(f(a[i]));
        }
        return result;
    };
}

var increment = function(x) {
    return x + 1;
};

var incrementer = mapper(increment);
console.log(incrementer([1, 2, 3])); // => [2, 3, 4]
  • 这个函数 mapper(f) 返回了一个新的函数,这个新的函数接受一个数组作为参数 a,然后使用 for 循环遍历数组中的每个元素,对每个元素执行函数 f,并将执行结果添加到 result 数组中,最后返回 result 数组。
function compose(f, g) {
    return function() {
        // 调用 g 函数并将所有参数传递给它
        var gResult = g.apply(this, arguments);
        // 调用 f 函数并将 g 函数的返回值作为参数传递给它
        return f.call(this, gResult);
    };
}

var square = function(x) {
    return x * x;
};

var sum = function(x, y) {
    return x + y;
};

var squareofsum = compose(square, sum);

console.log(squareofsum(2, 3)); // 输出 25
  • 在这个例子中,我们定义了两个函数 squaresum。然后我们使用 compose 函数将它们组合起来,创建了一个新的函数 squareofsum,这个新的函数首先将两个参数传递给 sum 函数,然后将 sum 函数的返回值作为参数传递给 square 函数,最后返回 square 函数的返回值。因此,调用 squareofsum(2, 3) 返回的结果是 square(sum(2, 3)),即 (2 + 3) * (2 + 3) = 25

8.8.3 不完全函数

  • 就是把函数拆成一段段的执行
 // 实现一个工具函数,将类数组对象(或对象)转换为真正的数组
      function array(a, n) {
        return Array.prototype.slice.call(a, n || 0);
      }

      // partialLeft: 创建一个偏函数,固定左侧的参数
      function partialLeft(f /*,...*/) {
        var args = arguments;
        return function () {
          var a = array(args, 1);
          a = a.concat(array(arguments));
          return f.apply(this, a);
        };
      }

      // partialRight: 创建一个偏函数,固定右侧的参数
      function partialRight(f /*,...*/) {
        var args = arguments;
        return function () {
          debugger;
          var a = array(arguments);
          a = a.concat(array(args, 1));
          return f.apply(this, a);
        };
      }

      // partial: 创建一个更加通用的偏函数,可以填充参数列表中的 undefined 值
      function partial(f /*,...*/) {
        var args = arguments;
        return function () {
          var a = array(args, 1),
            i = 0,
            j = 0;
          for (; i < a.length; i++) {
            if (a[i] === undefined) {
              a[i] = arguments[j++];
            }
          }
          a = a.concat(array(arguments, j));
          return f.apply(this, a);
        };
      }
      // 这个函数带有三个实参
      var f = function (x, y, z) {
        return x * (y - z);
      };

      // 注意这三个不完全调用之间的区别
      partialLeft(f, 2)(3, 4); // => -2: 绑定第一个实参: 2 * (3 - 4)
      partialRight(f, 2)(3, 4); // => 6: 绑定最后一个实参: 3 * (4 - 2)
      partial(f, undefined, 2)(3, 4); // => -6: 绑定中间的实参: 3 * (2 - 4)

      // 创建一个偏函数,固定左侧的参数为 1
      var increment = partialLeft(sum, 1);

      // 创建一个偏函数,固定右侧的参数为 1/3
      var cuberoot = partialRight(Math.pow, 1 / 3);

      // 创建一个偏函数,将字符串的第一个字符作为新函数的参数
      String.prototype.first = partial(String.prototype.charAt, 0);

      // 创建一个偏函数,将字符串的最后一个字符作为新函数的参数
      String.prototype.last = partial(String.prototype.substr, -1, 1);

      var not = partialLeft(compose, function (x) {
        return !x;
      });

      var even = function (x) {
        return x % 2 === 0;
      };

      var odd = not(even);

      var isNumber = not(isNaN);

      // 我们要处理的数据
      var data = [1, 1, 3, 5, 5];

      // 两个初等函数
      var sum = function (x, y) {
        return x + y;
      };

      var product = function (x, y) {
        return x * y;
      };

      // 定义其他函数
      var neg = partial(product, -1);
      var square = partial(Math.pow, undefined, 2);
      var sqrt = partial(Math.pow, undefined, 0.5);
      var reciprocal = partial(Math.pow, undefined, -1);

      // 现在计算平均值和标准差,所有的函数调用都不带运算符
      // 这段代码看起来很像lisp代码 这个是伪代码

      var mean = product(reduce(data, sum), reciprocal(data.length));
      var stddev = sqrt(
        product(
          reduce(map(data, compose(square, partial(sum, neg(mean)))), sum),
          reciprocal(sum(data.length, -1))
        )
      );

8.8.4 记忆

function memorize(f) {
    var cache = {};

    return function() {
        var key = arguments.length + Array.prototype.join.call(arguments, ",");
        
        if (key in cache) {
            return cache[key];
        } else {
            return cache[key] = f.apply(this, arguments);
        }
    };
}
  • 通过这样的实现,我们为任意函数添加了记忆功能,以提高函数的性能。记忆功能会在函数的每次调用时检查参数是否已经被缓存过,如果是,则直接返回缓存的结果,避免了重复计算
// 返回两个整数的最大公约数
// 使用欧几里德算法:http://en.wikipedia.org/wiki/Euclidean_algorithm
function gcd(a, b) {
    //这里省略对a和b的类型检查
    //临时变量用来存储交换数值
    var t;

    // 确保 a >= b
    if (a < b) {
        t = b;
        b = a;
        a = t;
    }
    while (b !== 0) {
        t = b;
        b = a % b;
        a = t;
    }
    //这是求最大公约数的欧几里德算法
    return a;
}

var gcdmemo = memorize(gcd);
gcdmemo(85, 187); //=>17

//注意,当我们写一个递归函数时,往往需要实现记忆功能
//我们更希望调用实现了记忆功能的递归函数,而不是原递归函数
var factorial = memorize(function(n) {
    return (n <= 1) ? 1 : n * factorial(n - 1);
});

factorial(5); //=>120.对于4~1的值也有缓存

9.5 类和类型

// 定义一个名为 speak 的函数,它接受一个可以“说话”的对象作为参数
function speak(animal) {
    if (animal && typeof animal.speak === 'function') {
        animal.speak(); // 调用对象的 speak 方法
    } else {
        console.log("This object cannot speak.");
    }
}

// 定义一个 duck 对象,它具有 speak 方法
var duck = {
    speak: function() {
        console.log("Quack!");
    }
};

// 定义一个 dog 对象,它也具有 speak 方法
var dog = {
    speak: function() {
        console.log("Woof!");
    }
};

// 调用 speak 函数,分别传入 duck 和 dog 对象
speak(duck); // 输出 "Quack!"
speak(dog);  // 输出 "Woof!"
  • 鸭式辩型(Duck Typing)是一种动态类型的编程风格,它关注对象的行为而不是对象的类型。这个术语来自于一句俗语:“如果它看起来像鸭子,叫起来像鸭子,那么它就是鸭子。”

    在鸭式辩型中,一个对象的类型或类并不重要,重要的是它具有的方法或属性。如果一个对象具有特定的方法或属性,那么它就可以被视为具有特定类型的对象,无论它是如何实现的。

10. 正则表达式的模式匹配

var regex = /Java(?!script)([A-Z]\w*)/;

console.log(regex.test("JavaBeans")); // 输出 true,因为 "Java" 后跟一个大写字母和任意多个字母或数字
console.log(regex.test("Javanese")); // 输出 false,因为 "Java" 后面跟着 "nese",不符合后面不能跟着 "script" 的条件
console.log(regex.test("JavaScript")); // 输出 true,因为 "Java" 后跟着 "Script",但符合后面不能跟着 "script" 的条件
console.log(regex.test("Javascripter")); // 输出 false,因为 "Java" 后跟着 "scripter",不符合后面不能跟着 "script" 的条件

var url = /(\w+):\/\/([\w.]+)\/(\S*)/;
var text = "Visit my blog at http://www.example.com/~david";
var result = text.match(url);

if (result !== null) {
    var fullurl = result[0]; // 完整URL,包含 "http://www.example.com/~david"
    var protocol = result[1]; // 协议部分,包含 "http"
    var host = result[2]; // 主机部分,包含 "www.example.com"
    var path = result[3]; // 路径部分,包含 "/~david"
    
    console.log("Full URL:", fullurl);
    console.log("Protocol:", protocol);
    console.log("Host:", host);
    console.log("Path:", path);
} else {
    console.log("No match found.");
}

"1, 2, 3, 4, 5".split(/\s*,\s*/); // 返回 ["1", "2", "3", "4", "5"]

11. javascript的子集和扩展


赞 (0)

评论区(暂无评论)

这里空空如也,快来评论吧~

我要评论