《JavaScript高级程序设计》笔记 —— ECMAScript基础
1. Js中调用函数时使用不带圆括号的函数名与带括号的函数名的区别
function sum (num1, num2) {
return num1 + num2
}
alert(sum(10,10)) // 20
let anotherSum = sum
alert(anotherSum(10,10)) // 20
sum = null
alert(anotherSum(10,10)); // 20
以上代码先定义了一个名为 sum() 的函数, 用于求两个值的和
又声明了变量 anotherSum, 将其设置为与 sum 相等 (将sum的值赋给anotherSum)
注意: 使用不带圆括号的函数名是访问函数指针, 而非调用函数
此时 anotherSum 和 sum 都指向了同一个函数, 因此 anotherSum() 也可以被调用返回结果. 即使将 sum 设置为 null, 让它与函数 "断绝关系", 但仍然可以正常调用 anotherSum()
2. Js中实现升序排序
function compare (val1, val2) {
return val2 - val1
}
3. Js中的this
函数内部的一个特殊对象是 this, 与Java中的 this 大致类似即, this 引用的使用函数据以执行的环境对象 —— 或者也可以说是 this 值 (当前网页在全局作用域中调用函数时, this对象引用的就是 window)
window.color = 'red'
let o = {color: 'blue'}
function sayColor () {
alert(this.color)
}
sayColor(); // 'red'
o.sayColor = sayColor
o.sayColor() // 'blue'
函数 sayColor() 是在全局作用域中定义的, 它引用了 this 对象.
由于在调用函数之前, this 的值并不确定, 因此 this 可能会在代码执行过程用引用不同的对象
当在全局作用域中调用 sayColor() 时, this引用的是全局对象 window, 即对 this.color 求值会转换成对 window.color求值, 于是返回了 'red'
当把这个函数赋给对象 o 并调用 o.sayColor() 时, this引用的是对象 o, 即对 this.color求值会转换成对 o.color 求值, this 引用的是对象o, 因此对this.color求值会转换成对 o.color求值, 结果就返回了 'blue'
注意: 函数的名字仅仅是一个包含指针的变量而已, 因此, 即使是在不同的环境中执行, 全局的 sayColor() 函数与 o.sayColor() 指向的仍然是同一个函数
4. Js Function的apply()方法与call()方法
apply()方法
每个函数都包含两个非继承而来的方法: apply() 和 call().
这两个方法的用途都是在特定的作用域中调用函数, 实际上等于设置函数体内 this对象的值
apply() 方法接收两个参数: 一个是在其中运行函数的作用域, 另一个是参数数组. 其中, 第二个参数可以是 Array 的示例, 也可以是 arguments 对象, 例如:
function sum (num1, num2) {
return num1 + num2
}
function callSum1 (num1, num2) {
return sum.apply(this, arguments) // 传入 arguments 对象
}
function callSum2 (num1, num2) {
return sum.apply(this, [num1, num2]) // 传入数组
}
alert (callSum1(10,10)) // 20
alert (callSum2(10,10)) // 20
上面的例子中
callSum1() 在执行 sum() 函数时传入了 this 作为 this 值(因为是在全局作用域中调用的, 所以传入的就是 window对象) 和 arguments 对象
callSum2() 同样也调用了 sum() 函数, 但它传入的是 this 和一个参数数组, 这两个函数都会正常执行并返回正确的结果
call() 方法
call() 方法与 apply() 方法的作用相同, 它们的区别在于接收参数的方式不同.
call() 方法第一个参数是 this 值没有变化, 变化是其余的参数都是直接传递给函数, 即使用 call() 方法时, 传递给函数的参数都必须逐个列举出来
function sum (num1, num2) {
return num1 + num2
}
function callSum (num1, num2) {
return sum.call(this, num1, num2)
}
alert(callSum(10,10)) // 20
在使用 call() 方法的情况下, callSum() 必须明确地传入每一个参数, 结果与使用 apply() 没有什么不同
两个方法的引申
这两个方法真正强大的地方是能够扩充函数赖以运行的作用域
window.color = 'red'
let = {color: 'blue'}
function sayColor () {
alert(this.color)
}
sayColor() // red
sayColor.call(this) // red
sayColor.call(window) // red
sayColor.call(o) // blue
使用 call() 或 apply() 来扩大作用域的最大好处, 就是对象不需要与方法有任何的耦合关系. 在前年的例子中, 我们先将 sayColor() 函数放到了对象 o 中, 然后再通过 o 来调用它, 但是在这个重写的例子中, 就不需要先前那个多余的步骤了
5. Number类型的几个方法
toString() 方法
可以为 toString() 方法传递一个表示基数的参数, 告诉他返回几进制数值的字符串形式
let num = 10
alert (num.toString()) // '10'
alert (num.toString(2)) // '1010'
toFixed()方法
toFixed() 方法会按照指定的小数位返回数值的字符串表示
let num = 10
alert(num.toFixed(2)) // '10.00'
6. String类型的几个方法
charAt() 和 charCodeAt() 方法
charAt() 和 charCodeAt() 方法都接收一个参数, 即基于0的字符位置, 其中 charAt() 方法以单字符串形式返回给定位置的那个字符, 而charCodeAt() 返回字符字符编码
let stringValue = 'hello world'
alert(stringValue.chatAt(1)) // 'e'
alert(stringValue.chatCodeAt(1)) // 输出'101', 即小写字母 'e' 的字符编码
字符串操作方法
concat() 用于将一或多个字符串拼接起来
slice(), substr(), substring(), 三个方法 一般使用 slice() 作为字符串截取的方法
字符串位置方法
indexOf() 在一个字符串中搜索给定的字符串, 如果没有返回 -1, 如果有返回字符串出现第一次出现的位置
lastIndexOf() 在一个字符串中搜索给定的字符串, 如果没有返回 -1, 如果有返回字符串出现最后一次出现的位置
trim() 用于删除前置以及后置的所有空格, 该方法会创建一个字符串的副本, 并返回处理后的结果, 不会修改原来的字符串
字符大小写转换方法
toLowerCase() 转换为全小写
toUpperCase() 转换为全大写
字符串的正则匹配方法
match(), search() 两个方法多用于正则表达式
replace() 方法多用于替换
split() 方法用于基于指定分隔符将一个字符串分隔成多个字符串, 并将结果放到一个数组中, 并返回它
7. 单体内置对象Global
Global对象
Global对象, 是不存在的, 不属于任何其他对象的属性和方法, 最终都是他的属性和方法
eval()方法
eval() 方法是一个完整的 ECMAScript解析器, 它可以执行任何的ECMAScript字符串, 它很危险, 尽量不要使用它
eval() 方法中创建的任何变量和函数都不会提升, 因为它们被包含在一个字符串中, 只有在 eval() 执行的时候才会执行
window对象
Web浏览器将 Global对象作为 window 对象的一部分加以实现, 故, 在全局作用域中声明的所有变量和函数, 都成为了 window 对象的属性
var color = 'red'
function sayColor () {
alert(window.color)
}
window.sayColor() // 'red'
另一种获取 Global对象的方法
let global = function () {
return this;
} ()
8. Js的设计模式
工厂模式
function createPerson (name, age, job) {
let o = new Object()
o.name = name
o.age = age
o.job = job
o.sayName = function () {
alert(this.name)
}
return o
}
let person1 = createPerson('XiaoMing', 29, 'Software Engineer')
let person2 = createPerson('XiaoHong', 18, 'Doctor')
工厂模式解决了创建多个相似对象的问题, 但没有解决对象识别的问题(即如何知道一个对象的类型)
构造函数模式
function Person(name, age, job) {
this.name = name
this.age = age
this.job = job
this.sayName = function() {
alert(this.name)
}
}
let person1 = new Person('XiaoMing', 29, 'Software Engineer')
let person2 = new Person('XiaoHong', 18, 'Doctor')
此处对比工厂模式, Person() 函数取代了 createPerson() 函数, 并且存在着以下区别
- 没有显式地创建独享
- 直接将属性和方法赋给了 this 对象
- 没有 return 语句
若要创建一个新的 Person 实例, 必须使用 new 操作符, 可以使用 instanceof 操作符来验证某个对象是否是 某类的示例
构造函数模式的问题
每个方法都要在每个实例上重新创建一遍, 上面的例子中, person1 和 person2 都有一个名为 sayName()的方法, 但其实两个方法不是同一个 Function实例, 注意 ECMAScript 中的所有函数也是对象, 因此每定义一个函数, 也就实例化了一个对象, 所以
alert(person1.sayName == person2.sayName) // false
原型模式
创建出来的每个函数都有 prototype(原型)属性, 该属性是一个指针, 指向一个对象, 该对象也就是它的原型
使用原型对象的好处是不必在构造函数中定义对象实例的信息, 而是可以将这些信息直接添加到原型对象中
function Person() {
}
Person.prototype.name = 'XiaoMing'
Person.prototype.age = 29
Person.prototype.job = 'Software Engineer'
Person.prototype.sayName = function () {
alert(this.name)
}
let person1 = new Person()
person1.sayName() // 'XiaoMing'
let person2 = new Person()
person1.sayName() // 'XiaoMing'
alert(person1.sayName == person2.sayName) // true
更简单的原型语法
在前面的例子中每添加一个属性和方法就要写一遍 Person.prototype, 为了减少不必要的输入也可以这么写
function Person() {
}
Person.prototype = {
name: 'Xiaoming',
age: 29,
job: 'Software Engineer',
sayName: function () {
alert(this.sayName)
}
}
我们可以随时为原型添加属性和方法, 并且修改能够立即在所有对象实例中反映出来, 不过如果是重写整个原型, 就不行了
let friend = new Person()
Person.prototype.sayHi = function () {
alert('hi')
}
friend.sayHi() // 没有问题
function Person() {
}
let friend = new Person()
Person.prototype = {
constructior: Person,
name: 'Xiaoming',
age: 29,
job: 'Software Engineer',
sayName: function () {
alert(this.name)
}
}
friend.sayName() // error
组合使用构造函数模式和原型模式
构造函数模式用于定义实例属性, 原型模式用于定义方法和共享的属性
function Person(name, age, job) {
this.name = name
this.age = age
this.job = job
this.friends = ['Shelby', 'Court']
}
Person.prototype = {
constructor: Person
sayName: function () {
alert(this.name)
}
}
例子中, 实例属性在构造函数中定义, 共享的属性 constructor 和方法 sayName() 定义在原型中
组合使用构造函数模式和原型模式, 是目前在ECMAScript中使用最广泛, 认同度最高的一种定义类型的方法,
动态原型模式
将所有信息都封装在构造函数中, 可以通过检查某个应该存在的方法是否有效, 来决定是否需要初始化原型
function Person(name, age, job) {
// 属性
this.name = name
this.age = age
this.job = job
// 方法
if (typeof this.sayName != 'function') {
Person.prototype.sayName = function() {
alert(this.name)
}
}
}
let friend = new Person('XiaoMing', 18, 'Software Engineer')
friend.sayName()
这里只会在 sayName() 方法不存在的情况下, 才会将它添加到原型中, 不过不能使用对象字面量重写原型
寄生构造函数模式
稳妥构造函数模式
9. Js的原型链
原型链是作为实现继承的主要方法, 基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法即让原型对象等于另一个类型的实例, 如此层层递进, 即为原型链, 原型链可以用来实现继承
10. 闭包
闭包指有权访问另一个函数作用于中的变量的函数, 闭包会携带它的函数的作用域, 因此会比其他函数占用更多的内存, 所以要谨慎使用闭包
详见: