参考学习:
- MDN JavaScript:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript
- ECMAScript 6入门(阮一峰):http://es6.ruanyifeng.com/
- 阮一峰的网络日志:http://www.ruanyifeng.com/blog/javascript/
- JS注释参考(Google中搜索JSDoc):http://usejsdoc.org/
- 参考:Babel is a JavaScript compiler
- 当前参考学习《JavaScript语言精粹》
目录:
- 函数
- 数组
- 方法(标准类型上的标准方法)
-
正则表达式
- 4.2 使用字符串方法(search()方法、replace()方法
-
- 举例1:JavaScript判断输入是否为数字、字母、下划线组成
- 举例2:JavaScript判断字符串是否全部为字母
- 举例3:JavaScript判断字符串是否全为数字
-
-
- (1)命名空间
- (2)标准内置对象
- (3)自定义对象
-
继承与原型链
- 6.1 继承与原型链(MDN)
-
6.2 继承(《JavaScript语言精粹》)
- (1)伪类
-
(2)对象说明符(属性特性)
- 1)数据属性
- 2)访问器属性
- 3)可扩展性
- 4)最后:get、set 与继承
- (3)函数化
- 严格模式
- JavaScript库
1、函数
1.0-1 变量
在JavaScript中声明一个新变量的方法时使用关键字 let
、const
和 var
:
let
语句声明一个块级作用域的本地变量,并且可选的将其初始化为一个值。
let a; let name = 'Simon';
下面是使用 let
声明变量作用域的例子:
// myLetVariable is *not* visible out here for (let myLetVariable = 0; myLetVariable < 5; myLetVariable++) { // myLetVariable is only visible in here } // myLetVariable is *not* visible out here
const
允许声明一个不可变的常量。这个常量在定义域内总是可见的。
const Pi = 3.14; // 设置 Pi 的值 Pi = 1; // 将会抛出一个错误因为你改变了一个常量的值。
输出结果:
d:\zhaoyingjun\Web\JavaScript_01\number2.js:81 Pi = 1; // 将会抛出一个错误因为你改变了一个常量的值。 ^ TypeError: Assignment to constant variable. at Object.<anonymous> (d:\zhaoyingjun\Web\JavaScript_01\number2.js:81:4) at Module._compile (module.js:653:30) at Object.Module._extensions..js (module.js:664:10) at Module.load (module.js:566:32) at tryModuleLoad (module.js:506:12) at Function.Module._load (module.js:498:3) at Function.Module.runMain (module.js:694:10) at startup (bootstrap_node.js:204:16) at bootstrap_node.js:625:3
var
是最常见的声明变量的关键字。它没有其他两个关键字的种种限制。这是因为它是传统上在 JavaScript 声明变量的唯一方法。使用 var
声明的变量在它所声明的整个函数都是可见的。
var a; var name = "simon";
一个使用 var
声明变量的语句块的例子:
// myVarVariable *is* visible out here for (var myVarVariable = 0; myVarVariable < 5; myVarVariable++) { // myVarVariable is visible to the whole function } // myVarVariable *is* visible out here
输出结果:略。
1.0-2 运算符
JavaScript的算术操作符包括 +
、-
、*
、/
和 % ——
求余(与模运算不同)。赋值使用 =
运算符,此外还有一些复合运算符,如 +=
和 -=
,它们等价于 x = x op y
。
特别注意,如果你用一个字符串加上一个数字(或其他值),那么操作数都会被首先转换为字符串。如下所示:
console.log("3" + 4 + 5); console.log(3 + "4" + 5); // 345,前面两个中只要有一个为字符串,则相加结果都是隐式转换为字符串 console.log(3 + 4 + '5');
JavaScript 中的比较操作使用 <
、>
、<=
和 >=
,这些运算符对于数字和字符串都通用。相等的比较稍微复杂一些。由两个“=
(等号)”组成的相等运算符有类型自适应的功能(即可以进行自动类型转换),具体例子如下:
console.log(123 == '123'); // true console.log(1 == true); // true
如果在比较前不需要自动类型转换,应该使用由三个“=
(等号)”组成的相等运算符:
console.log(123 === '123'); // false console.log(1 === true); // false
JavaScript 还支持 !=
和 !==
两种不等运算符,具体区别与两种相等运算符的区别类似。
JavaScript 还提供了 位操作符。
1.0-3 控制结构
JavaScript 的控制结构与其他类 C 语言类似。可以使用 if
和 else
来定义条件语句,还可以使用if、else if和else连接起来使用;
JavaScript 支持 while
循环和 do-while
循环。前者适合常见的基本循环操作,如果需要循环体至少被执行一次则可以使用 do-while;
JavaScript 的 for
循环与 C 和 Java 中的相同,使用时可以在一行代码中提供控制信息。
var a = ['a', 'b', 'c']; for (let i = 0; i < a.length; i++) { console.log(a[i]); }var a = ['a', 'b', 'c']; for (let i = 0; i < a.length; i++) { console.log(a[i]); }
输出结果:
a b c
JavaScript 也还包括其他两种重要的 for
循环: for
...of
for (let value of array) { // do something with value }
举例说明:
var a = ['a', 'b', 'c']; for (let value of a) { console.log(value); }
输出结果:
a b c
和 for
...in
:
for (let property in object) { // do something with object property }
举例说明1(只能得到下标值):
var a = ['a', 'b', 'c']; for (let value in a) { console.log(value); }
输出结果:
0 1 2
举例说明2(因此可以修改为):
for (let i in a) { console.log(a[i]); }
输出结果:
a b c
1.1 对象字面量
对象字面量提供了一种非常方便地创建新对象值的表示法。一个对象字面量就是包围在一对花括号中的零个或多个“名/值”(key-value)对。对象字面量可以出现在任何允许表达式出现的地方。
JavaScript中的对象可以简单理解成“名称-值”对。
有两种方法创建一个空对象:
// 方式一 var obj = new Object(); // 方式二 var obj = {};
这两种方法在语义上是相同的,第二种更方便的方法叫做“对象字面量(object literal)”法。这种也是 JSON 格式的核心语法,一般我们优先选择第二种方法。
“对象字面量”也可以用来在对象实例中定义一个对象:
// “对象字面量”也可以用来在对象实例中定义一个对象 var obj = { name: "Carrot", "_for": "Max", details: { color: "orange", size: 12 } }; // 对象的属性可以通过链式(chain)表示方法进行访问: console.log(obj.details.color); // orange console.log(obj["details"]["color"]); // orange // 下面的例子创建了一个对象原型,Person,和这个原型的实例,You。 function Person(name, age) { this.name = name; this.age = age; } // 定义一个对象 var You = new Person("John", 24); // 完成对象创建后,对象属性可以通过如下两种方式进行赋值和访问 // 方式一 obj.name = "Simon"; var name_str = obj.name; // 方式二 obj['name'] = "Simon"; var name_str = obj['name']; // can use a variable to define a key // 方式一 var user1 = 'what is your key?'; obj[user1] = 'what is its value?'; console.log(obj); // 方式二 obj["user2"] = "user2_value"; console.log(obj);
输出结果:
orange orange { name: 'Simon', _for: 'Max', details: { color: 'orange', size: 12 }, 'what is your key?': 'what is its value?' } { name: 'Simon', _for: 'Max', details: { color: 'orange', size: 12 }, 'what is your key?': 'what is its value?', user2: 'user2_value' }
举例:
var stooge = { "first_name": "Jerome", "last_name": "Howard", arrival: { city: "Los Angeles", "time": "2018-09-30 11:13" } };
举例:
var a = 2 + 3 + ""; var str = "hello"; console.log("hello"); console.log(str); var stooge = { "first_name": "Jerome", "last_name": "Howard", arrival: { city: "Los Angeles", "time": "2018-09-30 11:13" } }; console.log(stooge.arrival.time); console.log(stooge.arrival.city); console.log(stooge["first_name"]); var status = stooge.middle || "unknown"; console.log(status); stooge.first_name = "Yichun"; stooge["last_name"] = "Zhao"; stooge.middle = "Handsome"; stooge.test = { aa: "Test" }; console.log(stooge.middle); console.log(stooge); console.log("\n------------反射测试------------"); var flight = { airline: "Oceanic", number: 815, departure: { IATA: "SYD", time: "2018-09-30", city: "Sydney" }, arrival: { IATA: "LAX", time: "2018-09-29", city: "Los Angeles" } }; flight.equipment = { model: 'Boring 777' }; flight.status = 'overdue'; console.log(flight); typeof flight.number; // 'number', typeof类似于Java中的instanceof typeof flight.status; // 'string' typeof flight.arrival; // 'object' typeof flight.mainfest; // 'undefined' typeof flight.toString; // 'function' typeof flight.constructor; // 'function' console.log("flight.number: " + flight.number); console.log("flight.hasOwnProperty('number'): " + flight.hasOwnProperty('number')); // true console.log("flight.hasOwnProperty('function'): " + flight.hasOwnProperty('function')); // false console.log("flight.hasOwnProperty('constructor'): " + flight.hasOwnProperty('constructor')); // false console.log("\n------------枚举测试------------"); var body = { first_name: "First_Name", last_name: "Last_Name", 'profession': "Profession" }; body.middle_name = "Middle_Name"; console.log(body); var properties = [ 'first_name', 'last_name', 'middle_name', 'profession' ]; for (var i = 0; i < body.length; i++) { document.writeln(properties[i] + ': ' + body.properties[i]); } console.log("\n------------删除测试------------"); delete body.first_name; console.log(body);
运行结果:
D:\Program Files\nodejs\node.exe test.js hello hello 2018-09-30 11:13 Los Angeles Jerome unknown Handsome { first_name: 'Yichun', last_name: 'Zhao', arrival: { city: 'Los Angeles', time: '2018-09-30 11:13' }, middle: 'Handsome', test: { aa: 'Test' } } ------------反射测试------------ { airline: 'Oceanic', number: 815,
自定义对象:
在经典的面向对象语言中,对象是指数据和在这些数据上进行的操作的集合。与 C++ 和 Java 不同,JavaScript 是一种基于原型的编程语言,并没有 class
语句,而是把函数用作类。那么让我们来定义一个人名对象,这个对象包括人的姓和名两个域(field)。名字的表示有两种方法:“名 姓(First Last)”或“姓, 名(Last, First)”。使用我们前面讨论过的函数和对象概念,可以像这样完成定义:
function makePerson (first, last) { return { first: first, last: last }; } function personFullName (person) { return person.first + ' ' + person.last; } function personFullNameReserved (person) { return person.last + ', ' + person.first; } var s = makePerson("Simon", "Willison"); console.log(personFullName(s)); // Simon Willison console.log(personFullNameReserved(s)); // Willison, Simon
上面的写法虽然可以满足要求,但是看起来很麻烦,因为需要在全局命名空间中写很多函数。既然函数本身就是对象,如果需要使一个函数隶属于一个对象,那么不难得到:
function makePerson (first, last) { return { first: first, last: last, personFullName: function () { return this.first + ' ' + this.last; }, personFullNameReserved: function () { return this.last + ', ' + this.first; } }; } var s = makePerson("Simon", "Willison"); console.log(s.personFullName()); // Simon Willison console.log(s.personFullNameReserved()); // Willison, Simon
上面的代码里有一些我们之前没有见过的东西:关键字 this
。当使用在函数中时,this
指代当前的对象,也就是调用了函数的对象。如果在一个对象上使用点或者方括号来访问属性或方法,这个对象就成了 this
。如果并没有使用“点”运算符调用某个对象,那么 this
将指向全局对象(global object)。这是一个经常出错的地方。例如:
s = makePerson("Simon", "Willison"); var fullName = s.fullName; fullName(); // undefined undefined
当我们调用 fullName()
时,this
实际上是指向全局对象的,并没有名为 first
或 last
的全局变量,所以它们两个的返回值都会是 undefined
。
下面使用this改进已有的makePerson函数:
function Person (first, last) { this.first = first; this.last = last; this.personFullName = function () { return this.first + ' ' + this.last; }, this.personFullNameReserved = function () { return this.last + ', ' + this.first; } } var s = new Person("Simon", "Willison"); console.log(s.personFullName()); // Simon Willison console.log(s.personFullNameReserved()); // Willison, Simon
我们引入了另外一个关键字:new
,它和this密切相关。它的作用是创建一个崭新的空对象,然后使用指向那个对象的this调用特定的函数。
注意,含有this的特定个函数不会返回任何值,只会修改this对象本身。new关键字将生成的this对象返回给调用方,而被new调用的函数成为构造函数。习惯的做法是将这些函数的首字母大写,这样用new调用它们的时候就容易识别了。
不过这个改进的函数还是和上一个例子一样,单独调用fullName()时会产生相同的问题。
我们的Person对象现在已经相当完善了,但还有一些不太好的地方。每次我们创建一个Person对象的时候,我们都在其中创建了两个新的函数——如果过这个代码可以共享不是更好吗?
var personFullName = function () { return this.first + ' ' + this.last; } var personFullNameReserved = function () { return this.last + ', ' + this.first; } function Person (first, last) { this.first = first; this.last = last; this.fullName = personFullName; this.fullNameReserved = personFullNameReserved; } var s = new Person("Simon", "Willison"); console.log(s.fullName()); // Simon Willison console.log(s.fullNameReserved()); // Willison, Simon
这样写法的好处是,我们只需要创建一次方法函数,在构造函数中引用它们。那是否还有更好的方法呢?答案是肯定的。
function Person (first, last) { this.first = first; this.last = last; } Person.prototype.fullName = function () { return this.first + ' ' + this.last; } Person.prototype.fullNameReserved = function () { return this.last + ', ' + this.first; } var s = new Person("Simon", "Willison"); console.log(s.fullName()); // Simon Willison console.log(s.fullNameReserved()); // Willison, Simon
Person.prototype是一个可以被Person的所有实例共享的对象。它是一个原型链(prototype chain)的查询链的一部分:当你试图访问一个Person没有定义的属性时,解释器会首先检查这个Person.prototype来判断是否存在这个属性。所以,任何分配给Person.prototype的东西对通过this对象构造的实例都是可用的。
这个特性功能十分强大,JavaScript允许你在程序中的任何时候修改原型(prototype)中的一些东西,也就是说你可以在运行时(runtime)给已经存在的对象添加额外的方法:
// 增加原型链(proototype chain)属性 Person.prototype.firstNameCaps = function () { return this.first.toUpperCase(); } console.log(s.firstNameCaps()); // Simon
有趣的是,还可以给JavaScript的内置函数原型(prototype)添加东西。让我们给String添加一个方法用来返回逆序的字符串:
var str = "Simon"; // str.reserved(); // TypeError: str.reserved is not a function String.prototype.reserved = function () { var r = ''; for (let i = this.length - 1; i >= 0; i--) { r += this[i]; } return r; } console.log(str.reserved()); // nomiS // 定义新变量也可以在字符串字面量上用(string literal) console.log("This can now be reserved".reserved()); // devreser eb won nac sihT
正如前面提到的,原型组成链的一部分。那条链的根节点是Object.prototype,它包括toString()方法——将对相关转换成字符串时调用的方法。这对于我们的Person对象很有用:
var s = new Person("Simon", "Willison"); Person.prototype.toString = function () { return '<Person: ' + this.fullName() + '>'; } console.log(s.toString()); // <Person: Simon Willison>
1.2 函数字面量(Function Literal)
函数对象可以通过函数字面量来创建。函数字面量可以出现在任何允许表达式出现的地方。函数也可以被定义在其它函数中。一个内部函数自然可以访问自己的参数和变量,同时它也能方便地访问它被嵌套在其中的那个函数的参数与变量。通过函数字面量创建的函数对象包含一个连到外部上下文的连接。这被称为闭包(即类似于C的局部变量和全局变量的作用域情况)。它是JavaScript强大表现力的根基。
举例1:
var add = function (a, b) { return a + b; }
举例2:
function add() { // let声明一个块级作用域的本地变量,并且可选的将其初始化为一个值,当然可以用var替代let let sum = 0; for (let i = 0; i < arguments.length; i++) { sum += arguments[i]; } return sum; } var val = add(3, 4, 5, 6, 10); console.log("val: " + val); // 输出28
接下来创建一个求取平均值的函数:
// 接下来创建一个求取平均值的函数 function avg() { // let声明一个块级作用域的本地变量,并且可选的将其初始化为一个值,当然可以用var替代let let sum = 0; for (let i = 0; i < arguments.length; i++) { sum += arguments[i]; } return sum / arguments.length; } /* // JavaScript允许创建匿名函数,这个函数在语义上与function avg()相同。 var avg = function () { // let声明一个块级作用域的本地变量,并且可选的将其初始化为一个值,当然可以用var替代let let sum = 0; for (let i = 0; i < arguments.length; i++) { sum += arguments[i]; } return sum / arguments.length; }*/ var val_avg1 = avg(3, 4, 5, 6, 10); console.log("val_avg1: " + val_avg1); // 输出5.6
这个很有用,但是却带来了新的问题。avg() 函数处理一个由逗号连接的变量串,但如果想得到一个数组的平均值该怎么办呢?可以这么修改函数:
// 这个很有用,但是却带来了新的问题。 // avg() 函数处理一个由逗号连接的变量串,但如果想得到一个数组的平均值该怎么办呢?可以这么修改函数: function avgArray(arr) { // let声明一个块级作用域的本地变量,并且可选的将其初始化为一个值,当然可以用var替代let let sum = 0; for (let i = 0; i < arr.length; i++) { sum += arr[i]; } return sum / arr.length; } var val_avgArray = avgArray([3, 4, 5, 6, 10]); console.log("val_avgArray: " + val_avgArray); // 输出5.6
但如果能重用我们已经创建的那个函数不是更好吗?幸运的是 JavaScript 允许使用任意函数对象的apply() 方法来调用该函数,并传递给它一个包含了参数的数组。传给 apply() 的第二个参数是一个数组,它将被当作 avg() 的参数使用,至于第一个参数 null,我们将在后面讨论。这也正说明一个事实——函数也是对象。
// 但如果能重用我们已经创建的那个函数不是更好吗? // 幸运的是 JavaScript 允许使用任意函数对象的apply() 方法来调用该函数,并传递给它一个包含了参数的数组。 // 传给 apply() 的第二个参数是一个数组,它将被当作 avg() 的参数使用, // 至于第一个参数 null,我们将在后面讨论。这也正说明一个事实——函数也是对象。 var val_avg2 = avg.apply(null, [3, 4, 5, 6, 10]); console.log("val_avg2: " + val_avg2); // 输出5.6
这个函数在语义上与 function avg()
相同。你可以在代码中的任何地方定义这个函数,就像写普通的表达式一样。基于这个特性,有人发明出一些有趣的技巧。与 C 中的块级作用域类似,下面这个例子隐藏了局部变量:
var a = 1; var b = 2; (function() { var b = 3; a += b; })(); console.log("a: " + a); // a: 4 console.log("b: " + b); // b: 2
输出结果:略。
1.3 函数——JavaScript函数后面加不加括号的区别
JavaScript函数加括号表示执行该函数,不加括号仅仅表示定义了一个函数对象,比如:
var f = function () { return true; };
上述代码中,f是一个function()对象。
var f = function () { return ture; }();
上述代码中,f=1。
1.4 函数调用
每个函数接收两个附加的参数:this和argument。
当实际参数(arguments)的个数与形式参数(parameters)的个数不匹配时不会导致运行时错误。如果实际参数过多了,超出的参数值将被忽略。如果实际参数过少,缺失的值将会被替换为undefined。对参数值不会进行类型检查:任何类型的值都可以被传递给参数。
在JavaScript中一共有四种调用模式(这些模式在如何初始化 关键参数this上存在差异。):
- 方法调用模式;
- 函数调用模式;
- 构造器调用模式(如果调用构造器函数时候没有在前面加上new,可能会发生非常糟糕的事情,既没有编译时警告,也没有运行时警告,所以大写约定非常重要);
- apply调用模式。
// 因为函数是对象,所以它们可以像任何其它的值一样被使用。函数可以存放在变量、对象和数组中 // 函数可以被当作参数传递给其它函数,函数也可以再返回函数 // 因为函数是对象,所以函数可以拥有方法 console.log("\n------------原型测试------------"); function person() { person.prototype.name = "Jingzi"; person.prototype.age = 20; person.prototype.sayName = function() { console.log(this.name); } } var person_1 = new person(); person_1.sayName(); console.log("\n------------函数字面量测试------------"); // 认为是把一个匿名函数存放在一个add变量中 var add = function (a, b) { return a + b; }; console.log(add("Hello, ", "world!")); console.log("\n------------1.1 方法调用模式------------"); // 创建myObject。它有一个value属性和一个increment方法 // increment方法可以接受一个可选的参数。如果参数不是数字,那么默认使用数字1。 var myObject = { value: 0, increment: function (inc) { this.value += typeof inc === 'number' ? inc : 1; } }; myObject.increment(); console.log(myObject.value); // document.writeln(myObject.value); myObject.increment(2); console.log(myObject.value); // document.writeln(myObject.value); console.log("\n------------1.2 函数调用模式------------"); console.log(sum); // 给myObject增加一个double方法 myObject.double = function () { var that = this; // 解决方法 var helper = function () { that.value = add(that.value, that.value); } helper(); // 以函数的形式调用helper console.log(that); }; // 以方法 的形式调用double myObject.double(); console.log("\n------------1.3 构造器调用模式------------"); // 创造一个名为Quo的构造器函数。它构造一个带有status属性的对象 var Quo = function (str) { this.status = str; }; // 给Quo的所有实例提供一个名为get_status的公共方法 Quo.prototype.get_status = function () { return this.status; }; // 构造一个Quo实例 var myQuo = new Quo("confused"); console.log(myQuo.get_status()); // 因为JavaScript时一门函数式的面向对象编程语言,所以函数可以拥有方法 // 1.3中的构造器函数Quo和为Quo实例的公共方法也是必需的 console.log("\n------------1.4 Apply调用模式------------"); // 构造一个包含两个数字的数组,并将它们相加 var array = [3, 4]; var sum = add.apply(null, array); // sum值为7 console.log(sum); // 构造一个包含status成员的对象 var statusObject = { status: 'A-OK' }; var status = Quo.prototype.get_status.apply(statusObject); console.log(status);
运行结果:
------------1.1 方法调用模式------------ 1 3 ------------1.2 函数调用模式------------ 7 { value: 6, increment: [Function: increment], double: [Function] } ------------1.3 构造器调用模式------------ confused ------------1.4 Apply调用模式------------ 7 A-OK
1.5 函数参数
当函数被调用时,会得到一个“免费”奉送的参数,那就是arguments数组。通过它函数可以访问所有它被调用时传递给它的参数列表,包括那些没有被分配给函数声明时定义的形式参数的多余参数。这使得编写一个无须指定参数个数的函数成为可能。
// 构造一个将很多个值相加的函数 // 注意该函数内部定义的变量sum不会与函数外部定义的sum产生冲突 // 该函数只会看到内部的那个变量 var sum = function () { var i, sum = 0; for (i = 0; i < arguments.length; i++) { sum += arguments[i]; } return sum; }; console.log(sum(4, 7, 15, 16, 23, 42)); // 输出107 // document.writeln(sum(4, 7, 15, 16, 23, 42));
1.6 函数异常
如果在try代码块内抛出了一个异常,控制权就会跳转到它的catch从句。
一个try语句只会有一个将捕获所有异常 的catch代码块。如果你的处理手段取决于异常的类型,那么异常处理器必须检查异常对象的name属性以确定异常的类型。
// throw语句中断函数的执行。它应该抛出一个exception异常 // 该对象包含可识别异常类型的name属性和一个描述性的message属性,当然也可以添加其它的属性。 var add = function (a, b) { if ( (typeof a != 'number') || (typeof a != 'number') ) { throw { name: 'TypeError', message: 'add needs numbers' } } return a + b; }; // 该exception对象被传递到一个try语句的catch从句 var try_it = function () { try { add("seven"); } catch (e) { console.log(e.name + ": " + e.message); } } try_it();
输出结果:
TypeError: add needs numbers
1.7 函数——给类型增加方法
JavaScript允许给语言的基本类型增加方法。举例来说,我们可以通过给Function.prototype增加方法来使得该方法对所有函数可用:
1 Function.prototype.method = function () { 2 this.prototype[name] = func; 3 return this; 4 }
通过Function.prototype增加一个method方法,我们就不必键入prototype这个属性名。这个缺点也就被掩盖了(下面代码运行还有问题,需要后续进行调试)。
// 给类型增加方法 // JavaScript并没有单独的整数类型,因此有时候只提取数字中的整数部分是必要的。 // JavaScript本身提供的取整方法有些丑陋。我们可以通过给Number.prototype添加一个 // integer方法来改善它。它会根据数字的正负来判断是使用Math.ceiling还是Math.floor。 Number.method('integer', function () { return Math[this < 0 ? 'ceil' : 'floor'](this); }); console.log((-10 / 3).integer());
通过给基本类型增加方法,我们可以大大提高语言的表现力。因为JavaScript原型继承的动态本质,新的方法立刻被赋予到所有的值(对象实例)上,哪怕值(对象实例)是在方法被创建之前就创建好了。
// 有条件地增加一个方法 Function.prototype.method = function (name, func) { if (!this.prototype[name]) { this.prototype[name] = func; } };
1.8 函数——递归
“汉诺塔”是一个著名的难题。塔的设备包括三根柱子和一套直径各不相同的空心圆盘。开始时源柱子上的所有圆盘都按照较小的圆盘放在较大的圆盘之上的顺序堆叠。目标是通过每次移动一个圆盘到另一根柱子,最终将一对圆盘移动到目标柱子上,过程中不可以将大的圆盘放置在较小的圆盘之上。寻常解答如下:
var hanoi = function (disc, src, aux, dst) { if (disc > 0) { hanoi(disc - 1, src, dst, aux); console.log('Move disc ' + disc + ' from ' + src + ' to ' + dst); hanoi(disc - 1, src, aux, dst); } }; hanoi(3, 'Src', 'Aux', 'Dst');
运行结果:
Move disc 1 from Src to Dst Move disc 2 from Src to Aux Move disc 1 from Src to Aux Move disc 3 from Src to Dst Move disc 1 from Src to Aux Move disc 2 from Src to Dst Move disc 1 from Src to Dst
1.9 函数——闭包
下面我们将看到的是 JavaScript 中必须提到的功能最强大的抽象概念之一:闭包。但它可能也会带来一些潜在的困惑。那它究竟是做什么的呢?
举例1:
function makeAddr(a) { return function(b) { return a + b; }; } var x = makeAddr(5); var y = makeAddr(20); console.log(x(6)); // 返回11 console.log(y(7)); // 返回27
makeAddr这个名字本身应该能说明函数是用来做什么的:它创建了一个新的adder函数,这个函数自身带有一个参数,它被调用的时候这个参数会被夹在外层函数传进来的参数上。
这里发生的事情和前面介绍过的内嵌函数十分相似:一个函数被定义在了另外一个函数的内部,内部函数可以访问外部函数的变量。唯一的不同是,外部函数已经返回了,那么常识告诉我们局部变量“应该”不再存在。但是它们却仍然存在——否则 adder
函数将不能工作。也就是说,这里存在 makeAdder
的局部变量的两个不同的“副本”——一个是 a
等于5,另一个是 a
等于20。那些函数的运行结果就如下所示:
下面来说说到底发生了什么。每当JavaScript执行一个函数时,都会创建一个作用域对象(scope object),用来保存在这个函数中创建的局部变量。它和被传入函数的变量一起被初始化。它和被传入函数的变量一起被初始化。这与那些保存的所有全局变量和函数的全局对象(global object)类似,但仍有一些很重要的区别,第一,每次函数被执行的时候,就会创建一个新的,特定的作用域对象;第二,与全局对象(在浏览器里面是当做 window
对象来访问的)不同的是,你不能从 JavaScript 代码中直接访问作用域对象,也没有可以遍历当前的作用域对象里面属性的方法。
所以当调用 makeAdder
时,解释器创建了一个作用域对象,它带有一个属性:a
,这个属性被当作参数传入 makeAdder
函数。然后 makeAdder
返回一个新创建的函数。通常 JavaScript 的垃圾回收器会在这时回收 makeAdder
创建的作用域对象,但是返回的函数却保留一个指向那个作用域对象的引用。结果是这个作用域对象不会被垃圾回收器回收,直到指向 makeAdder
返回的那个函数对象的引用计数为零。
作用域对象组成了一个名为作用域链(scope chain)的链。它类似于原型(prototype)链一样,被JavaScript的对象系统使用。
一个闭包就是一个函数和被创建的函数中的作用域对象的组合。
举例2:
function sayHello(name) { var text = 'Hello ' + name; var say = function() { console.log(text); }; say(); } sayHello("Joe"); // Hello Joe
举例3:
// 以下代码返回对函数的引用 function sayHello2(name) { var text = "Hello " + name; var say = function() { console.log(text); }; return say; } var say2 = sayHello2("Joe"); say2(); // Hello Joe
或者写成:
// 以下代码返回对函数的引用 function sayHello2(name) { var text = "Hello " + name; var say = function() { console.log(text); }; return say(); } var say2 = sayHello2("Joe"); say2; // Hello Joe
有权访问另一个函数作用域内变量的函数都是闭包。
function a() { var n = 0; function inc() { n++; console.log(n); } return inc; } var c = a(); c(); c();
举例:
var quo = function (status) { return { get_status: function () { return status; } } }; // 创造一个Quo实例 var myQuo = quo("amazed"); console.log(myQuo.get_status());
输出结果为:amazed
上述代码和下面代码的作用相同:
var quo = function (status) { return { get_status : status } }; // 创造一个Quo实例 var myQuo = quo("amazed"); console.log(myQuo.get_status);
输出结果为:amazed
上述代码和下面代码的作用相同:
var quo = function (status) { return status; }; // 创造一个Quo实例 var myQuo = quo("amazed"); console.log(myQuo);
输出结果为:amazed
1.10 函数——模块
可以使用函数和闭包来构造模块。模块是一个提供接口却隐藏状态与实现的函数或对象。通过使用函数去产生模块,我们几乎可以完全摒弃全局变量的使用,从而缓解这个JavaScript的最为糟糕的特性之一所带来的影响。
// 模块模式也可以用来产生安全的对象 // 假设我们想要构造一个用来产生序列号的对象 var serial_maker = function () { // 返回一个用来产生唯一字符串的对象 // 唯一字符串由两个部分组成:前缀+序列号 // 该对象包含一个设置前缀的方法,一个设置序列号的方法 // 和一个产生唯一字符串的gensym方法 var prefix = ''; var seq = 0; return { set_prefix: function (p) { prefix = String(p); }, set_seq: function (s) { seq = s; }, gensym: function () { var result = prefix + '_' + seq; seq += 1; return result; } }; }; var seqer = serial_maker(); seqer.set_prefix('ZYJ'); seqer.set_seq('serialTest20181003'); var test_seqer = seqer.gensym(); console.log(test_seqer);
输出结果:ZYJ_serialTest20181003
1.11 函数——记忆
函数可以调用对象去记忆先前操作的结果,从而避免无谓的运算。这种优化被称为记忆(memoization)。
比如说,我们想要一个递归函数计算Fibonacci数列。一个Fibonacci数字是之前两个Fibonacci数字之和。最前面两个数字是0和1。
var fibonacci = function (n) { return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2); }; for (var i = 0; i <= 10; i++) { console.log('//' + i + ": " + fibonacci(i)); }
输出结果:
//0: 0 //1: 1 //2: 1 //3: 2 //4: 3 //5: 5 //6: 8 //7: 13 //8: 21 //9: 34 //10: 55
这样是可以工作的,但它做了很多无谓的工作。fibonacci函数被调用了453次。我们调用了11次,而它自身调用了442次去计算可能已被刚刚计算过的值。如果我们让该函数具备记忆功能,就可以显著地减少它的运算量。
我们在一个名为memo的数组里保存我们的存储结果,存储结果可以隐藏在闭包中。当我们的函数被调用时,这个函数首先看是否已经知道存储结果,如果已经知道,就立即返回这个存储结果。
var fibonacci = function () { var memo = [0, 1]; var fib = function (n) { var result = memo[n]; if (typeof result !== 'number') { console.log(n); result = fib(n - 1) + fib(n - 2); memo[n] = result; } return result; }; return fib; }(); for (var i = 0; i <= 10; i++) { console.log('//' + i + ": " + fibonacci(i)); }
输出结果:
//0: 0 //1: 1 2 //2: 1 3 //3: 2 4 //4: 3 5 //5: 5 6 //6: 8 7 //7: 13 8 //8: 21 9 //9: 34 10 //10: 55
上述代码可以看出,这个函数返回同样的结果,但它只被调用了29次。我们调用了它11次,它自身调用了18次去取得之前存储的结果。
我们可以把这种形式一般化,编写一个函数来帮助我们构造带记忆功能的函数。
1.12 函数——内部函数
JavaScript 允许在一个函数内部定义函数,这一点我们在之前的 makePerson()
例子中也见过。关于 JavaScript 中的嵌套函数,一个很重要的细节是它们可以访问父函数作用域中的变量:
function betterExampledNeeded() { var a = 1; function oneMoreThanA() { return a + 1; } return oneMoreThanA(); }
如果某个函数依赖于其它的一两个函数,而这一两个函数对你其余的代码没有用处,你可以用它们嵌套在会被调用的那个函数内部,这样做可以减少全局作用域下的函数的数量,这有利于编写易于维护的 代码。
这也是一个减少使用全局变量的好方法。当编写复杂代码时,程序员往往试图使用全局变量,将值共享给多个函数,订单这样做会使得代码很难维护。内部函数可以共享父函数的变量,所以你可以使用这个特性把一些函数捆绑在一起,这样可以有效地方防止“污染”你的全局命名空间——可以称它为“局部全局(local global)”。虽然这种方法应该谨慎使用,但它确实很有用,应该掌握。
1.13 内存泄漏
使用闭包的一个坏处是,在 IE 浏览器中它会很容易导致内存泄露。JavaScript 是一种具有垃圾回收机制的语言——对象在被创建的时候分配内存,然后当指向这个对象的引用计数为零时,浏览器会回收内存。宿主环境提供的对象都是按照这种方法被处理的。
浏览器主机需要处理大量的对象来描绘一个正在被展现的 HTML 页面——DOM 对象。浏览器负责管理它们的内存分配和回收。
IE 浏览器有自己的一套垃圾回收机制,这套机制与 JavaScript 提供的垃圾回收机制进行交互时,可能会发生内存泄露。
在 IE 中,每当在一个 JavaScript 对象和一个本地对象之间形成循环引用时,就会发生内存泄露。如下所示:
function leakMemory() { var el = document.getElementById('e1'); var o = { 'el': el }; el.o = o; }
这段代码的循环引用会导致内存泄露:IE 不会释放被 el
和 o
使用的内存,直到浏览器被彻底关闭并重启后。
这个例子往往无法引起人们的重视:一般只会在长时间运行的应用程序中,或者因为巨大的数据量和循环中导致内存泄露发生时,内存泄露才会引起注意。
不过一般也很少发生如此明显的内存泄露现象——通常泄露的数据结构有多层的引用(references),往往掩盖了循环引用的情况。
闭包很容易发生无意识的内存泄露。如下所示:
function addHandler() { var el = document.getElementById('e1'); el.onclick = function() { el.style.backgroundColor = 'red'; }; }
这段代码创建了一个元素,当它被点击的时候变红,但同时它也会发生内存泄露。为什么?因为对 el
的引用不小心被放在一个匿名内部函数中。这就在 JavaScript 对象(这个内部函数)和本地对象之间(el
)创建了一个循环引用。
这个问题有很多种解决方法,最简单的一种是不要使用 el
变量:
function addHandler() { document.getElementById('el').onclick = function() { this.style.backgroundColor = 'red'; }; }
另外一种避免闭包的好方法是在 window.onunload
事件发生期间破坏循环引用。很多事件库都能完成这项工作。注意这样做将使 Firefox 中的 bfcache 无法工作。所以除非有其他必要的原因,最好不要在 Firefox 中注册一个onunload
的监听器。
2、数组
2.1 数组字面量
一个数组字面量是在一对方括号中包围零个或多个用逗号分隔的值的表达式。数组字面量可以出现在任何表达式可以出现的地方。数组的第一个值将获得属性名'0',第二个值将获得属性名'1',依次类推:
// numbers继承来自Array.prototype,所以numbers继承了大量有用的方法。 // 同时numbers也有一个诡异的lenght属相,而numbers_object则(见下文中)没有 var empty = []; var numbers = [ 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine' ]; console.log('1.1 ' + empty[1]); // undefined console.log('1.2 ' + numbers[1]); // 'one' console.log('1.3 ' + empty.length); console.log('1.4 ' + numbers.length); // 对象字面量 // numbers_object继承来自Object.prototype var numbers_object = { '0': 'zero', '1': 'one', '2': 'two', '3': 'three', '4': 'four', '5': 'five', '6': 'six', '7': 'seven', '8': 'eight', '9': 'nine' }; // 在大多数语言中一个数组的所有元素都要求是相同的类型。 // JavaScript允许数组包含任意混合类型的值。 var misc = [ 'str', 98.6, true, false, null, undefined, ['nested', 'array'], {object: true}, NaN, Infinity ]; console.log('2.1 ' + misc.length);
输出结果:
1.1 undefined 1.2 one 1.3 0 1.4 10 2.1 10
2.2 数组长度
每个数组都有一个length属性。和大多数其它语言不同,JavaScript数组的length是没有上界的。如果你用大于或等于当前length的数字作为下标来保存一个元素,那么length将增大来容纳新元素。不会发生数组边界错误。
var myArray = []; console.log("1 " + myArray.length); myArray[10000] = true; console.log("2 " + myArray.length); // 10001, myArray数组只包含一个属性
输出结果:
1 0 2 10001
举例2:
// 可以直接设置length的值。 // 设置大的length无须给数组分配更多的空间。 // 而把length设小,将导致所有虾苗大于等于新length的属性被删除 numbers.length = 3; console.log('3.1 ' + numbers); // 附加一个新元素到该数组的尾部 numbers[numbers.length] = 'ten'; console.log('3.2 ' + numbers); // 有时用push方法可以更方便地完成同样的事情 numbers.push('go'); console.log('3.3 ' + numbers);
输出结果:
3.1 zero,one,two 3.2 zero,one,two,ten 3.3 zero,one,two,ten,go
2.3 删除&枚举(查询)
// 由于JavaScript的数组其实就是对象, // 所以delete云算法可以用来从数组中移出元素 // 不幸的是,那样会在数组中遗留一个空洞 delete numbers[2]; console.log("4.1 " + numbers); // 幸运的是JavaScript数组有一个splice方法。 // 它可以对数组做个手术,删除一些元素并将它们替换为其它的元素 // 第一个参数是数组中的一个序号,第二个参数是要删除的元素个数 numbers.splice(2, 1); console.log("4.2 " + numbers);
输出结果:
4.1 zero,one,,ten,go 4.2 zero,one,ten,go
因为JavaScript的数组其实就是对象,所以for in语句可以用来遍历一个数组的所有属性。不幸的是,for in无法保证属性的顺序,而大多数的数组应用都期望按照阿拉伯数字顺序来产生元素。因此可以用常规的for语句进行查询使用。
// 查询数组 for (var i = 0; i < numbers.length; i++) { console.log("5.1 " + numbers[i]); }
输出结果:
5.1 zero 5.1 one 5.1 ten 5.1 go
2.4 混淆的地方
在JavaScript编程中,一个常见的错误就是在须使用数组时使用了对象,或者在须使用对象时使用了数组。其实规则很简单:当属性名是小而连续的整数时,你应该使用数组。否则,使用对象。
2.5 方法
JavaScript提供了一套作用于数组的方法,这些方法是被存储在Array.prototype中的函数。前面有看到Object.prototype是可以扩充的。同样,Array.prototype也可以被扩充。
// 自己创建的Array.dim方法 Array.dim = function (dimension, initial) { var a = []; for (var i = 0; i < dimension; i++) { a[i] = initial; } return a; }; // 创建一个包含10个0的数组 var myArray = Array.dim(10, 0); console.log('1.1: ' + myArray); // JavaScript没有多维数组,但就像大多数类C语言一样,它支持元素为数组的数组 var matrix = [ [0, 1, 2], [3, 4, 5], [6, 7, 8] ]; console.log('2.1: ' + matrix[2][1]); console.log('2.2: ' + matrix); // 修改Array.dim方法为Array.matrix Array.matrix = function (m, n, initial) { var a; var mat = []; for (var i = 0; i < m; i++) { a = []; for (var j = 0; j < n; j++) { a[j] = initial; } mat[i] = a; } return mat; }; // 构造一个用0填充的4*4矩阵 var myMatrix = Array.matrix(4, 4, 0); console.log('3.1: ' + myMatrix[3][3]); console.log('3.2: ' + myMatrix); // 自己创建一个用来构造一个恒等矩阵的方法 Array.identity = function (n) { var mat = Array.matrix(n, n, 0); for (var i = 0; i < n; i++) { mat[i][i] = 1; } return mat; }; myMatrix = Array.identity(4); console.log('4.1: ' + myMatrix[3][3]); console.log('4.2: ' + myMatrix);
输出结果:
1.1: 0,0,0,0,0,0,0,0,0,0 2.1: 7 2.2: 0,1,2,3,4,5,6,7,8 3.1: 0 3.2: 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 4.1: 1 4.2: 1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1
3、方法(标准类型上的标准方法)
额外参考:MDN官方教程(重新介绍JavaScript(JS教程))
JavaScript 是一种面向对象的动态语言,它包含类型、运算符、标准内置( built-in)对象和方法。它的语法来源于 Java 和 C,所以这两种语言的许多语法特性同样适用于 JavaScript。需要注意的一个主要区别是 JavaScript 不支持类,类这一概念在 JavaScript 通过对象原型(object prototype)得到延续(有关 ES6 类的内容参考这里Classes
)。另一个主要区别是 JavaScript 中的函数也是对象,JavaScript 允许函数在包含可执行代码的同时,能像其他对象一样被传递。
3.1 Array
参考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Array
Array(数组)类自带了许多方法。查看array 方法的完整文档。
方法名称 | 描述 |
---|---|
a.toString() |
返回一个包含数组中所有元素的字符串,每个元素通过逗号分隔。 |
a.toLocaleString() |
根据宿主环境的区域设置,返回一个包含数组中所有元素的字符串,每个元素通过逗号分隔。 |
a.concat(item1[, item2[, ...[, itemN]]]) |
返回一个数组,这个数组包含原先 a 和 item1、item2、……、itemN 中的所有元素。 |
a.join(sep) |
返回一个包含数组中所有元素的字符串,每个元素通过指定的 sep 分隔。 |
a.pop() |
删除并返回数组中的最后一个元素。 |
a.push(item1, ..., itemN) |
将 item1、item2、……、itemN 追加至数组 a 。 |
a.reverse() |
数组逆序(会更改原数组 a )。 |
a.shift() |
删除并返回数组中第一个元素。 |
a.slice(start, end) |
返回子数组,以 a[start] 开头,以 a[end] 前一个元素结尾。 |
a.sort([cmpfn]) |
依据 cmpfn 返回的结果进行排序,如果未指定比较函数则按字符顺序比较(即使元素是数字)。 |
a.splice(start, delcount[, item1[, ...[, itemN]]]) |
从 start 开始,删除 delcount 个元素,然后插入所有的 item 。 |
a.unshift([item]) |
将 item 插入数组头部,返回数组新长度(考虑 undefined )。 |
举例:
// array.concat(item...) // concat方法返回一个新数组,它包含array的浅复制(shallow copy), // 并将1个或多个参数与item附加在其后。 // 如果item是一个数组,那么它的每个元素都会被分别添加 var a = ['a', 'b', 'c']; var b = ['x', 'y', 'z']; var c = ['1', '2', '3']; var d1 = a.concat(b, true); console.log("d1: " + d1); var d2 = a.concat(b, false); console.log("d2: " + d2); var d3 = a.concat(b, c, true); console.log("d3: " + d3); console.log("----------------1 array.concat(item...) end----------------"); // array.join(separator) // join方法是把一个array构造成一个字符串。 // 它将array中的每个元素构造成一个字符串 // 并用一个separator为分隔符把它们连接在一起 // 默认的separator是','。 var a = ['a', 'b', 'c']; a.push('d'); var c = a.join(''); console.log("a: " + a); console.log("c: " + c); console.log("----------------2 array.join(separator) end----------------"); // array.pop() // pop和push方法使数组array像堆栈(stack)一样工作 // pop方法移除array中最后一个元素并返回该元素。如果该array为空,它会返回undefined var a = ['a', 'b', 'c']; var c = a.pop(); console.log("a: " + a); console.log("c: " + c); console.log("----------------3 array.pop() end----------------"); // array.push(item...) // push方法将一个或多个item附加到一个数组的尾部 // 不像concat方法那样,它会修改数组array,如果item是一个数组, // 它会将参数数组作为单个元素整个添加到数组中。它返回这个数组array的新长度值 var a = ['a', 'b', 'c']; var b = ['x', 'y', 'z']; var c1 = a.push(b, true); // a,b,c, [x,y,z], true, c1 = 5; var c2 = a.push(b, false); // a,b,c, [x,y,z], true, [x,y,z], false, c2 = 7 console.log("a: " + a); console.log("c1: " + c1); console.log("c2: " + c2); console.log("----------------4 array.push(item...) end----------------"); // array.reverse() // 反转array中的元素的顺序 var a = ['a', 'b', 'c']; var b = a.reverse(); console.log("a: " + a); console.log("b: " + b); console.log("----------------5 array.reverse() end----------------"); // array.shift() // shift方法移除数组array中的第一个元素并返回该元素 var a = ['a', 'b', 'c']; var b = a.shift(); console.log("a: " + a); console.log("b: " + b); console.log("----------------6 array.shift() end----------------"); // array.slice(start, end),前开后闭,end参数是可选的 // slice方法对array中的一段做浅复制 // 如果start大于等于array.length,得到的结果将是一个空数组 var a = ['a', 'b', 'c']; var b = a.slice(0, 1); // 'a' var c = a.slice(1); // 'b', 'c' var d = a.slice(1, 2); // 'b' console.log("a: " + a); console.log("b: " + b); console.log("c: " + c); console.log("d: " + d); console.log("----------------7 array.slice() end----------------"); // array.spice(start, deleteCount, item...) // splice方法从array中移除1个或多个元素,并用新的item替换它们, // deleteCount是要移除的元素个数,它返回一个包含被移除元素的数组 var a = ['a', 'b', 'c']; var b = a.splice(1, 1, 'ache', 'bug'); console.log("a: " + a); // 'a', 'ache', 'bug', 'c' console.log("b: " + b); // 'b' console.log("----------------8 array.spice() end----------------"); // array.sort(comparefn) // sort方法对array中的内容进行适当的排序。 // 它不能正确地给一组数字排序 var a = ['a', 'c', 'b']; var n = [4, 5, 15, 16, 23, 42]; a.sort(); n.sort(); console.log("a: " + a); // a, b, c console.log("n: " + n); // 15, 16, 23, 4, 42, 5 console.log("----------------9 array.sort(comparefn) end----------------"); // array.unshift(item...) // unshift方法像push方法一样用于将元素添加到数组中 // 但它是把item插入到array的开始部分而不是尾部。 // 它返回array的新的长度值 var a = ['a', 'b', 'c']; var b = ['x', 'y', 'z']; var c1 = a.push(b); console.log("a: " + a); // [a,b,c], x, y, z console.log("c1: " + c1); console.log("----------------10 array.unshift(item...) end----------------");
输出结果:
d1: a,b,c,x,y,z,true d2: a,b,c,x,y,z,false d3: a,b,c,x,y,z,1,2,3,true ----------------1 array.concat(item...) end---------------- a: a,b,c,d c: abcd ----------------2 array.join(separator) end---------------- a: a,b c: c ----------------3 array.pop() end---------------- a: a,b,c,x,y,z,true,x,y,z,false c1: 5 c2: 7 ----------------4 array.push(item...) end---------------- a: c,b,a b: c,b,a ----------------5 array.reverse() end---------------- a: b,c b: a ----------------6 array.shift() end---------------- a: a,b,c b: a c: b,c d: b ----------------7 array.slice() end---------------- a: a,ache,bug,c b: b ----------------8 array.spice() end---------------- a: a,b,c n: 15,16,23,4,42,5 ----------------9 array.sort(comparefn) end---------------- a: a,b,c,x,y,z c1: 4 ----------------10 array.unshift(item...) end----------------
3.2 Function 的属性和方法
语法总结:
// 1、Function 属性和方法 // 全局的Function对象没有自己的属性和方法, 但是, 因为它本身也是函数,所以它也会通过原型链从Function.prototype上继承部分属性和方法。 // 1.1 原型对象:属性 // (1)Function.arguments(此属性已被 arguments 替代) // (2)Function.caller(获取调用函数的具体对象) // 如果一个函数f是在全局作用域内被调用的,则f.caller为null,相反,如果一个函数是在另外一个函数作用域内被调用的,则f.caller指向调用它的那个函数. // (3)Function.length(获取函数的接收参数个数) // (4)Function.name(获取函数的名称) // (5)Function.displayName(获取函数的display name) // (6)Function.prototype.constructor(声明函数的原型构造方法,详细请参考 Object.constructor, Object.prototype.constructor) // 1.2 原型对象:方法 // (1)Function.prototype.apply() // 在一个对象的上下文中应用另一个对象的方法;参数能够以数组形式传入 // (2)Function.prototype.call() // 在一个对象的上下文中应用另一个对象的方法;参数能够以列表形式传入。 // (3)Function.prototype.bind() // bind()方法会创建一个新函数,称为绑定函数.当调用这个绑定函数时,绑定函数会以创建它时传入 bind()方法的第一个参数作为 this,传入 bind()方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数. // (4)Function.prototype.toSource() // 获取函数的实现源码的字符串。 覆盖了 Object.prototype.toSource 方法。 // (5)Function.prototype.toString() // 获取函数的实现源码的字符串。覆盖了 Object.prototype.toString 方法。
举例:
// 1、Function 属性和方法 // 全局的Function对象没有自己的属性和方法, 但是, 因为它本身也是函数,所以它也会通过原型链从Function.prototype上继承部分属性和方法。 // 1.1 原型对象:属性 // (1)Function.arguments(此属性已被 arguments 替代) // 在函数递归调用时()在某一刻同一个函数运行了多次,也就是有多套实参,那么 arguments 属性的值就是最近一次该函数调用时传入的参数 // 如果函数不在执行期间,那么该函数的 arguments 属性的值是 null console.log("\n"); function f1(n) { g1(n - 1); } function g1(n) { console.log("1.1 before: " + g1.arguments[0]); if (n > 0) { f1(n); } console.log("1.2 after: " + g1.arguments[0]); } f1(2); console.log("1.3 函数退出后的 arguments 属性值: " + g1.arguments); // 输出: // before: 1 // before: 0 // after: 0 // after: 1 // 函数退出后的 arguments 属性值: null // (2)Function.caller(获取调用函数的具体对象,返回调用制定函数的函数) // 如果一个函数f是在全局作用域内被调用的,则f.caller为null,相反,如果一个函数是在另外一个函数作用域内被调用的,则f.caller指向调用它的那个函数. console.log("\n"); function stop() { console.log("2.1 Stop function."); } function f(n) { stop(); if (f.caller == null) { console.log("2.2 n: " + n + "该函数在全局作用域内被调用!"); } else console.log("2.3 n: " + n + "调用我的是函数是" + f.caller); } f(2); // (3)Function.length(获取函数的接收参数个数) console.log("\n"); function sum(a, b) { return a + b; } console.log("sum.length: " + sum.length); // sum.length: 2 // (4)Function.name(获取函数的名称) console.log("sum.name: " + sum.name); // sum.name: sum // (5)Function.displayName(获取函数的显示名称) console.log("sum.displayName: " + sum.displayName); // sum.displayName: undefined // 当一个函数的 displayName 属性被定义,这个函数的 displayName 属性将返回显示名称 sum.displayName = "Sum Function"; console.log("sum.displayName: " + sum.displayName); // sum.displayName: Sum Function // (6)Function.prototype.constructor(声明函数的原型构造方法,详细请参考 Object.constructor, Object.prototype.constructor) // 所有的对象都会从它的原型上继承一个 constructor 属性 console.log("\n"); var o = {}; console.log("o.constructor === Object: " + (o.constructor === Object)); // true var o = new Object; console.log("o.constructor === Object: " + (o.constructor === Object)); // true var a = []; console.log("a.constructor === Array: " + (a.constructor === Array)); // true var a = new Array; console.log("a.constructor === Array: " + (a.constructor === Array)); // true var n = new Number; console.log("n.constructor === Number: " + (n.constructor === Number)); // true // 创建一个原型:Tree,以及该类型的对象,即 theTree。然后打印 theTree对象的 constructor 属性 function Tree(name) { this.name = name; } var theTree = new Tree("Redwood"); console.log("theTree.constructor is " + theTree.constructor); // 1.2 原型对象:方法 // (1)Function.prototype.apply() // 语法:func.apply(thisArg, [argsArray]) // thisArg:可选的。在 func 函数运行时使用的 this 值。请注意,this可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动替换为指向全局对象,原始值会被包装。 // argsArray:可选的。一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 func 函数。如果该参数的值为 null 或 undefined,则表示不需要传入任何参数。从ECMAScript 5 开始可以使用类数组对象。 // 返回值:调用有指定this值和参数的函数的结果。 // 在一个对象的上下文中应用另一个对象的方法;参数能够以数组形式传入 // 注意:call()方法的作用和 apply() 方法类似,区别就是call()方法接受的是参数列表,而apply()方法接受的是一个参数数组。 console.log("\n"); var numbers = [5, 6, 2, 3, 7]; var max = Math.max.apply(null, numbers); // 基本等同于 Math.max(numbers[0], ...) 或 Math.max(5, 6, ..) var min = Math.min.apply(null, numbers); // 基本等同于 Math.min(numbers[0], ...) 或 Math.min(5, 6, ..) console.log("max: " + max + ", min: " + min); // 用 apply() 将数组添加到另一个数组中 var array = ['a', 'b']; var elements = [0, 1, 2]; array.push.apply(array, elements); // max: 7, min: 2 console.log("array: " + array); // array: a,b,0,1,2 // (2)Function.prototype.call() // 语法:fun.call(thisArg, arg1, arg2, ...) // thisArg:在fun函数运行时指定的this值。需要注意的是,指定的this值并不一定是该函数执行时真正的this值,如果这个函数处于non-strict mode, // 则指定为null和undefined的this值会自动指向全局对象(浏览器中就是window对象),同时值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象。 // arg1, arg2, ...:指定的参数列表。 // 返回值:使用调用者提供的this值和参数调用该函数的返回值。若该方法没有返回值,则返回undefined。 // 在一个对象的上下文中应用另一个对象的方法;参数能够以列表形式传入。 // 1)使用call方法调用父构造函数 function Product(name, price) { this.name = name; this.price = price; } function Food(name, price) { Product.call(this, name, price); this.category = 'food'; } function Toy(name, price) { Product.call(this, name, price); this.category = 'toy'; } var cheese = new Food("feta", 5); var fun = new Toy("robot", 40); console.log("cheese: " + JSON.stringify(cheese)); // cheese: {"name":"feta","price":5,"category":"food"} console.log("fun: " + JSON.stringify(fun)); // fun: {"name":"robot","price":40,"category":"toy"} // 2)使用call方法调用匿名函数 var animals = [ { species: 'Lion', name: 'King' }, { species: 'Whale', name: 'Fail' } ]; for (var i = 0; i < animals.length; i++) { (function (i) { this.print = function () { console.log("#" + i + " " + this.species + ": " + this.name); } this.print(); }).call(animals[i], i); } // 3)使用call方法调用函数并且没有确定第一个参数(argument) var sData = 'Wisen'; function display() { console.log('sData value is %s ', this.sData); } display.call(); // sData value is Wisen, 在严格模式下this的值将会是undefined. // (3)Function.prototype.bind() // bind()方法会创建一个新函数,称为绑定函数. // 当调用这个绑定函数时,绑定函数会以创建它时传入 bind()方法的第一个参数作为 this,传入 bind()方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数. // 1)创建绑定函数 var module = { x: 42, getX: function () { return this.x; } }; console.log("module.getX(): " + module.getX()); var unboundGetX = module.getX; console.log("unboundGetX: " + unboundGetX()); boundGetX = unboundGetX.bind(module); // 创建一个新函数,把 'this' 绑定到 module 对象 console.log("boundGetX: " + boundGetX()); // (4)Function.prototype.toSource() // 获取函数的实现源码的字符串。 覆盖了 Object.prototype.toSource 方法。 // (5)Function.prototype.toString() // 获取函数的实现源码的字符串。覆盖了 Object.prototype.toString 方法。 console.log("module.toSource: " + display.toString());
3.3 Number
和其他编程语言(如 C 和 Java)不同,JavaScript 不区分整数值和浮点数值,所有数字在 JavaScript 中均用浮点数值表示,所以在进行数字运算的时候要特别注意。
console.log(0.1 + 0.2); // 0.30000000000000004
JavaScript 支持标准的算术运算符,包括加法、减法、取模(或取余)等等。还有一个之前没有提及的内置对象 Math
(数学对象),用以处理更多的高级数学函数和常数:
console.log(Math.sin(3.5)); // -0.35078322768961984 var r = 0.5; var d = Math.PI * (r + r); // 3.141592653589793 console.log(d);
你可以使用内置函数 parseInt()
将字符串转换为整型。该函数的第二个参数表示字符串所表示数字的基(进制):
// 可以使用内置函数 parseInt() 将字符串转换为整型 // 该函数的第二个参数(可选)表示字符串所表示数字的基(进制) console.log(parseInt("123")); console.log(parseInt("123", 10)); console.log(parseInt("010")); console.log(parseInt("010", 10)); console.log(parseInt("0x10")); console.log(parseInt("11", 2));
单元运算符+也可以把数字字符串转换成数值:
console.log(+ "42"); console.log(+ "010"); console.log(+ "0x10");
如果给定的字符串不存在数值形式,函数会返回一个特殊的值 NaN
(Not a Number 的缩写),NaN作为参数进行过任何数学运算,结果也会是NaN
console.log(parseInt("hello", 10)); // NaN console.log(NaN + 5); // NaN
可以使用内置函数 isNaN()
来判断一个变量是否为 NaN
:
console.log(isNaN(NaN)); // true
JavaScript 还有两个特殊值:Infinity
(正无穷)和 -Infinity
(负无穷):
可以使用内置函数 isFinite()
来判断一个变量是否是一个有穷数, 如果类型为Infinity
, -Infinity
或 NaN则返回false
:
console.log(1 / 0); // Infinity console.log(-1 / 0); // -Infinity // isFinit()判断数字是否是有限的 // 如果类型为Infinity, -Infinity 或 NaN则返回false console.log(isFinite(1 / 0)); // false console.log(isFinite(Infinity)); // false console.log(isFinite(NaN)); // false console.log(isFinite(-Infinity)); // false console.log(isFinite(0)); // true console.log(isFinite(2e64)); // true console.log(isFinite('0')); // true,如果是纯数值类型的检测,则返回false:Number.isFinite("0");
JavaScript 还有一个类似的内置函数 parseFloat()
,用以解析浮点数字符串,与parseInt()
不同的地方是,parseFloat()只应用于解析十进制数字。
// parseFloat返回正常数字 console.log(parseFloat("3.14")); // 3.14 console.log(parseFloat("314e-2")); // 3.14 console.log(parseFloat("0.0314e2")); // 3.14 console.log(parseFloat("3.14more non-digit characters")); // 3.14 // 下面例子返回NaN console.log(parseFloat("FF2")); // NaN // 该函数通过正则表达式的方式,在需要更严格地转换float值时可能会有用 var filterFloat = function (value) { if(/^(\-|\+)?|(\.\d+)(\d+(\.\d+)?(\d+\.)|Infinity)$/.test(value)) { return Number(value); } }; console.log(filterFloat('421')); console.log(filterFloat('-421')); // -421 console.log(filterFloat('+421')); console.log(filterFloat('Infinity')); // Infinity console.log(filterFloat('1.61803398875')); // 1.61803398875 console.log(filterFloat('421e+0')); console.log(filterFloat('421hop')); // NaN console.log(filterFloat('hop1.61803398875')); // NaN console.log(filterFloat("999 888")); // NaN console.log(filterFloat('.421')); // 0.421 console.log(filterFloat('421.'));
parseInt()
和 parseFloat()
函数会尝试逐个解析字符串中的字符,直到遇上一个无法被解析成数字的字符,然后返回该字符前所有数字字符组成的数字。使用运算符 "+" 将字符串转换成数字,只要字符串中含有无法被解析成数字的字符,该字符串都将被转换成 NaN
。请你用这两种方法分别解析“10.2abc”这一字符串,比较得到的结果,理解这两种方法的区别。
console.log(parseInt("10.2abc")); console.log(parseFloat("10.2abc")); // 10.2
举例:
// number.toExponential(fractionDigits) // toExponential方法把这个number转换成一个指数形式的字符串 // 可选参数fractionDigits控制其小数点后的数字位数。它的值必须在0~2之间 console.log("Math.PI.toExponential(0): " + Math.PI.toExponential(0)); console.log("Math.PI.toExponential(2): " + Math.PI.toExponential(2)); console.log("Math.PI.toExponential(7): " + Math.PI.toExponential(7)); console.log("Math.PI.toExponential(16): " + Math.PI.toExponential(16)); console.log("Math.PI.toExponential(): " + Math.PI.toExponential()); console.log("----------------1 number.toExponential(fractionDigits) end----------------"); // number.toFixed(fractionDigits) // toFixed方法把这个number转换成一个十进制数形式的字符串 // 可选参数fractionDigits控制其小数点后的数字位数。它的值必须在0~20之间,默认值为0 console.log("Math.PI.toFixed(0): " + Math.PI.toFixed(0)); console.log("Math.PI.toFixed(2): " + Math.PI.toFixed(2)); console.log("Math.PI.toFixed(7): " + Math.PI.toFixed(7)); console.log("Math.PI.toFixed(16): " + Math.PI.toFixed(16)); console.log("Math.PI.toFixed(): " + Math.PI.toFixed()); console.log("----------------2 number.toFixed(fractionDigits) end----------------"); // number.toPrecision(precision) // toFixed方法把这个number转换成一个十进制数形式的字符串 // 可选参数precision控制有效数字的位数。它的值必须在1~21之间。 console.log("Math.PI.toPrecision(1): " + Math.PI.toPrecision(1)); console.log("Math.PI.toPrecision(2): " + Math.PI.toPrecision(2)); console.log("Math.PI.toPrecision(7): " + Math.PI.toPrecision(7)); console.log("Math.PI.toPrecision(16): " + Math.PI.toPrecision(16)); console.log("Math.PI.toPrecision(): " + Math.PI.toPrecision()); console.log("----------------3 number.toPrecision(precision) end----------------"); // number.toString(radix) // toString方法把这个number转换成字符串 // 可选参数radix控制基数。它的值必须在2~36之间。默认的radix是以10为基数的。 // radix最常用的是整数,但是它可以用任意的数字 // 在最普通情况下,number.toString()可以更简单的写成String(number) console.log("Math.PI.toString(2): " + Math.PI.toString(2)); console.log("Math.PI.toString(8): " + Math.PI.toString(8)); console.log("Math.PI.toString(16): " + Math.PI.toString(16)); console.log("Math.PI.toString(): " + Math.PI.toString()); console.log("String(Math.PI): " + String(Math.PI)); console.log("----------------4 number.toString(radix) end----------------");
输出结果:
Math.PI.toExponential(0): 3e+0 Math.PI.toExponential(2): 3.14e+0 Math.PI.toExponential(7): 3.1415927e+0 Math.PI.toExponential(16): 3.1415926535897931e+0 Math.PI.toExponential(): 3.141592653589793e+0 ----------------1 number.toExponential(fractionDigits) end---------------- Math.PI.toFixed(0): 3 Math.PI.toFixed(2): 3.14 Math.PI.toFixed(7): 3.1415927 Math.PI.toFixed(16): 3.1415926535897931 Math.PI.toFixed(): 3 ----------------2 number.toFixed(fractionDigits) end---------------- Math.PI.toPrecision(1): 3 Math.PI.toPrecision(2): 3.1 Math.PI.toPrecision(7): 3.141593 Math.PI.toPrecision(16): 3.141592653589793 Math.PI.toPrecision(): 3.141592653589793 ----------------3 number.toPrecision(precision) end---------------- Math.PI.toString(2): 11.001001000011111101101010100010001000010110100011 Math.PI.toString(8): 3.1103755242102643 Math.PI.toString(16): 3.243f6a8885a3 Math.PI.toString(): 3.141592653589793 String(Math.PI): 3.141592653589793 ----------------4 number.toString(radix) end----------------
3.4 Object
// object.hasOwnProperty(name) // 判断object是否包含name属性参数 var a = {member: "Hello, world"}; // var b = Object.beget(a); var t = a.hasOwnProperty('member'); // t: true console.log("t: " + t); console.log("a.member: " + a.member); // a.member: Hello, world
3.5 String
- string.charAt(pos ):返回在string中pos位置处的字符。
- string.charCodeAt(pos ):返回在string中pos位置处的字符的字符码位(整数形式表示)。
- string.concat(item...):很少被使用,因为'+'使用会更方便。
- string.indexOf(searchString, position):返回某个指定的字符串值在字符串中首次出现的位置。
- string.lastIndexOf(searchString, position):返回一个指定的字符串值最后出现的位置,在一个字符串中的指定位置从后向前搜索。
- string.localCompare(stringOther):localCompare方法比较两个字符串。如何比较字符串的规则没有详细的说明(从第一个字符开始比较),如果string比字符串that小,那么结果为负数;如果它们是相等的,那么结果为0
- string.match(regexp):在字符串内检索指定的值,返回值
(1)JS匹配字符串中两个子字符串之间的字符串var str = "aaa好的bd123内容kkk内容k好的2内容"; str = str.match(/好的(\S*)内容/)[1];
输出结果:略。
- string.replace(searchValue, replaceVale):str.replace(/Microsoft/g, "W3School") 全局替换
- string.search(regexp):stringObject 中第一个与 regexp 相匹配的子串的起始位置
- string.slice(start, end):slice方法复制string的一部分来构造一个新的字符串。
- string.substring(start, end):提取字符串中介于两个指定下标之间的字符。
- string.toLocaleLowerCase():使用本地化的规则把这个string中的所有字母转换为小写格式。这个方法主要用在土耳其语上,因为在土耳其语中'I'转换为'l',而不是'i'
- string.fromCharCode(char...):该函数从一串数字中返回一个字符串,var a1 = String.fromCharCode(67, 97, 116); // Cat
- join:把数组中的所有元素放入一个字符串。
- split:把一个字符串分割成字符串数组。
- toString():把一个逻辑值转换为字符串,并返回结果。
- valueOf():返回 Boolean 对象的原始值。
- JSON.parse('text'):字符串转对象
- JSON.stringify(value):对象转为字符串
// string.charAt(pos ) // charAt方法返回在string中pos位置处的字符 // 如果pos小于0或者大于等于字符串的长度string.length,它会返回空字符串 var name = "Curly"; var initial = name.charAt(0); console.log("initial: " + initial); console.log("----------------1 string.charAt(pos ) end----------------"); // string.charCodeAt(pos ) // charCodeAt方法返回在string中pos位置处的字符的字符码位(整数形式表示) // 如果pos小于0或者大于等于字符串的长度string.length,它会返回NaN var name = "Curly"; var initial = name.charCodeAt(0); console.log("initial: " + initial); console.log("----------------2 string.charCodeAt(pos ) end----------------"); // string.concat(item...),很少被使用,因为'+'使用会更方便 var str = 'C'.concat('a', 't'); console.log("str: " + str); console.log("----------------3 string.concat(item...) end----------------"); // string.indexOf(searchString, position) // indexOf方法在string中查找另一个字符串searchString。 // 如果它被找到,则返回第一个匹配字符的位置,否则返回-1。 // 可选参数position可设置从string的某个指定的位置开始查找 var text = "Mississippi"; var p1 = text.indexOf('ss'); var p2 = text.indexOf('ss', 3); var p3 = text.indexOf('ss', 6); console.log("p1: " + p1 + ", p2: " + p2 + ", p3: " + p3); console.log("----------------4 string.indexOf(searchString, position) end----------------"); // string.lastIndexOf(searchString, position) // lastIndexOf方法在string中(从末尾开始)查找另一个字符串searchString。 // 如果它被找到,则返回第一个匹配字符的位置(即从后往前找的第一个匹配字符的位置),否则返回-1。 // 可选参数position可设置从string的某个指定的位置开始查找 var text = "Mississippi"; var p1 = text.lastIndexOf('ss'); var p2 = text.lastIndexOf('ss', 3); var p3 = text.lastIndexOf('ss', 6); console.log("p1: " + p1 + ", p2: " + p2 + ", p3: " + p3); console.log("----------------5 string.lastIndexOf(searchString, position) end----------------"); // string.localCompare(stringOther) // localCompare方法比较两个字符串。如何比较字符串的规则没有详细的说明(从第一个字符开始比较) // 如果string比字符串that小,那么结果为负数;如果它们是相等的,那么结果为0 var str1 = "Mississippi"; var str2 = "Mississippi, hahaha"; var result1 = str1.localeCompare(str2); // -1 console.log("6.1 result1: " + result1 + ", ressult2: " + result2); var str1 = "Mississippi"; var str2 = "Mississippi"; var result2 = str2.localeCompare(str1); // -1 console.log("6.2 result1: " + result1 + ", ressult2: " + result2); var str1 = "aississippi"; var str2 = "bississipp, hahaha"; var result1 = str1.localeCompare(str2); // -1 console.log("6.3 result1: " + result1 + ", ressult2: " + result2); var str1 = "bississippi"; var str2 = "aississipp, hahaha"; var result2 = str2.localeCompare(str1); // -1 console.log("6.4 result1: " + result1 + ", ressult2: " + result2); console.log("----------------6 string.localCompare(stringOther) end----------------"); // string.match(regexp) // match方法匹配一个字符串和一个正则表达式 // 它依据g标识来决定如何进行匹配。如果没有g标识,那么调用string.match(regexp)的结果与调用regexp.exec(string)的结果相同 // 如果带有g标识,那么它返回一个包含捕获分组之外的所有匹配的数组 var text = '<html><body bgcolor=linen><p>' + 'This is <b>bold<\/b>!<\/p><\/body><\/html>'; var tags = /[^<>]+|<(\/?)(a-zA-Z+)([^<>]*)>/g; var a = text.match(tags); console.log(a); console.log("----------------7 string.match(regexp) end----------------"); // string.replace(searchValue, replaceVale) // 查找和替换操作,并返回一个新的字符串 // searchValue可以是一个字符串或者一个正则表达式对象 // 如果searchValue它是一个字符串,那么searchValue只会在第一次出现的地方被替换 var result = "mother_in_law".replace('_', '-'); console.log("result: " + result); // result: mother-in_law // 如果searchValue它是一个正则表达式并且带有g标志,那么searchValue会替换所有匹配支出,如果没有带g标识,那么它将仅替换第一个匹配之处。 // 捕获括号中的3个数字 var oldaracode = /\((d{3})\)/g; var p = '(555)666-1212'.replace(oldaracode, '$1-'); console.log("p: " + p); // p: (555)666-1212 console.log("----------------8 string.replace(searchValue, replaceVale) end----------------"); // string.search(regexp) // search方法和indexOf方法类似,只是它只接受一个正则表达式对象作为参数而不是一个字符串。 // 如果找到匹配,它返回第一个匹配的首字符位置,如果没有找到匹配,则返回-1,此方法会忽略g标志,且没有position参数 var text = 'and in it he says "Any damn fool could"'; var pos1 = text.search(/["']/); var pos2 = text.search(/[h]/); console.log("pos1: " + pos1 + ", pos2: " + pos2); console.log("----------------9 string.search(regexp) end----------------"); // string.slice(start, end) // slice方法复制string的一部分来构造一个新的字符串。 // 如果start参数是负值,它将于string.length相加。即截取从最后往前数的多少个字符 // end参数是可选的,并且它的默认值是string.length,如果end参数是负数,那么它将于string.length相加。 var text = 'and in it he says "Any damn fool could"'; var a = text.slice(18); var b = text.slice(0, 3); var c = text.slice(-5); var d = text.slice(18); console.log("a: " + a); console.log("b: " + b); console.log("c: " + c); console.log("d: " + d); console.log("----------------10 string.slice(start, end) end----------------"); // string.substring(start, end) // substring的用法与slice方法一样,只是substring方法不能处理负数参数。 // 没有任何理由去使用substring方法。请用slice替代它。 // string.split(separator, limit) // split方法把这个string分割成片段来创建一个字符串数组。 // 可选参数limit可以限制被分割的片段数量。 // separator参数可以是一个字符串或者一个正则表达式 // 如果separator参数是一个空字符串,将返回一个单字符的数组 var str_digits = '0123456789'; var a = str_digits.split('', 5); console.log(a); // [ '0', '1', '2', '3', '4' ] var ip = "192.168.1.0"; var b = ip.split('.'); console.log(b); // [ '192', '168', '1', '0' ] var c1 = '|a|b|c'.split('|'); console.log(c1); // [ '', 'a', 'b', 'c' ] var c2 = '|a|b|c'.split('|'); console.log(c2); // [ '', 'a', 'b', 'c' ] var text = 'last, first, middle'; var d1 = text.split(/\s*, \s*/); console.log(d1); // [ 'last', 'first', 'middle' ] var d1 = text.split(/\s*(,)\s*/); console.log(d1); // [ 'last', ',', 'first', ',' 'middle' ] var d2 = text.split(/\s*/); console.log(d2); // 'l', 'a', ...,单个字符组成的数组 console.log("----------------11 string.split(start, end) end----------------"); // string.toLocaleLowerCase() // 使用本地化的规则把这个string中的所有字母转换为小写格式。 // 这个方法主要用在土耳其语上,因为在土耳其语中'I'转换为'l',而不是'i' var text = "Hello, world, PI, Pi"; var str1 = text.toLocaleLowerCase(); console.log("str1: " + str1); // hello, world, pi, pi // string.toLocaleUpperCase() // 使用本地化的规则把这个string中的所有字母转换为小写格式。这个方法主要用在土耳其语上 var str2 = text.toLocaleUpperCase(); console.log("str2: " + str2); // HELLO, WORLD, PI, PI // string.toLowerCase(),返回一个新的字符串,这个string中的所有字母都被转化为小写格式 var str3 = text.toLowerCase(); console.log("str3: " + str3); // hello, world, pi, pi // string.toUpperCase(),返回一个新的字符串,这个string中的所有字母都被转化为小写格式 var str4 = text.toUpperCase(); console.log("str4: " + str4); // HELLO, WORLD, PI, PI console.log("----------------12 string.toLocaleLowerCase/toLocaleUpperCase/toLowerCase/toUpperCase end----------------"); // string.fromCharCode(char...),该函数从一串数字中返回一个字符串 var a1 = String.fromCharCode(67, 97, 116); console.log("a1: " + a1); // a1: Cat var nums = (67, 97, 116); var a2 = String.fromCharCode(nums); console.log("a2: " + a2); // a2: t var nums = '67, 97, 116'; var a3 = String.fromCharCode(nums); console.log("a3: " + a3); // a3: console.log("----------------13 string.fromCharCode(char...) end----------------");
输出结果:
initial: C ----------------1 string.charAt(pos ) end---------------- initial: 67 ----------------2 string.charCodeAt(pos ) end---------------- str: Cat ----------------3 string.concat(item...) end---------------- p1: 2, p2: 5, p3: -1 ----------------4 string.indexOf(searchString, position) end---------------- p1: 5, p2: 2, p3: 5 ----------------5 string.lastIndexOf(searchString, position) end---------------- 6.1 result1: -1, ressult2: 1 6.2 result1: 0, ressult2: 0 6.3 result1: -1, ressult2: 1 6.4 result1: 1, ressult2: -1 ----------------6 string.localCompare(stringOther) end---------------- [ 'html', 'body bgcolor=linen', 'p', 'This is ', 'b', 'bold', '/b', '!', '/p', '/body', '/html' ] ----------------7 string.match(regexp) end---------------- result: mother-in_law p: (555)666-1212 ----------------8 string.replace(searchValue, replaceVale) end---------------- pos1: 18, pos2: 10 ----------------9 string.search(regexp) end---------------- a: "Any damn fool could" b: and c: ould" d: "Any damn fool could" ----------------10 string.slice(start, end) end---------------- [ '0', '1', '2', '3', '4' ] [ '192', '168', '1', '0' ] [ '', 'a', 'b', 'c' ] [ '', 'a', 'b', 'c' ] [ 'last', 'first', 'middle' ] [ 'last', ',', 'first', ',', 'middle' ] [ 'l', 'a', 's', 't', ',', 'f', 'i', 'r', 's', 't', ',', 'm', 'i', 'd', 'd', 'l', 'e' ] ----------------11 string.split(start, end) end---------------- str1: hello, world, pi, pi str2: HELLO, WORLD, PI, PI str3: hello, world, pi, pi str4: HELLO, WORLD, PI, PI ----------------12 string.toLocaleLowerCase/toLocaleUpperCase/toLowerCase/toUpperCase end---------------- a1: Cat a2: t a3: ----------------13 string.fromCharCode(char...) end----------------
举例1:判空函数和判非空函数
// 判空函数 function isNull(value) { if (value == null || value == undefined) { return true; } var valstr = value.toString(); if (valstr == 'null' || valstr == 'undefined' || valstr == '') { return true; } return false; } // 判非空函数 function isNotNull(value) { if (value == null || value == undefined) { return false; } var valstr = value.toString(); if (valstr == 'null' || valstr == 'undefined' || valstr == '') { return false; } return true; }
输出结果:略。
举例2:判断字符串中是否包含中文
参考:https://blog.csdn.net/foolishandstupid/article/details/45268637
escape对字符串进行编码时,字符值大于255的以"%u****"格式存储,而字符值大于255的恰好是非英文字符(一般是中文字符,非中文字符也可以当作中文字符考虑);indexOf用以判断在字符串中是否存在某子字符串,找不到返回"-1"。
<script language="javascript"> var str='中国'; if(escape(str).indexOf("%u")<0){ alert("没有包含中文"); } else{ alert("包含中文"); } </script>
输出结果:略。
3.6 Boolean
3.7 Symbol(符号)(第六版新增)
3.8 Null(空)和 Undefined(未定义)
JavaScript 中 null
和 undefined
是不同的,前者表示一个空值(non-value),必须使用null关键字才能访问,后者是“undefined(未定义)”类型的对象,表示一个未初始化的值,也就是还没有被分配的值。我们之后再具体讨论变量,但有一点可以先简单说明一下,JavaScript 允许声明变量但不对其赋值,一个未被赋值的变量就是 undefined
类型。还有一点需要说明的是,undefined
实际上是一个不允许修改的常量。
JavaScript 包含布尔类型,这个类型的变量有两个可能的值,分别是 true
和 false
(两者都是关键字)。根据具体需要,JavaScript 按照如下规则将变量转换成布尔类型:
- false、0、空字符串("")、NaN、null和undefined被转换为false;
- 所有其它值被转换为true。
console.log(Boolean('')); // false console.log(Boolean(234)); // true
不过一般没必要这么做,因为 JavaScript 会在需要一个布尔变量时隐式完成这个转换操作(比如在 if
条件语句中)。所以,有时我们可以把转换成布尔值后的变量分别称为 真值(true values)——即值为 true 和 假值(false values)——即值为 false;也可以分别称为“真的”(truthy)和“假的”(falsy)。
3.9 判断JS数据类型的四种方法
参考:https://www.jqhtml.com/34944.html
在 ECMAScript 规范中,共定义了 7 种数据类型,分为 基本类型 和 引用类型 两大类,如下所示:
- 基本类型:String、Number、Boolean、Symbol、Undefined、Null
- 引用类型:Object
基本类型也称为简单类型,由于其占据空间固定,是简单的数据段,为了便于提升变量查询速度,将其存储在栈中,即按值访问。
引用类型也称为复杂类型,由于其值的大小会改变,所以不能将其存放在栈中,否则会降低变量查询速度,因此,其值存储在堆(heap)中,而存储在变量处的值,是一个指针,指向存储对象的内存处,即按址访问。引用类型除 Object 外,还包括 Function 、Array、RegExp、Date 等等。
鉴于 ECMAScript 是松散类型的,因此需要有一种手段来检测给定变量的数据类型。对于这个问题,JavaScript 也提供了多种方法,但遗憾的是,不同的方法得到的结果参差不齐。
下面介绍常用的4种方法,并对各个方法存在的问题进行简单的分析。
(1)typeof
typeof 是一个操作符,其右侧跟一个一元表达式,并返回这个表达式的数据类型。返回的结果用该类型的字符串(全小写字母)形式表示,包括以下 7 种:number、boolean、symbol、string、object、undefined、function 等。
typeof ''; // string 有效 typeof 1; // number 有效 typeof Symbol(); // symbol 有效 typeof true; //boolean 有效 typeof undefined; //undefined 有效 typeof null; //object 无效 typeof [] ; //object 无效 typeof new Function(); // function 有效 typeof new Date(); //object 无效 typeof new RegExp(); //object 无效
有些时候,typeof 操作符会返回一些令人迷惑但技术上却正确的值:
- 对于基本类型,除 null 以外,均可以返回正确的结果。
- 对于引用类型,除 function 以外,一律返回 object 类型。
- 对于 null ,返回 object 类型。
- 对于 function 返回 function 类型。
其中,null 有属于自己的数据类型 Null , 引用类型中的 数组、日期、正则 也都有属于自己的具体类型,而 typeof 对于这些类型的处理,只返回了处于其原型链最顶端的 Object 类型,没有错,但不是我们想要的结果。
(2)instanceof
instanceof 是用来判断 A 是否为 B 的实例,表达式为:A instanceof B,如果 A 是 B 的实例,则返回 true,否则返回 false。
理解原型和原型链:
var one = {x: 1}; var two = new Object(); one.__proto__ === Object.prototype // true two.__proto__ === Object.prototype // true one.toString === one.__proto__.toString // true
在这里需要特别注意的是:instanceof 检测的是原型,我们用一段伪代码来模拟其内部执行过程:
instanceof (A, B) = { var L = A.__proto__; var R = B.prototype; if (L === R) { // A的内部属性 __proto__ 指向 B 的原型对象 return true; } return false; }
从上述过程可以看出,当 A 的 __proto__ 指向 B 的 prototype 时,就认为 A 就是 B 的实例,我们再来看几个例子:
[] instanceof Array; // true { } instanceof Object; // true new Date() instanceof Date; // true function Person() {}; new Person() instanceof Person; [] instanceof Object; // true new Date() instanceof Object; // true new Person instanceof Object; // true
我们发现,虽然 instanceof 能够判断出 [ ] 是Array的实例,但它认为 [ ] 也是Object的实例,为什么呢?
我们来分析一下 [ ]、Array、Object 三者之间的关系:
从 instanceof 能够判断出 [ ].__proto__ 指向 Array.prototype,而 Array.prototype.__proto__ 又指向了Object.prototype,最终 Object.prototype.__proto__ 指向了null,标志着原型链的结束。因此,[]、Array、Object 就在内部形成了一条原型链:
从原型链可以看出,[] 的 __proto__ 直接指向Array.prototype,间接指向 Object.prototype,所以按照 instanceof 的判断规则,[] 就是Object的实例。依次类推,类似的 new Date()、new Person() 也会形成一条对应的原型链 。因此,instanceof 只能用来判断两个对象是否属于实例关系, 而不能判断一个对象实例具体属于哪种类型。
instanceof 操作符的问题在于,它假定只有一个全局执行环境。如果网页中包含多个框架,那实际上就存在两个以上不同的全局执行环境,从而存在两个以上不同版本的构造函数。如果你从一个框架向另一个框架传入一个数组,那么传入的数组与在第二个框架中原生创建的数组分别具有各自不同的构造函数。
var iframe = document.createElement('iframe'); document.body.appendChild(iframe); xArray = window.frames[0].Array; var arr = new xArray(1, 2, 3); // [1,2,3] arr instanceof Array; // false
针对数组的这个问题,ES5 提供了 Array.isArray() 方法 。该方法用以确认某个对象本身是否为 Array 类型,而不区分该对象在哪个环境中创建。
if (Array.isArray(value)) { // 对数组执行某些操作 }
Array.isArray() 本质上检测的是对象的 [[Class]] 值,[[Class]] 是对象的一个内部属性,里面包含了对象的类型信息,其格式为 [object Xxx] ,Xxx 就是对应的具体类型 。对于数组而言,[[Class]] 的值就是 [object Array] 。
(3)constructor
当一个函数 F被定义时,JS引擎会为F添加 prototype 原型,然后再在 prototype上添加一个 constructor 属性,并让其指向 F 的引用。如下所示:
当执行 var f = new F() 时,F 被当成了构造函数,f 是F的实例对象,此时 F 原型上的 constructor 传递到了 f 上,因此 f.constructor == F
可以看出,F 利用原型对象上的 constructor 引用了自身,当 F 作为构造函数来创建对象时,原型上的 constructor 就被遗传到了新创建的对象上, 从原型链角度讲,构造函数 F 就是新对象的类型。这样做的意义是,让新对象在诞生以后,就具有可追溯的数据类型。
同样,JavaScript 中的内置对象在内部构建时也是这样做的:
(4)toString
toString() 是 Object 的原型方法,调用该方法,默认返回当前对象的 [[Class]] 。这是一个内部属性,其格式为 [object Xxx] ,其中 Xxx 就是对象的类型。
对于 Object 对象,直接调用 toString() 就能返回 [object Object] 。而对于其他对象,则需要通过 call / apply 来调用才能返回正确的类型信息。
Object.prototype.toString.call(''); // [object String] Object.prototype.toString.call(1); // [object Number] Object.prototype.toString.call(true); // [object Boolean] Object.prototype.toString.call(Symbol()); //[object Symbol] Object.prototype.toString.call(undefined); // [object Undefined] Object.prototype.toString.call(null); // [object Null] Object.prototype.toString.call(new Function()); // [object Function] Object.prototype.toString.call(new Date()); // [object Date] Object.prototype.toString.call([]); // [object Array] Object.prototype.toString.call(new RegExp()); // [object RegExp] Object.prototype.toString.call(new Error()); // [object Error] Object.prototype.toString.call(document); // [object HTMLDocument] Object.prototype.toString.call(window); //[object global] window 是全局对象 global 的引用
3.10 时间处理相关(获取当前时间/时间戳)
- 参考:http://www.cnblogs.com/hanwenhua/articles/3853352.html
- 参考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Date
// 1. 获取当前时间(标准时间格式) // 1.1 获取当前时间:YYYY-MM-DD function getNowFormatDate1() { var date = new Date(); var currentdate = date.getFullYear() + '-' + ('0' + (date.getMonth() + 1)).slice(-2) + '-' + ('0' + date.getDate()).slice(-2); return currentdate; } console.log("getNowFormatDate1(): " + getNowFormatDate1()); // 1.2 获取当前时间:YYYY-MM-DD hh:mm:ss function getNowFormatDate4() { var date = new Date(); var currentdate = date.getFullYear() + '-' + ('0' + (date.getMonth() + 1)).slice(-2) + '-' + ('0' + date.getDate()).slice(-2) + ' ' + ('0' + date.getHours()).slice(-2) + ':' + ('0' + date.getMinutes()).slice(-2) + ':' + ('0' + date.getSeconds()).slice(-2); return currentdate; } console.log("getNowFormatDate4(): " + getNowFormatDate4()); // 2. 将时间戳转换成标准的时间格式,1398250549123: 2014-04-23 18:55:49 // 2.1 将时间戳转换成:YYYY-MM-DD function getTimeStamp1(timeStamp) { var date = new Date(timeStamp); var currentdate = date.getFullYear() + '-' + ('0' + (date.getMonth() + 1)).slice(-2) + '-' + ('0' + date.getDate()).slice(-2); return currentdate; } console.log("getTimeStamp1(1398250549123): " + getTimeStamp1(1398250549123)); // 2014-04-23 // 2.2 将时间戳转换成:YYYY-MM-DD hh:mm:ss function getTimeStamp2(timeStamp) { var date = new Date(timeStamp); var currentdate = date.getFullYear() + '-' + ('0' + (date.getMonth() + 1)).slice(-2) + '-' + ('0' + date.getDate()).slice(-2) + ' ' + ('0' + date.getHours()).slice(-2) + ':' + ('0' + date.getMinutes()).slice(-2) + ':' + ('0' + date.getSeconds()).slice(-2); return currentdate; } console.log("getTimeStamp2(1398250549123): " + getTimeStamp2(1398250549123)); // 2014-04-23 18:55:49 // 3. 将标准时间格式转换为时间戳 var startTime = '2014-04-23 18:55:49:123'; startTime = startTime.replace(/-/g, '/') // 可选操作:"2014-04-23 18:55:49:123" 转换为 "2014/04/23 18:55:49:123" console.log("startTime: " + startTime); var date = new Date(startTime); console.log("date: " + date); // Wed Apr 23 2014 18:55:49 GMT+0800 (中国标准时间) var time1 = date.getTime(); // 精确到毫秒 var time2 = date.valueOf(); // 精确到毫秒 var time3 = Date.parse(date); // 精确到秒,毫秒将用 0 来代替 console.log("time1: " + time1); console.log("time2: " + time2); console.log("time3: " + time3);
输出结果:略。
3.11 箭头函数( => )
基础语法:
(参数1, 参数2, …, 参数N) => { 函数声明 } //相当于:(参数1, 参数2, …, 参数N) =>{ return 表达式; } (参数1, 参数2, …, 参数N) => 表达式(单一) // 当只有一个参数时,圆括号是可选的: (单一参数) => {函数声明} 单一参数 => {函数声明} // 没有参数的函数应该写成一对圆括号。 () => {函数声明}
高级语法:
//加括号的函数体返回对象字面表达式: 参数=> ({foo: bar}) //支持剩余参数和默认参数 (参数1, 参数2, ...rest) => {函数声明} (参数1 = 默认值1,参数2, …, 参数N = 默认值N) => {函数声明} //同样支持参数列表解构 let f = ([a, b] = [1, 2], {x: c} = {x: a + b}) => a + b + c; f();
举例1:
var testFunc1 = () => console.log("hello"); var testFunc2 = (x) => console.log("x: " + x); var testFunc3 = (x, y) => console.log("x + y: " + (x + y)); testFunc1(); testFunc2("Hello, 2"); testFunc3(3, 5);
输出结果:
举例2:
var func1 = ({value1, value2}) => ({total: value1 + value2}) var result = func1({value1: 2, value2: 3}) console.log("result: " + result);
输出结果:
4、正则表达式
- 正则表达式在线测试:http://tool.oschina.net/regex/
- 参考:《JavaScript语言精粹》和http://www.runoob.com/js/js-regexp.html
- 参考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/RegExp
- 参考:https://www.cnblogs.com/coshaho/p/6653741.html
例如:字符串中 是否包含 某个/某些 值,可以用 "深圳广州南京" in (深圳|广州)
regexp.exec(string )和regexp.test(string )参考正则表达式示例。
正则表达式是一门简单语言的语法规范。它以方法的形式被用于对字符串中的信息进行查找、替换和提取操作。可处理正则表达式有regexp.exec、regexp.test、string.match、string.repleace、string.search和string.split(具体后续会详述它们)。
通常来说,在JavaScript中正则表达式相较于等效的字符串运算有着显著的性能优势。
- 正则表达式参数可用在以上方法中(替代字符串参数)。
- 正则表达式使得搜索功能更加强大(如实例中不区分大小写)。
4.1 语法
其中修饰符是可选的。
1 /正则表达式主体/修饰符(修饰符为可选)
实例:
1 var patt = /runoob/i;
实例解析:
- /runoob/i是一个正则表达式;
- runoob是一个正则表达式主体(用于检索);
- i是一个修饰符(搜索不区分大小写)
4.2 使用字符串方法(search()方法、replace()方法)
在 JavaScript 中,正则表达式通常用于两个字符串方法 : search() 和 replace()。
search() 方法 用于检索字符串中指定的子字符串,或检索与正则表达式相匹配的子字符串,并返回子串的起始位置。
replace() 方法 用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串,其返回值才是替换后的新字符串。
实例1,search()方法使用这则表达式:
// 使用正则表达式搜索"Runoob"字符串,且不区分大小写 // 返回子串的起始位置 var str = "Visit Runoob!"; var n = str.search(/Runoob/i); console.log(n);
输出结果:6
实例2,search()方法使用字符串:
var str = "Visit Runoob!"; var n = str.search("Runoob"); console.log(n);
输出结果:6
实例3,repleace()方法使用正则表达式:
// repleace()方法使用正则表达式 var str = "Visit Microsoft!"; console.log("str old: " + str); var txt = str.replace(/microsoft/i, "Runoob"); console.log("str self is not changed: " + str); console.log("str new (txt): " + txt);
输出结果:
str old: Visit Microsoft! str self is not changed: Visit Microsoft! str new (txt): Visit Runoob!
实例4,repleace()方法使用字符串:
// repleace()方法使用字符串 var str = "Visit Microsoft!"; console.log("str old: " + str); var txt = str.replace("Microsoft", "Runoob"); console.log("str self is not changed: " + str); console.log("str new (txt): " + txt);
输出结果:
str old: Visit Microsoft! str self is not changed: Visit Microsoft! str new (txt): Visit Runoob!
4.3 正则表达式修饰符
修饰符可以在全局搜索中不区分大小写:
修饰符 | 描述 |
---|---|
i | 执行对大小写不敏感的匹配。 |
g | 执行全局匹配(查找所有匹配而非在找到第一个匹配后停止)。 |
m | 执行多行匹配。 |
4.4 正则表达式模式
方括号用于查找某个范围内的字符:
表达式 | 描述 |
---|---|
[abc] | 查找方括号之间的任何字符。 |
[0-9] | 查找任何从 0 至 9 的数字。 |
(x|y) | 查找任何以 | 分隔的选项。 |
元字符是拥有特殊含义的字符:
元字符 | 描述 |
---|---|
\d | 查找数字。(digital) |
\s | 查找空白字符。(space) |
\b | 匹配单词边界。(boundary) |
\uxxxx | 查找以十六进制数 xxxx 规定的 Unicode 字符。 |
量词:
量词 | 描述 |
---|---|
n+ | 匹配任何包含至少一个 n 的字符串。 |
n* | 匹配任何包含零个或多个 n 的字符串。 |
n? | 匹配任何包含零个或一个 n 的字符串。 |
4.5 使用RegExp对象
在JavaScript中,RegExp对象是一个预定义了属性和方法的正则表达式对象。
4.6 使用test()
test() 方法是一个正则表达式方法。
test() 方法用于检测一个字符串是否匹配某个模式,如果字符串中含有匹配的文本,则返回 true,否则返回 false。
以下实例用于搜索字符串中的字符 "e":
// 搜索字符串中的字符'e' // 即判断字符串中是否包含字符'e',是返回true,否则返回false var patt = /e/; var ret; ret = patt.test("The best things in life are free."); console.log("1 ret_test: " + ret); // 以上语句等价于 console.log("2 ret_test: " + /e/.test("The best things in life are free."));
输出结果:
1 ret_test: true 2 ret_test: true
4.7 使用exec()
exec() 方法是一个正则表达式方法。
exec() 方法用于检索字符串中的正则表达式的匹配。
该函数返回一个数组,其中存放匹配的结果。如果未找到匹配,则返回值为 null。
以下实例用于搜索字符串中的字母 "e":
// 搜索字符串中的字符'e' // 即判断字符串中是否包含字符'e',是返回true,否则返回false console.log("3 ret_exec: " + /e/.exec("The best things in life are free."));
输出结果:3 ret_exec: e
4.8 其它正则表达式举例说明
举例1:JavaScript判断输入是否为数字、字母、下划线组成
var isValid = function(str) { return /^\w+$/.test(str); // '^'表示从字符串开头开始,'+$'表示到字符串结束 }; var str1 = "1234abd__"; console.log("'1234abd__' isValid? " + isValid(str1)); var str2 = "$32343"; console.log("'$32343' isValid? " + isValid(str2));
输出结果:
'1234abd__' isValid? true '$32343' isValid? false
举例2:JavaScript判断字符串是否全部为字母
方式一:
var isLetter = function(str) { return /^[a-zA-Z]+$/.test(str); // '^'表示从字符串开头开始,'+$'表示到字符串结束 }; var str1 = "1234abd__"; console.log("'1234abd__' isLetter? " + isLetter(str1)); var str2 = "$32343"; console.log("'$32343' isLetter? " + isLetter(str2)); var str3 = "hello"; console.log("'hello' isLetter? " + isLetter(str3)); var str4 = "hello, world"; console.log("'hello, world' isLetter? " + isLetter(str4));
输出结果:
'1234abd__' isLetter? false '$32343' isLetter? false 'hello' isLetter? true 'hello, world' isLetter? false
方式二:
var isLetter = function(str) { return /[a-zA-Z]/.test(str); // '^'表示从字符串开头开始,'+$'表示到字符串结束 }; var str1 = "1234abd__"; console.log("'1234abd__' isLetter? " + isLetter(str1)); var str2 = "$32343"; console.log("'$32343' isLetter? " + isLetter(str2)); var str3 = "hello"; console.log("'hello' isLetter? " + isLetter(str3)); var str4 = "hello, world"; console.log("'hello, world' isLetter? " + isLetter(str4)); var str5 = "hello, 1234"; console.log("'hello, 1234' isLetter? " + isLetter(str5)); var str6 = "1234, world"; console.log("'1234, world' isLetter? " + isLetter(str6));
输出结果:
'1234abd__' isLetter? true '$32343' isLetter? false 'hello' isLetter? true 'hello, world' isLetter? true 'hello, 1234' isLetter? true '1234, world' isLetter? true
方式三:
var isLetter = function(str) { return /^[a-zA-Z]/.test(str); // '^'表示从字符串开头开始,'+$'表示到字符串结束 }; var str1 = "1234abd__"; console.log("'1234abd__' isLetter? " + isLetter(str1)); var str2 = "$32343"; console.log("'$32343' isLetter? " + isLetter(str2)); var str3 = "hello"; console.log("'hello' isLetter? " + isLetter(str3)); var str4 = "hello, world"; console.log("'hello, world' isLetter? " + isLetter(str4)); var str5 = "hello, 1234"; console.log("'hello, 1234' isLetter? " + isLetter(str5)); var str6 = "1234, world"; console.log("'1234, world' isLetter? " + isLetter(str6));
输出结果:
'1234abd__' isLetter? false '$32343' isLetter? false 'hello' isLetter? true 'hello, world' isLetter? true 'hello, 1234' isLetter? true '1234, world' isLetter? false
方式四:
var isLetter = function(str) { return /[a-zA-Z]+$/.test(str); // '^'表示从字符串开头开始,'+$'表示到字符串结束 }; var str1 = "1234abd__"; console.log("'1234abd__' isLetter? " + isLetter(str1)); var str2 = "$32343"; console.log("'$32343' isLetter? " + isLetter(str2)); var str3 = "hello"; console.log("'hello' isLetter? " + isLetter(str3)); var str4 = "hello, world"; console.log("'hello, world' isLetter? " + isLetter(str4)); var str5 = "hello, 1234"; console.log("'hello, 1234' isLetter? " + isLetter(str5)); var str6 = "1234, world"; console.log("'1234, world' isLetter? " + isLetter(str6));
输出结果:
'1234abd__' isLetter? false '$32343' isLetter? false 'hello' isLetter? true 'hello, world' isLetter? true 'hello, 1234' isLetter? false '1234, world' isLetter? true
举例3:JavaScript判断字符串是否全为数字
var isNum = function(str) { return /^\d+$/.test(str); // '^'表示从字符串开头开始,'+$'表示到字符串结束 }; var str1 = "1234"; console.log("'1234' isNum? " + isNum(str1)); var str2 = "1234 3456"; console.log("'1234 3456' isNum? " + isNum(str2)); var str3 = "1234 abcd"; console.log("'1234 abcd' isNum? " + isNum(str3)); var str4 = "abcd 3456"; console.log("'abcd 3456' isNum? " + isNum(str4)); var str5 = "1234abd__"; console.log("'1234abd__' isNum? " + isNum(str5)); var str6 = "$32343"; console.log("'$32343' isNum? " + isNum(str6)); var str7 = "hello"; console.log("'hello' isNum? " + isNum(str7)); var str8 = "hello, world"; console.log("'hello, world' isNum? " + isNum(str8)); var str9 = "hello, 1234"; console.log("'hello, 1234' isNum? " + isNum(str9)); var str10 = "1234, world"; console.log("'1234, world' isNum? " + isNum(str10));
输出结果:
'1234' isNum? true '1234 3456' isNum? false '1234 abcd' isNum? false 'abcd 3456' isNum? false '1234abd__' isNum? false '$32343' isNum? false 'hello' isNum? false 'hello, world' isNum? false 'hello, 1234' isNum? false '1234, world' isNum? false
5、面向对象介绍
当前参考学习:MDN官方教程(JavaScript面向对象简介)
面向对象编程是用抽象方式创建基于现实世界模型的一种编程模式。它使用先前建立的范例,包括模块化,多态和封装几种技术。今天,许多流行的编程语言(如Java,JavaScript,C#,C+ +,Python,PHP,Ruby和Objective-C)都支持面向对象编程(OOP)。
面向对象程序设计的目的是在编程中促进更好的灵活性和可维护性,在大型软件工程中广为流行。凭借其对模块化的重视,面向对象的代码开发更简单,更容易理解,相比非模块化编程方法,它能更直接地分析,编码和理解复杂的情况和过程。
5.1 术语
Namespace 命名空间
允许开发人员在一个独特, 应用相关的名字的名称下捆绑所有功能的容器。
Class 类
定义对象的特征。它是对象的属性和方法的模板定义.
Object 对象
类的一个实例。
Property 属性
对象的特征,比如颜色。
Method 方法
对象的能力,比如行走。
Constructor 构造函数
对象初始化的瞬间, 被调用的方法. 通常它的名字与包含它的类一致.
Inheritance 继承
一个类可以继承另一个类的特征。
Encapsulation 封装
一种把数据和相关的方法绑定在一起使用的方法.
Abstraction 抽象
结合复杂的继承,方法,属性的对象能够模拟现实的模型。
Polymorphism 多态
多意为‘许多’,态意为‘形态’。不同类可以定义相同的方法或属性。
更多关于面向对象编程的描述,请参照*的 面向对象编程 。
5.2 原型编程
基于原型的编程不是面向对象编程中体现的风格,且行为重用(在基于类的语言中也称为继承)是通过装饰它作为原型的现有对象的过程实现的。这种模式也被称为弱类化,原型化,或基于实例的编程。
原始的(也是最典型的)基于原型语言的例子是由大卫·安格尔和兰德尔·史密斯开发的。然而,弱类化的编程风格近来变得越来越流行,并已被诸如JavaScript,Cecil,NewtonScript,IO,MOO,REBOL,Kevo,Squeak(使用框架操纵Morphic组件),和其他几种编程语言采用。1
5.3 JavaScript面向对象编程
(1)命名空间
命名空间是一个容器,它允许开发人员在一个独特的,特定于应用程序的名称下捆绑所有的功能。 在JavaScript中,命名空间只是另一个包含方法,属性,对象的对象。
创造的JavaScript命名空间背后的想法很简单:一个全局对象被创建,所有的变量,方法和功能成为该对象的属性。使用命名空间也最大程度地减少应用程序的名称冲突的可能性。
我们来创建一个全局变量叫做 MYAPP
// 全局命名空间 var MYAPP = MYAPP || {};
在上面的代码示例中,我们首先检查MYAPP是否已经被定义(是否在同一文件中或在另一文件)。如果是的话,那么使用现有的MYAPP全局对象,否则,创建一个名为MYAPP的空对象用来封装方法,函数,变量和对象。
我们也可以创建子命名空间:
// 子命名空间 MYAPP.event = {};
下面是用于创建命名空间和添加变量,函数和方法的代码写法:
// 给普通方法和属性创建一个叫做MYAPP.commonMethod的容器 MYAPP.commonMethod = { regExForName: "", // 定义名字的正则验证 regExForPhone: "", // 定义电话的正则验证 validateName: function(name){ // 对名字name做些操作,你可以通过使用“this.regExForname” // 访问regExForName变量 }, validatePhoneNo: function(phoneNo){ // 对电话号码做操作 } } // 对象和方法一起申明 MYAPP.event = { addListener: function(el, type, fn) { // 代码 }, removeListener: function(el, type, fn) { // 代码 }, getEvent: function(e) { // 代码 } // 还可以添加其他的属性和方法 } //使用addListener方法的写法: MYAPP.event.addListener("yourel", "type", callback);
(2)标准内置对象
JavaScript有包括在其核心的几个对象,例如,Math,Object,Array和String对象。下面的例子演示了如何使用Math对象的random()方法来获得一个随机数。
console.log(Math.random()); // 比如输出值为:0.1400815874857133
(3)自定义对象
JavaScript是一种基于原型的语言,它没类的声明语句,比如C+ +或Java中用的。这有时会对习惯使用有类申明语句语言的程序员产生困扰。相反,JavaScript可用方法作类。定义一个类跟定义一个函数一样简单。在下面的例子中,我们定义了一个新类Person。
类:
function Person() { // 函数体 } // 或者 var Person = function() { // 函数体 };
对象(类的实例):
我们使用 new obj
创建对象 obj
的新实例, 将结果(obj 类型
)赋值给一个变量方便稍后调用。
在下面的示例中,我们定义了一个名为Person
的类,然后我们创建了两个Person
的实例(person1
and person2
)。
// 定义一个新类Person function Person() { // 函数体 } // 创建两个Person的实例 var person1 = new Person(); var person2 = new Person();
注意:有一种新增的创建未初始化实例的实例化方法,请参考 Object.create 。
构造器:
在实例化时构造器被调用 (也就是对象实例被创建时)。构造器是对象中的一个方法。 在JavaScript,中函数就可以作为构造器使用,因此不需要特别地定义一个构造器方法。每个声明的函数都可以在实例化后被调用执行构造器常用于给对象的属性赋值或者为调用函数做准备。 在本文的后面描述了类中方法既可以在定义时添加,也可以在使用前添加。
在下面的示例中, Person类实例化时构造器调用一个
alert函数。
// 定义一个新类Person function Person() { // 函数体 alert('Person instantiated'); } // 创建两个Person的实例 var person1 = new Person(); var person2 = new Person();
属性(对象属性):
属性就是 类中包含的变量;每一个对象实例有若干个属性。为了正确的继承,属性应该被定义在类的原型属性(函数)中。
// 定义一个新类Person function Person(firstName) { this.firstName = firstName; console.log('Person instantiated'); } // 创建两个Person的实例 var person1 = new Person('Alice'); var person2 = new Person('Bob'); // Show the firstName properties of the objects console.log('person1 is ' + person1.firstName); // person1 is Alice console.log('person2 is ' + person2.firstName); // person2 is Bob
方法(对象属性):
// 定义一个新类Person function Person(firstName) { this.firstName = firstName; console.log('Person instantiated'); } Person.prototype.sayHello = function() { console.log("Hello, I'm " + this.firstName); } // 创建两个Person的实例 var person1 = new Person('Alice'); var person2 = new Person('Bob'); // Show the firstName properties of the objects console.log('person1 is ' + person1.firstName); // person1 is Alice console.log('person2 is ' + person2.firstName); // person2 is Bob person1.sayHello(); // Hello, I'm Alice person2.sayHello(); // Hello, I'm Bob
在JavaScript中方法通常是一个绑定到对象中的普通函数,这意味着可以在其所在的context之外被调用。思考以下示例中的代码:
// 定义一个新类Person function Person(firstName) { this.firstName = firstName; console.log('Person instantiated'); } Person.prototype.sayHello = function() { console.log("Hello, I'm " + this.firstName); } // 创建两个Person的实例 var person1 = new Person('Alice'); var person2 = new Person('Bob'); var helloFunction = person1.sayHello; // Show the firstName properties of the objects console.log('person1 is ' + person1.firstName); // person1 is Alice console.log('person2 is ' + person2.firstName); // person2 is Bob person1.sayHello(); // Hello, I'm Alice person2.sayHello(); // Hello, I'm Bob helloFunction(); // Hello, I'm undefined console.log(helloFunction === person1.sayHello); // true console.log(helloFunction === Person.prototype.sayHello); // true helloFunction.call(person1); // Hello, I'm Alice
如上例所示,所有指向sayHello函数的引用,包括person1,Person.prototype,和helloFunction等,均引用了相同的函数。
在调用函数的过程中,this的值取决于我们怎么样调用函数. 在通常情况下,我们通过一个表达式person1.sayHello()来调用函数:即从一个对象的属性中得到所调用的函数。此时this被设置为我们取得函数的对象(即person1)。这就是为什么person1.sayHello()
使用了姓名“Alice”而person2.sayHello()使用了姓名“bob”的原因。
然而我们使用不同的调用方法时, this的值也就不同了
。当从变量 helloFunction()中调用的时候,
this
就被设置成了全局对象 (在浏览器中即window
)。由于该对象 (非常可能地) 没有firstName
属性, 我们得到的结果便是"Hello, I'm undefined". (这是松散模式下的结果, 在严格模式中,结果将不同(此时会产生一个error)。 但是为了避免混淆,我们在这里不涉及细节) 。另外,我们可以像上例末尾那样,使用Function#call
(或者Function#apply
)显式的设置this的值。
继承:
创建一个或多个类的专门版本类方式称为继承(JavaScript只支持单继承)。创建的专门版本对的类通常叫做子类,另外的类通常叫做父类。在JavaScript中,继承通过赋予子类一个父类的实例并专门化子类来实现呢。在现代浏览器中可以使用Object.create实现继承。
JavaScript 并不检测子类的 prototype.constructor
(见 Object.prototype), 所以我们必须手动申明它.
下面的例子,我们定义了 Student类作为
Person类的子类
. 之后我们重定义了sayHello()
方法并添加了 sayGoodBye() 方法
.
// 定义一个Person构造器 function Person(firstName) { this.firstName = firstName; console.log('Person instantiated'); } // 在Person.prototype中加入方法 Person.prototype.walk = function() { console.log("I am walking"); }; Person.prototype.sayHello = function() { console.log("(Person) Hello, I'm " + this.firstName); }; // 定义Student构造器 function Student(firstName, subject) { // 调用父类构造器,确保(使用Function#call)"this"在调用过程中设置正确 Person.call(this, firstName); // 初始化Student类特有属性 this.subject = subject; } // 建立一个由Person.prototype继承而来的Student.prototype对象 // 注意:常见的错误是使用new Person()来建立Student.prototype // 这样做的错误之处有很多, 最重要的一点是我们在实例化时 // 不能赋予Person类任何的FirstName参数 // 调用Person的正确位置如下,我们从Student中来调用它 Student.prototype = Object.create(Person.prototype); // Student.prototype更换"sayHello"方法 Student.prototype.sayHello = function() { console.log("(Student) Hello, I'm " + this.firstName + ". I'm studying " + this.subject + "."); } // Student.prototype加入"sayGoodbye"方法 Student.prototype.sayGoodbye = function() { console.log("(Student) Goodbye!"); } // 测试实例 var person1 = new Person('(Person) Alice'); var student1 = new Student("(Student) Janet", "Applied Physics"); person1.sayHello(); // (Person) Hello, I'm (Person) Alice student1.sayHello(); // (Student) Hello, I'm (Student) Janet. I'm studying Applied Physics. student1.sayGoodbye(); // (Student) Goodbye! console.log(student1 instanceof Person); // true console.log(student1 instanceof Student); // true
封装:
在上一个例子中,Student类虽然不需要知道Person类的walk()方法是如何实现的,但是仍然可以使用这个方法;Student类不需要明确地定义这个方法,除非我们想改变它。 这就叫做封装,对于所有继承自父类的方法,只需要在子类中定义那些你想改变的即可。
抽象:
抽象是允许模拟工作问题中通用部分的一种机制。这可通过继承(具体化/实现)或组合来实现。JavaScript通过继承实现具体化,通过让类的实例是其它对相关的属性来实现组合。
JavaScript Function 类继承自Object类(这是典型的具体化) 。Function.prototype的属性是一个Object实例(这是典型的组合)。
var foo = function() {}; console.log("foo is a Function: " + (foo instanceof Function)); // foo is a Function: true console.log( 'foo.prototype is an Object: ' + (foo.prototype instanceof Object) ); // foo.prototype is an Object: true
多态:
就像所有定义在原型属性内部的方法和属性一样,不同的类可以定义具有相同名称的方法;方法是作用于所在的类中。并且这仅在两个类不是父子关系时成立(继承链中,一个类不是继承自其他类)。
6、继承与原型链
6.1 继承与原型链(MDN)
举例:
// 1. 语法结构创建的对象 // 创建一个自身拥有属性a和b的函数里创建一个对象o function f() { this.a = 1; this.b = 2; } var o = new f(); // 在f函数的原型上定义属性 f.prototype.c = 3; f.prototype.d = 3; f.prototype.func = function () { console.log("1.5 hello"); } console.log("1.1 f: " + f); console.log("1.2 f.prototype: " + JSON.stringify(f.prototype)); console.log("1.3 f.prototype.prototype: " + f.prototype.prototype); console.log("1.4 o.a: " + o.a + ", o.b: " + o.b + ", o.c: " + o.c + ", o.d: " + o.d); o.func(); var o1 = { a: 2, m: function () { return this.a + 2; } } console.log("1.6 " + o1.m()); // 当调用 o.m 时,"this" 指向了 o1. var p = Object.create(o1); // p 是一个继承来自 o1 的对象 p.a = 4; console.log("1.7 " + p.m()); console.log("1.8 " + Object.prototype + "\n"); // 2. 构造器创建的对象 function Graph() { this.vertices = []; this.edges = []; } Graph.prototype = { addVertex: function (v) { this.vertices.push(v); } }; var g = new Graph(); g.addVertex(15); console.log("2.1 g.vertices[0]: " + g.vertices[0] + "\n"); // 3. Object.create创建的对象 var aa = { a1: 1 }; // aa ---> Object.prototype ---> null console.log("3.1 " + aa.hasOwnProperty); var b = Object.create(aa); // 创建一个新的对象 // b ---> aa ---> Object.prototype ---> null console.log("3.2 " + b.hasOwnProperty); var c = Object.create(b); // 创建一个新的对象 // c ---> b ---> aa ---> Object.prototype ---> null console.log("3.3 " + c.hasOwnProperty); var d = Object.create(null); // 创建一个新的对象 // d ---> null console.log("3.4 " + d.hasOwnProperty); // undefined, 因为d没有继承Object.prototype // 4. class 关键字创建的对象 "use strict"; class Polygon { constructor(height, width) { this.height = height; this.width = width; } } class Square extends Polygon { constructor(sideLength) { super(sideLength, sideLength); } get area() { return this.height * this.width; } set sideLength(newLength) { this.height = newLength; this.width = newLength; } call() { console.log("4.2 hello"); } } var square = new Square(2); console.log("4.1 square.height: " + square.height + ", square.width: " + square.width + "\n"); // 5. 性能 // 在原型链上查找属性比较耗时,对性能有副作用,这在性能要求苛刻的情况下很重要。另外,试图访问不存在的属性时会遍历整个原型链。 // 遍历对象的属性时,原型链上的每个可枚举属性都会被枚举出来。 // 要检查对象是否具有自己定义的属性,而不是其原型链上的某个属性,则必须使用所有对象从Object.prototype继承的 hasOwnProperty 方法。 console.log("5.1 " + square.hasOwnProperty("height")); console.log("5.2 " + square.hasOwnProperty("height2")); // hasOwnProperty 是 JavaScript 中处理属性并且不会遍历原型链的方法之一。(另一种方法: Object.keys()) // 注意:检查属性是否undefined还不够。该属性可能存在,但其值恰好设置为undefined。
输出结果:略。
6.2 继承(《JavaScript语言精粹》)
(1)伪类
当一个函数对象被创建时,Function构造器产生的函数对象会运行类似这样的一些代码:
this.prototype = {constructor: this};
新函数对象被赋予一个prototype属性,其值是包含一个constructor属性且属性值为该新函数对象。该prototype对象是存放继承特征的地方。因为JavaScript语言没有提供一种方法确定哪个函数式打算用来作构造器的,所以每个函数都会得到一个prototype对象。constructor属性没什么用。重要的是prototype对象。
我们可以定义一个构造器并扩充它的原型:
var Mammal = function (name) { this.name = name; }; Mammal.prototype.get_name = function () { return this.name; }; Mammal.prototype.says = function () { return this.saying || ''; }; // 现在可以构造一个实例 var myMammal = new Mammal("Herb the Mammal"); var name = myMammal.get_name(); console.log(name);
输出结果:Herb the Mammal
我们可以构造另一个伪类来继承Mammel,这是通过定义它的constructor函数并替换它的prototype为一个Mammal的实例来实现的:
var Mammal = function (name) { this.name = name; }; Mammal.prototype.get_name = function () { return this.name; }; Mammal.prototype.says = function () { return this.saying || ''; }; // // 现在可以构造一个实例 // var myMammal = new Mammal("Herb the Mammal"); // var name = myMammal.get_name(); // console.log(name); var Cat = function (name) { this.name = name; this.saying = 'meow'; }; // 替换Cat.prototype为一个新的Mammal实例 Cat.prototype = new Mammal(); // 扩充新原型对象,增加purr和get_name方法 Cat.prototype.purr = function (n) { var i, s = ''; for (i = 0; i < n; i++) { if (s) { s += '-'; } s += 'r'; } return s; }; Cat.prototype.get_name = function () { return this.says() + ' ' + this.name + ' ' + this.says(); }; var myCat = new Cat('Henrietta'); var says = myCat.says(); console.log(says); // 'meow' var purr = myCat.purr(5); console.log(purr); // r-r-r-r-r var name = myCat.get_name(); console.log(name); // 'meow Henrietta meow'
输出结果:
r-r-r-r-r meow Henrietta meow
伪类模式本意是想向面向对象靠拢,但它看起来格格不入。我们可以隐藏一些丑陋的细节,这是通过使用method方法定义一个inherits方法来实现的。
(2)对象说明符(属性特性)
有时候,构造器要接受一大串的参数。这可能是令人烦恼的,因为要记住参数的顺序可能非常困难。在这种情况下,如果我们在编写构造器时使其接受一个简单的对象说明符可能会更加友好。那个对象包含了将要构建的对象规格说明。所以,语气这样写:
1 var myObject = maker(f, l, m, c, s);
不如这么写:
var myObject = maker({ first: f, last: l, state: s, city: c });
现在多个参数可以任意顺序排列,如果构造器会聪明地使用默认值,一些参数可以忽略掉,并且代码也更容易阅读。
举例说明
先创建一个对象:
var person = { name: "Nicholas", _job: "Software Engineer", sayName: function () { console.log(this.name); }, get job() { return this._job; }, set job(newJob) { this._job = newJob; } };
在这个对象中,我们定义了一个name属性和一个_job属性;至于set和get开头的两处代码,它们共同定义了一个属性job。明显属性job和_job、name的属性是不同的。是的,JavaScript中的对象由两种不同类型的属性:数据属性和访问器属性。name和_job是数据属性,job是访问器属性数据属性和访问器属性最大的不同在于:当访问一个访问器属性时,得到get后面函数的返回值;给访问器属性赋值时,执行的是set后面的函数,这个函数以赋的值为参数:
console.log(person.name); // 输出Nicholas console.log(person._job); // "Software Engineer" console.log(person.job); // "Software Engineer" person.job = 'Coder'; console.log(person.job); // Coder console.log(person._job); // Coder
上述代码中,在set函数中我们通过this改变了_job的值(this指向person这个对象)。
我们在创建person对象时没有为它的属性们直接指定特征值,JavaScript自动为它们创建了属性特性。在ES3中属性特性不可访问,但是ES5中属性的特性可以通过Object.getOwnPropertyDescriptors或Object.getOwnPropertyDescriptor得到:
var descriptors= Object.getOwnPropertyDescriptors(person); console.log(descriptors);
输出结果如下(descriptors的属性):
{ name: { value: 'Nicholas', writable: true, enumerable: true, configurable: true }, _job: { value: 'Coder', writable: true, enumerable: true, configurable: true }, sayName: { value: [Function: sayName], writable: true, enumerable: true, configurable: true }, job: { get: [Function: get job], set: [Function: set job], enumerable: true, configurable: true } }
1)数据属性
数据属性的描述符的有四个属性分别是:
- value:包含这个属性的数据值。读取属性的时候,从这个位置读;写入属性值的时候,把新值保存在这个位置。默认为undefined。
- writable:表示能否修改属性的值。是一个bool值,默认为true。
- enumerable:属性是否可枚举,即能否通过for-in循环返回属性。是一个bool值,默认为true。
- configrable:属性是否可配置。即属性能否通过delete删除,能否修改属性的特性,或者能否把属性修改为访问器属性。是一个bool值,默认为true。
我们最初开始创建的person对象的属性name,它的value为“Nicholas”,其他描述符为true。
var person ={}; // 除了configrable之外,其他三个属性相互之间不会影响,读者可以自己测试 console.log('---------------------1 writable start---------------------'); Object.defineProperty(person, "name", { writable: false, enumerable: true, configurable: true, value: "Nicholas" }); console.log("1.1 " + person.name); // "Nicholas" person.name = "Greg"; // writable为false,属性不可修改 console.log("1.2 " + person.name); // "Nicholas" // writable为false,但configrable为true,我们可以重新配置属性描述符, Object.defineProperty(person, "name", { writable: false, enumerable: true, configurable: true, value: "John" }); console.log("1.3 " + person.name) // John delete person.name //但configrable为true,属性可以被删除 console.log("1.4 " + person.name) // undefined console.log('---------------------1 writable end---------------------\n'); console.log('---------------------2 enumerable start---------------------'); var person = {}; Object.defineProperty(person, "name", { writable: false, enumerable: true, configurable: true, value: "Nicholas" }); // enumerable为true属性可枚举 for (var prop in person) { console.log("2.1 " + prop); // name } Object.defineProperty(person, "name", { writable: false, enumerable: false, configurable: true, value: "Nicholas" }); // enumerable为false属性不可枚举,循环体不执行 for (var prop in person) { console.log("2.2 " + prop) // } console.log('---------------------2 enumerable end---------------------\n'); console.log('---------------------3 configurable start---------------------'); var person = {}; Object.defineProperty(person, "name", { writable: true, enumerable: true, configurable: false, value: "Nicholas" }); // configurable为false,writable为true,属性仍然可修改 person.name = "John" console.log("3.1 " + person.name); // John // configurable为false,writable为true,仍然可以通过配置的方式改变属性值 Object.defineProperty(person, "name", { writable: true, enumerable: true, configurable: false, value: "Nicholas" }); console.log("3.2 " + person.name) // Nicholas // configurable为false,enumerable为ture,属性可枚举 for (var prop in person) { console.log("3.3 " + prop) // name } // configurable为false,我们仍然可以把writable属性由true改为false Object.defineProperty(person, "name", { writable: false, enumerable: true, configurable: false, value: "Nicholas" }); console.log("3.4 " + Object.getOwnPropertyDescriptor(person, "name"))//{value: "Nicholas", writable: false, enumerable: true, configurable: false} // configurable为false,writable为false,不能通过配置改变value的值 try{ Object.defineProperty(person, "name", { writable: false, enumerable: true, configurable: false, value: "John" }); } catch(error) { console.log("3.5 " + "value change error"); console.log(Object.getOwnPropertyDescriptor(person, "name"))//{value: "Nicholas", writable: false, enumerable: true, configurable: false} } // configurable为false,但是不能把writable属性由false改为true try{ Object.defineProperty(person, "name", { writable: true, enumerable: true, configurable: false, value: "Nicholas" }); } catch(error) { console.log("3.6 " + "writable false to true error") console.log(Object.getOwnPropertyDescriptor(person, "name"))//{value: "Nicholas", writable: false, enumerable: true, configurable: false} } // configurable为false,不能改变enumerable的值 try{ Object.defineProperty(person, "name", { writable: false, enumerable: false, configurable: false, value: "Nicholas" }); } catch(error) { console.log("3.7 " + "enumerable change error"); console.log(Object.getOwnPropertyDescriptor(person, "name"))//{value: "Nicholas", writable: false, enumerable: true, configurable: false} } var person = {}; Object.defineProperty(person, "name", { writable: true, enumerable: true, configurable: true, value: "Nicholas" }); // configurable为true,可以把数据属性修改为访问器属性 try{ Object.defineProperty(person, "name", { get: function(){return "Nicholas"}, enumerable: true, configurable: false }); } catch(error) { console.log("3.8 " + "get error"); } console.log("3.9 " + Object.getOwnPropertyDescriptor(person, "name"))//{set: undefined, enumerable: true, configurable: false, get: ƒ} var person = {}; Object.defineProperty(person, "name", { writable: true, enumerable: true, configurable: false, value: "Nicholas" }); // configurable为false,不可以把数据属性修改为访问器属性 try{ Object.defineProperty(person, "name", { get: function(){return "Nicholas"}, enumerable: true, configurable: false }); } catch(error) { console.log("3.10 " + "get error"); } console.log("3.11 " + Object.getOwnPropertyDescriptor(person, "name"))//{value: "Nicholas", writable: true, enumerable: true, configurable: false} console.log('---------------------3 configurable end---------------------\n');
输出结果:
1.3 John 1.4 undefined ---------------------1 writable end--------------------- ---------------------2 enumerable start--------------------- 2.1 name ---------------------2 enumerable end--------------------- ---------------------3 configurable start--------------------- 3.1 John 3.2 Nicholas 3.3 name { value: 'Nicholas', writable: false, enumerable: true, configurable: false } 3.5 value change error { value: 'Nicholas', writable: false, enumerable: true, configurable: false } 3.6 writable false to true error { value: 'Nicholas', writable: false, enumerable: true, configurable: false } 3.7 enumerable change error { value: 'Nicholas', writable: false, enumerable: true, configurable: false } 3.9 [object Object] 3.10 get error 3.11 [object Object] ---------------------3 configurable end---------------------
2)访问器属性
访问器属性的描述符的有四个属性分别是:
- get:在读取属性时调用的函数。默认值为 undefined。
- set:在写入属性时调用的函数。默认值为 undefined。
- enumerable:属性是否可枚举,即能否通过for-in循环返回属性。是一个bool值,默认为true。
- configrable:属性是否可配置。即属性能否通过delete删除,能否修改属性的特性,或者能否把属性修改为数据属性。是一个bool值,默认为true。
我们最初开始创建的person对象的属性job,它的get和set值分别是我们指定的函数,其他描述符为true。
要修改属性默认的特性,必须使用 ECMAScript 5 的 Object.defineProperty或 Object.defineProperties。
有了定义属性特性的方法,那我们通过代码来探索下这些属性特性的作用:
console.log("----------------------1 访问器属性开始----------------------"); var person = {_name: "Nicholas"}; Object.defineProperty(person, "name", { get: function () { console.log("1.1 get被调用"); return this._name; }, set: function (newName) { console.log("1.2 set被调用"); this._name = newName; }, enumerable: true, configurable: true }); person.name; // get被调用 person.name = "John"; // set被调用 console.log("----------------------1 访问器属性结束----------------------\n"); console.log("----------------------2 不设set开始----------------------"); var person = {_name: "Nicholas"}; Object.defineProperty(person, "name", { get: function () { console.log("2.1 get被调用"); return this._name; }, enumerable: true, configurable: true }); person.name; // get被调用 person.name = "John"; // 没有设置set,什么也没发生 console.log("2.2 " + person.name) // Nicholas console.log("----------------------2 不设set结束----------------------\n"); console.log("----------------------3 不设get开始----------------------"); var person = {_name: "Nicholas"}; Object.defineProperty(person, "name", { set: function (newName) { console.log("3.1 set被调用"); this._name = newName; }, enumerable: true, configurable: true }); console.log("3.2 " + person.name); // 没有get,得到 undefined console.log("3.3 " + person._name); // Nicholas person.name = "John"; // set被调用 console.log("3.4 " + person._name) // John,通过set,_name的值被改变 console.log("----------------------3 不设get结束----------------------\n"); console.log("----------------------4 不设get set开始----------------------"); // 虽然不报错,但是这个属性没有任何意义 var person = {_name: "Nicholas"}; Object.defineProperty(person, "name", { enumerable: true, configurable: true }); console.log("----------------------4 不设get set结束----------------------\n"); console.log("----------------------5 enumerable 开始----------------------"); var person = {_name: "Nicholas"}; Object.defineProperty(person, "name", { get: function () { console.log("5.1 " + "get被调用"); return this._name; }, set: function (newName) { console.log("5.2 " + "set被调用"); this._name = newName; }, enumerable: true, configurable: true }); for (var prop in person) { console.log("5.3 " + prop); // _name,name } Object.defineProperty(person, "name", { get: function () { console.log("5.4 " + "get被调用"); return this._name; }, set: function (newName) { console.log("5.5 " + "set被调用"); this._name = newName; }, enumerable: false, configurable: true }); for (var prop in person) { console.log("5.6 " + prop); // _name } console.log("----------------------5 enumerable 结束----------------------\n"); console.log("----------------------6 configurable 开始----------------------"); var person = {_name: "Nicholas"}; Object.defineProperty(person, "name", { get: function () { console.log("6.1 " + "get被调用"); return this._name; }, set: function (newName) { console.log("6.2 " + "set被调用"); this._name = newName; }, enumerable: true, configurable: false }); person.name; // get被调用 person.name = "John"; // set被调用 console.log("6.3 " + person.name); // John // configurable: false,重新配置访问器属性报错 try{ Object.defineProperty(person, "name", { get: function () { console.log("6.4 " + "get被调用"); return this._name; }, set: function (newName) { console.log("6.5 " + "set被调用"); this._name = newName; }, enumerable: true, configurable: false }); } catch (e) { console.log("6.6 " + "不能重新定义name的属性标识符"); } // configurable: false,重新配置数据属性报错 try{ Object.defineProperty(person, "name", { value: "123", writable: true, enumerable: true, configurable: false }); } catch (e) { console.log("6.7 " + "不能重新定义name的属性标识符"); } console.log("----------------------6 configurable 结束----------------------\n");
输出结果:
----------------------1 访问器属性开始---------------------- 1.1 get被调用 1.2 set被调用 ----------------------1 访问器属性结束---------------------- ----------------------2 不设set开始---------------------- 2.1 get被调用 2.1 get被调用 2.2 Nicholas ----------------------2 不设set结束---------------------- ----------------------3 不设get开始---------------------- 3.2 undefined 3.3 Nicholas 3.1 set被调用 3.4 John ----------------------3 不设get结束---------------------- ----------------------4 不设get set开始---------------------- ----------------------4 不设get set结束---------------------- ----------------------5 enumerable 开始---------------------- 5.3 _name 5.3 name 5.6 _name ----------------------5 enumerable 结束---------------------- ----------------------6 configurable 开始---------------------- 6.1 get被调用 6.2 set被调用 6.1 get被调用 6.3 John 6.6 不能重新定义name的属性标识符 6.7 不能重新定义name的属性标识符 ----------------------6 configurable 结束----------------------
3)可扩展性
ES5定义了三个方法Object.preventExtensions、Object.seal、Object.freeze分别定义了不同级别的可扩展性。可点击连接前往MDN阅读。
4)最后:get、set 与继承
举例:
function Person () {} var p = Person.prototype; p._name = "John"; Object.defineProperty(p, "name", { get: function () {return this._name;}, set: function (newName) {this._name = newName;}, enumerable: true, configurable: true }); var person = new Person(); console.log("1 " + person.name); // John console.log("2 " + person.hasOwnProperty("_name")); // false console.log("3 " + person.hasOwnProperty("name")); // false // 虽然name属性的set get方法是定义在原型中的 // 但是通过person调用时,它们的this属性会指向person // 所以通过person.name设置属性时,执行set方法中this._name = newName时 // 会给person对象添加_name属性,并把newName赋给person新建的属性_name person.name = "Nicholas"; console.log("4 " + person.name); // Nicholas console.log("5 " + p._name); // John console.log("6 " + person.hasOwnProperty("name")); // false console.log("7 " + person.hasOwnProperty("_name")); // true
(3)函数化
函数化构造器的伪代码模板:
// 函数化构造器的伪代码模板 var constructor = function (spec, my) { var that, 其它的私有实例变量; my = my || {}; // 把共享的变量和函数添加到my中 that = 一个新对象; 添加给that的特权方法 return that; };
上述模板中,spec对象包含构造器要构造一个新实例的所有信息。spec的内容可能会被复制到私有变量中,或者被其它函数改变。或者方法可以在需要的时候访问spec的信息(一个简化的方式是替代spec为一个单一的值。当构造对象时并不需要整个spec的时候,这是有用的)。my对象是一个为伪继承链中的构造器提供秘密共享的容器。my对象可以选择性地使用。如果没有传入一个my对象,那么会创建一个my对象。
var mammal = function (spec) { var that = {}; that.get_name = function () { return spec.name; }; that.says = function () { return spec.saying || ''; }; return that; }; var myMammal_1 = mammal({name: 'Herb'}); console.log("1.1 " + myMammal_1.get_name()); console.log("1.2 " + myMammal_1.says()); var myMammal_2 = mammal({name: 'Herb', saying: 'Hello, world!'}); console.log("2.1 " + myMammal_2.get_name()); console.log("2.2 " + myMammal_2.says()); // 在伪类模式中,构造器函数Cat不得不重复构造器Mammal已经完成的工作。 // 在函数话模式中那不再需要了,因为构造器将会调用构造器Mammal, // 让Mammal去做创建对象中的大部分工作,所以Cat只需关注自身的差异即可。 var cat = function (spec) { spec.saying = spec.saying || 'meow'; var that = mammal(spec); that.purr = function (n) { var i, s = ''; for (i = 0; i < n; i++) { if (s) { s += '-'; } s += 'r'; } return s; }; that.get_name = function () { return this.says() + " " + spec.name + " " + this.says(); }; return that; }; var myCat = cat({name: 'Henrietta'}); console.log("3.1 " + myCat.get_name()); console.log("3.2 " + myCat.purr(5));
输出结果:
1.1 Herb 1.2 2.1 Herb 2.2 Hello, world! 3.1 meow Henrietta meow 3.2 r-r-r-r-r
7、严格模式
- 严格模式通过抛出错误来消除了一些原有静默错误。
- 严格模式修复了一些导致 JavaScript引擎难以执行优化的缺陷:有时候,相同的代码,严格模式可以比非严格模式下运行得更快。
- 严格模式禁用了在ECMAScript的未来版本中可能会定义的一些语法。
(1)为脚本开启严格模式
为整个脚本文件开启严格模式,需要在所有语句之前放一个特定语句 "use strict";
(或 'use strict';
)
// 整个脚本都开启严格模式的语法 "use strict"; var v = "Hi! I'm a strict mode script!";
这种语法存在陷阱,有一个大型网站已经被它坑倒了:不能盲目的合并冲突代码。试想合并一个严格模式的脚本和一个非严格模式的脚本:合并后的脚本代码看起来是严格模式。反之亦然:非严格合并严格看起来是非严格的。合并均为严格模式的脚本或均为非严格模式的都没问题,只有在合并严格模式与非严格模式有可能有问题。建议按一个个函数去开启严格模式(至少在学习的过渡期要这样做).
您也可以将整个脚本的内容用一个函数包括起来,然后在这个外部函数中使用严格模式。这样做就可以消除合并的问题,但是这就意味着您必须要在函数作用域外声明一个全局变量。
(2)为函数开启严格模式
同样的,要给某个函数开启严格模式,得把 "use strict";
(或 'use strict';
)声明一字不漏地放在函数体所有语句之前。
function strict() { // 函数级别严格模式语法 'use strict'; function nested() { return "And so am I!"; } return "Hi! I'm a strict mode function! " + nested(); } function notStrict() { return "I'm not strict."; }
输出结果:略。
8、JavaScript库
8.1 JavaScript 框架(库)
JavaScript 高级程序设计(特别是对浏览器差异的复杂处理),通常很困难也很耗时。
为了应对这些调整,许多的 JavaScript (helper) 库应运而生。
这些 JavaScript 库常被称为 JavaScript 框架。
在本教程中,我们将了解到一些广受欢迎的 JavaScript 框架:
- jQuery
- Prototype
- MooTools
所有这些框架都提供针对常见 JavaScript 任务的函数,包括动画、DOM(Document Object Model) 操作以及 Ajax 处理。
在本教程中,您将学习到如何开始使用它们,来使得 JavaScript 编程更容易、更安全且更有乐趣。
8.2 jQuery
jQuery 是目前最受欢迎的 JavaScript 框架。
它使用 CSS 选择器来访问和操作网页上的 HTML 元素(DOM 对象)。
jQuery 同时提供 companion UI(用户界面)和插件。
许多大公司在网站上使用 jQuery:
- Microsoft
- IBM
- Netflix
如需更深入地学习 jQuery,请访问我们的 jQuery 教程。
8.3 Prototype
Prototype 是一种库,提供用于执行常见 web 任务的简单 API。
API 是应用程序编程接口(Application Programming Interface)的缩写。它是包含属性和方法的库,用于操作 HTML DOM(Document Object Model)。
Prototype 通过提供类和继承,实现了对 JavaScript 的增强。
8.4 MooTools
MooTools 也是一个框架,提供了可使常见的 JavaScript 编程更为简单的 API。
MooTools 也含有一些轻量级的效果和动画函数。
8.5 其他框架
下面是其他一些在上面未涉及的框架:
YUI - Yahoo! User Interface Framework,涵盖大量函数的大型库,从简单的 JavaScript 功能到完整的 internet widget。
Ext JS - 可定制的 widget,用于构建富因特网应用程序(rich Internet applications)。
Dojo - 用于 DOM 操作、事件、widget 等的工具包。
script.aculo.us - 开源的 JavaScript 框架,针对可视效果和界面行为。
UIZE - Widget、AJAX、DOM、模板等等。
8.6 CDN - 内容分发网络
您总是希望网页可以尽可能地快。您希望页面的容量尽可能地小,同时您希望浏览器尽可能多地进行缓存。
如果许多不同的网站使用相同的 JavaScript 框架,那么把框架库存放在一个通用的位置供每个网页分享就变得很有意义了。
CDN (Content Delivery Network) 解决了这个问题。CDN 是包含可分享代码库的服务器网络。
Google 为一系列 JavaScript 库提供了免费的 CDN,包括:
- jQuery
- Prototype
- MooTools
- Dojo
- Yahoo! YUI
如需在您的网页中使用 JavaScript 框架库,只需在 <script> 标签中引用该库即可:
引用jQuery库:
1 <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"> 2 </script>
8.7 使用框架
在您决定为网页使用 JavaScript 框架之前,首先对框架进行测试是明智的。
JavaScript 框架很容易进行测试。您无需在计算机上安装它们,同时也没有安装程序。
通常您只需从网页中引用一个库文件。