1. 背景
1.1. Javascript 诞生于1995年
1.2. Javascript 实现组成部分
- ECMAScript 提供语言核心功能
- BOM 提供与浏览器交互的方法和接口
- DOM 提供访问和操作网页内容的方法与接口
2. html中的引用
- 内部引用
- 外部引用
3. Javascript 基本概念
3.1 语法
3.2 关键字和保留字
3.3 变量 (松散型 - 可保存任何类型的数据)
var 定义变量仅为该变量作用域下的内部变量
function test () {
var message = ''
}
alert (message) // 报错
3.4 数据类型
3.4.1 typeof检验数据类型
3.4.2 Boolean() 转换
3.4.3 Number() 转换
3.4.4 parseInt() 转换
3.4.5 Object类型
3.4.6 其他知识点
- 保存浮点数值所需的内容是保存整数的两倍
alert(undefined == null) // true
alert(NaN == NaN) // false
3.5 操作符
3.5.1 一元操作符
- ++ / -- (前置后置)
var a = 1
var b = 2
var c = ++a - 2 // 0
var d = a - 2 // 0
var a = 1
var b = 2
var c = a++ - 2 // -1
var d = a - 2 // 0
-
- (转数字) / - (负数)
var a = ‘a’
var c = +a // NaN
var d = '6'
var e = +d // 6
var f = -e // -6
3.5.2 位操作符
3.5.3 布尔操作符
var found = true
var result = found && a // false , a未定义
var result1 = found ||| a // true
3.5.4 乘性操作符
Infinity * 0 = NaN
Infinity * Infinity = Infinity
0 / 0 = NaN
3.5.5 加性操作符
var a = "5" + "5" // 55
var b = 1
var c = 2
var d = 'hahah' + c + b // hahah21
var e = 'haha' + (c + b) // haha3
var f = 5- null // 5
var g = 5 - true // 4
3.5.6 关系操作符
- 字符串按照字母编码比较
var a = "51" < "5" // true
var b = NaN > 3 // false
var c = NaN <= 3 // false
3.5.7 相等操作符
null == undefined // true
"NaN" == NaN // false
NaN == NaN // false
null == 0 // false
undefined == 0 // false
3.5.8 条件操作符
var a = b ? c : d
3.6 语句
if , do-while , while , for , for-in , break , continue , label , switch
for (var i = 0; i < 10; i++) {
alert(i) // 0-9
}
alert(i) // 10
var num = 0
add:
for (var i = 0; i < 10; i++) {
for (var j = 0; j < 10; j++) {
if (i == 5 && j == 5) {
break add
}
num++
}
}
alert(num) // 55
var num = 0
add:
for (var i = 0; i < 10; i++) {
for (var j = 0; j < 10; j++) {
if (i == 5 && j == 5) {
continue add
}
num++
}
}
alert(num) // 95
3.7 函数
3.7.1 参数 arguments
function howManyArgs () {
alert (arguments.length) // 2
}
howManyArgs ("1", "2")
- 函数没有签名,无法重载
- 未指定返回值的函数返回的是undefined
4. 变量,作用域和内存
4.1 基本类型和引用类型的值
4.1.1 变量
- 基本类型值
var name = 'haha'
name.age = 27
alert(name.age) // undefined
- 引用类型值
var person = new Object()
person.age = 27
alert(person.age) // 27
4.1.2 复制变量
- 基本类型,互不影响
var num1 = 1
var num2 = num1 = 1
- 引用类型,两个变量引用同一个对象,会改变
var obj1 = new Object()
var obj2 = obj1
obj2.name = 'haha'
alert(obj1.name) // haha 被赋值了
4.1.2 传递参数
- 基本类型 - 保存在栈内存
function addTen (num) {
num += 10
return num
}
var count = 20
var res = addTen(count)
alert(count) // 20
alert(res) // 30
- 引用类型 - 保存在堆内存
function setName(obj) {
obj.name = 'hah'
obj = new Object () // 局部对象在函数执行完后摧毁
obj.name = 'hello'
}
var person = new Object()
setName(person)
alert(person.name) // hah
4.1.3 检测类型
- typeof 基本类型检测(见上)
- instanceOf 引用类型检测
alert(colors instanceOf Array/Object/RegExp)
4.2 执行环境和作用域
4.2.1 当代码在一个环境执行时,会创建变量对象的一个作用域链
- 作用域链:保证对执行环境有权访问的所有变量和函数的有序访问
4.2.2没有块级作用域
a. if语句中的变量声明会将变量添加到当前的执行环境
if (true) {
var color = 'blue'
}
alert(color) // true
b. for 语句创建的变量即使在for循环执行环境结束后,也依旧会存在于循环外部的执行环境中
for (var i = 0; i < 10; i++) {
alert(i)
}
alert(i) // 10
4.2.3 局部变量只在函数执行时存在
4.2.4 垃圾收集
- 标记清除 - JavaScript中最常用的垃圾收集方式
- 解除引用 - 一旦数据不再有用,可通过将其值设置为null来释放其引用
5. 引用类型
构造函数: 创建新对象定义
5.1 Object类型
5.1.1 创建实例
- new
var person = new Object ()
person.name = 'hahah'
- 字面量方式
var person = {
name: 'hahah'
}
5.1.2 读属性
alert (person.name)
alert (person['name']) // 可为变量
5.2 Array类型
5.2.1 lenth属性
- 数组的length属性设置,可以从数组的末尾移除项或向数组中添加新项
var colors = ['blue','red']
colors.length = 1
alert(colors[1]) // undefined
5.2.2 检验数组
- Array.isArray()
5.2.3 属性
var person1 = {
toLocaleString: function () {
return 'John'
},
toString: function () {
return 'Lily'
}
}
alert(person1) // Lily
alert(person1.toString) // Lily
alert(person1.toLocaleString) // John
5.2.4 栈方法 (后进后出)
- push() 接受参数,设置尾部,并修改长度
- pop() 从数组末尾移除最后一项,并返回
var colors = ['red', 'blue']
colors.push('black')
alert(colors.length) // 3
var item = colors.pop()
alert(item) // 'black'
5.2.5 队列方法 (后进先出)
- shift() 移除数组中的第一项并返回该项
- unshift() 添加任意值,并返回长度
var colors = ['red', 'blue']
var item = colors.shift()
alert(colors.length) // 1
alert(item) // 'red'
5.2.6 重排序
- reverse() 反转
- sort() 升序排列
先调用toString(), 比较排序
5.2.7 操作方法
- concat() 复制当前数组,有参数的话,将参数添加到结果数组
var size = [1, 2, 3]
var newSize = size.concat([4, 5])
alert(newSize) // 1, 2, 3, 4, 5
- slice() 截取
var size = [1, 2, 3]
var size1 = size.slice(1) // [2, 3] 一个参数,从参数位置到末尾均返回
var size2 = size.slice(1, 2) // [2] 两个参数,从参数1位置到参数2位置之间的数,但不包含位置2
- splice()
删除 - 2个参数,第一个参数为第一项的位置,第2个参数代表要删除的项数
插入 - 3个参数,起始位置,要删除的项数,需要插入的项
替换
var size = [1, 2, 3]
var size1 = size.splice(0, 1) // 1
var size2 = size.splice(1, 0 ,4, 5) // 2, 4, 5, 3
5.2.8 位置方式
- iindexOf() 头查
- lastIndexOf() 尾查
var numbers = [1,2,3,4,5,6]
alert(numbers.indexOf(4)) // 5
5.2.9 迭代方式
- every 所有的为true, 则为true
var nums = [1,3,4,5]
var everyRes = nums.every(function (item, index, array) {
return (item > 2)
})
everyRes() // false
- filter 过滤符合条件的数据
var nums = [1, 2, 3, 4]
var everyRes = nums.filter(function (item, index, array) {
return (item > 2)
})
everyRes() // [3, 4]
- forEach 遍历执行
- map 遍历数据
var nums = [1, 2, 3, 4]
var everyRes = nums.map(function (item, index, array) {
return (item * 2)
})
everyRes() // [2, 4, 6, 8]
5.2.10 归并方式
- reduce 小到大
- reduceRight 大到小
var nums = [1, 2, 3, 4]
var everyRes = nums.reduce(function (prev, cur, index,array) {
return (prev + cur)
})
everyRes() // 10
5.3 Date类型
var start = new Date() // 时间戳
var start1 = Date.now() // 方法前
...function
var stop1 = Date.now() // 方法后
var res = stop1 - start1 // 执行时间
5.4 RegExp类型 - 正则表达式
可见文章https://www.jianshu.com/p/7a63f40e8f41
5.5 Function类型
5.5.1 函数声明
- 函数声明提升
alert(num(10, 10)) // 20
funtion sum (sum1, sum2) {
return sum1 + sum2
}
- 函数表达式必须等到解析器执行到它所在的代码行
alert(num(10, 10)) // 报错
var sum = sum (sum1, sum2) {
return sum1 + sum2
}
5.5.2 内部属性(arguments、this)
- callee属性 - 是一个指针 - 指向拥有arguments对象
function test (num) {
if (num <= 1) {
return 1
} else {
return num * test(num - 1) 等价于 return num * arguments.callee(num - 1)
}
}
好处: 运用callee与函数名无关,均可以调用
var realTest = test()
function test (num) {
return 0
}
realTest (5) // 120
test (0)
- this 引用的是函数执行的环境对象(
当全局调用时,是windows对象
)
window.color = 'red'
var obj = {
color: 'blue'
}
func0tion sayColor () {
alert(this.color)
}
sayColor() // red
obj.sayColor = sayColor
obj.sayColor() // blue
- caller 属性保存着调用当前函数的函数的引用
func0tion outer() {
inner()
}
func0tion inner() {
alert(inner.caller) // outer()
}
outer()
inner.caller == auguments.callee.caller
5.5.3 属性和方法
- prototype 不可枚举,无法for in 发现
- apply / call 设置函数体内this对象的值, apply(在其中运行函数的作用域, 参数数组),call(在其中运行函数的作用域, {...传递参数 或者arguments})
用武之地: 可以扩充函数赖以运行的作用域
window.color = 'red'
var obj = {
color: 'blue'
}
func0tion sayColor () {
alert(this.color)
}
sayColor() // red
sayColor.call(this) // red
sayColor().call(window) // red
sayColor().call(obj) // blue
- bind() 这个方法会创建一个函数的实例,其this值会被绑定到传给bind()函数
window.color = 'red'
var obj = {
color: 'blue'
}
func0tion sayColor () {
alert(this.color)
}
sayColor() // red
sayColor.bind(obj) // blue
5.6 基本包装类型 Boolean , Number , String
5.6.1 使用new调用基本包装类型的构造函数
var value = '25'
var obj = new Number(value)
alert(typeof obj) // object
alert(obj.instanceOf (String)) // true
5.6.2 String类型
5.7 单体内置对象
不依赖宿主环境的对象,程序执行前就已存在
5.7.1 global
不属于任何其他对象的属性和函数,都是全局global对象所有
var global = function () {
return this
} // 全局对象
5.7.2 Math对象
6. 面向对象的程序设计
6.1 理解对象
6.1.1 属性类型 - 为了实现JavaScript引擎用的,不能直接访问
-
数据属性 - 包含数据值
Object,defineProperty()可修改属性的特性
var person = {}
Object.defineProperty(person, "name", {
writable: false, // 不可修改
value: 'hahah'
})
person.name = 'lalala'
person.name // hahah'
一旦改变特性,无法更改
Object.defineProperty(person, "name", {
writable: true, // 报错
})
-
访问器属性 - 不包含数据值,有getter和setter
只有getter不能写入,只有setter无法读取
var book = {
_year: 2021, // _表示只能通过对象方法访问的属性
edition: 1
}
Object.defineProperty(book, "year", {
get: function () {
return this.year
},
set: function (value) {
if (value > 2021) {
this.year = value
editiion += value - 2021
}
}
})
book.year = 2022
alert(book.edition) // 2
6.1.2 定义多个属性 - Object.defineProperties()
var book = {}
Object.defineProperty(book, {
_year: {
writable: false, // 不可修改
value: 'hahah'
},
year: {
get: function () {
return this.year
},
set: function (value) {
if (value > 2021) {
this.year = value
editiion += value - 2021
}
}
}
})
book.year = 2022
alert(book.edition) // 2
6.1.3 读取属性的特性 - Object.getOwnPropertyDescriptor()
var descriptor = Object.getOwnPropertyDescriptor(book, 'year')
alert(descriptor.value) // hahah
6.2 创建对象
6.2.1 工厂模式
为了解决使用同一个接口创建许多对象,会产生大量的代码,抽象创建具体对象的过程如下
function createPerson (name, age, job) {
var o = new Object()
o.name = name
o.age = age
o.job = job
o.sayName = function () {
alert(this.name)
}
return o
}
ver person1 = createPerson ('haha', 17, 'nurse')
ver person2 = createPerson ('hello', 17, 'doctor')
无法解决对象识别问题,引出构造函数模式
6.2.2 构造函数模式
- 构造函数本身也是函数,用来创建对象
function createPerson (name, age, job) {
this.name = name
this.age = age
this.job = job
this.sayName = function () {
alert(this.name)
}
}
ver person1 = new createPerson ('haha', 17, 'nurse')
ver person2 = new createPerson ('hello', 17, 'doctor')
- 构造new操作符步骤
① 创建一个新对象
② 将构造函数的作用域赋值给新对象,this指向新对象
③ 执行构造函数中的代码(为这个对象添加属性和方法
)
④ 返回新对象
alert(person1.constructor == createPerson) // true
- 构造函数胜过工厂函数
创建自定义的构造函数意味着将来可以将它的实例标识为一种特定的类型 - 构造函数与其他函数的区别
调用方式不同(new
) - 构造函数的问题
每个方法都要在每一个实例上重新创建一遍(Funtion实例
) - 不同实例上的同名函数是不相等的
person1.sayName == person2.sayName // false
6.2.3 原型模式
- prototype 属性是一个指针,指向一个对象,好处是:可以让所有对象实例共享它所包含的属性和方法
function person () {}
person.prototype.name = 'ha'
person.prototype.sayName = function () {
alert(this.name)
}
ver person1 = new person ()
ver person2 = new person ()
person1.sayName == person2.sayName // true
person1.sayName = person2.sayName // ha
person.prototype.isPrototypeOf(person1) // true
Object.getPrototypeOf(person1)
- 可以通过对象实例访问保存原型的值,但是无法通过实例修改原型的值
function person () {}
person.prototype.name = 'ha'
ver person1 = new person ()
ver person2 = new person ()
person1.name = 'hello'
person2.name // ha
person1.name // hello
- 使用delete符可以完全删除实例属性
delete person1.name
person1.name = 'ha'
- hasOwnProperty(Object继承) 在实例 - 相反 - hasPropertypeProperty() 在原型
判断属性存在原型中还是实例中
person1.name = 'hello'
person1.hasOwnProperty('name') // true
person2.hasPropertypeProperty('name') // false
- 原型与In操作符
"name" in person1 // true
"name" in person2 // true
- Object.keys 取属性集合
var keys = Object.keys(person, Prototype)
keys['name']
- 原型语法更简洁话(对象字面量)
person.prototype = {
name: 'hello'
}
// 以上方法,constructor指向了Object对象。而非person
var person1 = new person()
person1.instantceOf(Object) // true
person1.instantceOf(person) // true
person1.constructor(Object) // true
person1.constructor(person) // false
可以通过设置constructor改回指向
person.prototype = {
constructor: person,
name: 'hello'
}
但更改后,无法通过for in 读取,[[Enumerable]] = true
- 动态性
重写原型对象切断了现有原型与任何之前已经存在的对象实例之间的联系,他们引用的仍是最初的原型
function person () {}
var person1 = new person()
person.prototype = { // 新的person.newProperty
constructor: person,
name: 'hello'
}
person1.name // error 读取的是person的property
- 原生对象的原型
① 通过原生对象的原型, 不仅可以取得所有默认方法的调用,也可以定义新方法
alert(typeof Array.prototype.sort) // true
String.propotype.startWith = function (text) {
return this.indexOf(text) == 0
}
var msg = 'Hello'
alert(msg.startWith('Hello')) // true
② 构造函数可以与原型模式组合
③ 构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性
6.2.4 稳妥的构造函数
指的是没有公共属性,而且其方法也不引用this对象
function createPerson (name, age, job) {
var o = new Object()
o.sayName = function () {
alert(name)
}
}
ver person1 = new createPerson ('haha', 17, 'nurse')
person1.sayName // haha , 唯一输出name的调用
6.3 继承
6.3.1 原型链
基本思想:利用原型让一个引用类型继承另一个引用类型的属性和方法
function SuperType () {
this.property = true
}
SuperType.prototype.getSuperValue = function () {
return this.property
}
function SubType () {
this.subProperty = false
}
SubType.prototype = new SuperType()
SubType.prototype.getSubValue = function () {
return this.subProperty
}
var instance = new SubType()
alert(instance.getSuperValue()) // true
SubType继承了SuperType,原来存在于SuperType的实例中的所有属性和方法,现在也存在于SubType的prototype中
- instance.constructor 已经变成了SuperType
- 所有的函数默认都是Object实例
instance -> SubType -> SuperType -> Object - 通过原型链继承时,不能使用字面量创建原型方法,否则会重写原型链
- 原型链继承有个问题:通过原型链继承时,原先的实例属性会变成现在的原型属性(引用类型值)
6.3.2 借用构造函数
基本思想: 在子类型构造函数的内部调用超类型构造函数
function SuperType () {
this.colors = ['red','blue','black']
}
function SubType () {
SuperType.call(this)
}
var instantce1 = new SubType()
instantce1.colors.push('green')
var instantce2 = new SubType()
instantce2.colors // ['red','blue','black']
instantce1.colors // ['red','blue','black','green']
- 可传递参数
function SuperType (color) {
this.colors = color
}
function SubType () {
SuperType.call(this, 'black')
this.age = 17
}
var instantce1 = new SubType()
instantce1.colors // black
instantce1.age // 17
- 问题:方法都在构造函数中,无法复用
6.3.3 组合继承(原型链+借用构造函数)
function SuperType (name) {
this.name = name
this.colors = ['blue', 'red']
}
SuperType.prototype.sayName = function () {
return this.name
}
function SubType () {
this.subProperty = false
}
SubType.prototype = new SuperType()
SubType.prototype.getSubValue = function () {
return this.subProperty
}
var instance = new SubType()
alert(instance.getSuperValue()) // true
6.3.4 原型式继承:一个对象作为另一个对象的基础
function object (o) {
function F () {}
F.prototype = o
return new F()
}
var person = {
friends: ["Lily']
}
var person1 = object(person)
var person2 = object(person)
person1.friends.push('Bob')
person2.friends.push('Amy')
alert(person.friends) // ["Lily","Bob","Amy"]
6.3.5 寄生式函数
基本思想:创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像是真地是它做了所有工作一样返回对象
function createAnother(original){
var clone = object(original) // 通过调用函数来创建一个新对象
clone.sayHi = function () { // 强化对象
alert('hi')
}
return clone // 返回对象
}
var person = {
name: 'hi',
friends: ['lily', 'lucy']
}
var anotherPerson = new createAnother(person )
anotherPerson.sayHi() // hi
- 使用寄生式函数继承时,会由于不能做到函数复用而降低效率,这一点与构造函数模式类似
6.3.6 寄生组合式继承,js最常用的继承模式
基本思想:通过借用构造函数来继承属性,通过原型链的混成形式来继承方法
function inheritPrototype (SubType, SuperType) {
var prototype = object(SuperType.prototype) // 创建对象
prototype.constructor = SubType // 增加对象
SubType.constructor = prototype // 指定对象
}
function SuperType (name) {
this.name = name
this.colors = ['blue', 'red']
}
SuperType.prototype.sayName = function () {
return this.name
}
function SubType (name, age) {
SuperType.call(this, name) // 第二次调用SuperType
this.age = age
}
inheritPrototype(SubType,SuperType)
SubType.prototype.sayAge = function () {
alert(this.age)
}
7. 函数表达式
function关键字后面没有跟标识符,称为匿名函数
7.1 递归
一个函数通过名字调用自身
function test (num) {
if (num <= 1) {
return 1
} else {
return num * test(num - 1) 等价于 return num * arguments.callee(num - 1)
}
}
arguments.callee严格模式无法访问,可以使用命名函数来达成相同效果
7.2 闭包
- 指有权访问另一个函数作用域中的变量的函数
7.2.1 闭包与变量
闭包所保存的是整个变量对象,而不是某个特殊的变量
function createFunction () {
var result = new Array()
for (var i = 0; i < 10; i++) {
result[i] = function () {
return i
}
}
return result
}
实际上,每个函数都返回10,因为每个函数的作用域链都保存着
function createFunction函数的活动对象,所以他们引用的是一个变量,当函数返回后,i的变量均为10,可通过创建另一个匿名函数强制让闭包的行为符合预期。
function createFunction () {
var result = new Array()
for (var i = 0; i < 10; i++) {
result[i] = function (num) {
return function () {
return num
}
}(i)
}
return result
}
函数是按值传参,i赋值给num,匿名函数会存在num的副本,因为会返回不同i值