javascript 高级
js 基础补充
typeof
可以查看一个值的类型
- typeof 简单类型:返回结果简单类型
- typeof null: 并不是 null 而是 object
- typeof 复杂类型: 返回的都是 object
- typeof 函数: 返回的 function (在 js 中函数是一等公民)
console.log(typeof 11) // 输出number
// 另一种写法console.log(typeof(11));
console.log(typeof null) // object
function fn() {} //fn 是一个函数
console.log(typeof fn) // function
比较运算符
// == : 只会比较值 不会比较类型
// === : 会比较类型,也比较值(推荐)
// 优先级从上往下递减
// == 只会比较值 如果类型不同,自动转成相同的类型进行比较
// 1. NaN不等于任何值
// 2. null 不等于任何值 除了 本身和undefined
// 3. undefined 不等于任何值 除了本身和null
// 4. 如果两边有数字或者布尔类型,都转成数字进行比较
// 5. 如果两边有字符串,两边都变成字符串比较
// 6. 如果两个都是复杂类型, 直接比地址
// '' 转换成数值是 0
// [] 转换成数值是 0
// true 1 false 0
console.log([] == 0) // [] == 0 true
console.log('1' == true) // true
console.log('2' == true) // false
console.log('' == []) // true
console.log([] == []) // false
console.log({} == {}) //false
console.log({} == []) // false
js 高级
深拷贝和浅拷贝
浅拷贝:将对象中的各个属性依次进行复制,浅拷贝只复制了一层对象的属性。如果对象属性中还有对象,那么赋值的仅仅是地址。还是会相互影响。
// 浅拷贝
function cloneObj(obj) {
if (typeof obj !== 'object') {
return
}
var temp = {}
for (var k in obj) {
temp[k] = obj[k]
}
return temp
}
深拷贝:将对象中的各个属性一次进行复制,深拷贝会递归赋值所有层对象的属性。如果对象属性中还有对象,会继续拷贝,这样拷贝出来的对象完全独立。
// 深拷贝
function cloneObj(obj) {
//如果不是对象,不拷贝
if (typeof obj !== 'object') {
return
}
var temp = {}
for (var k in obj) {
//如果对象的属性是复杂类型,递归复制
//如果对象的属性是简单类型,直接复制
temp[k] = typeof obj[k] === 'object' ? cloneObj(obj[k]) : obj[k]
}
return temp
}
原型
在讲原型之前先说一下,为什么会用到原型?
这就要提到另一个东西,构造函数的缺点?
function Person(name, age) {
this.name = name
this.age = age
this.sayName = function() {
console.log(this.name)
}
}
var p1 = new Person('Jack', 18)
p1.sayName() // => Jack
var p2 = new Person('Mike', 23)
p2.sayName() // => Mike
每次我们通过构造函数实例化一个对象的时候,对象的属性和方法都不一样,但是按理来说,同一个构造函数实例化的对象执行相同的方法应该是一样的,但是…
console.log(p1.sayName === p2.sayName) // ==> false
就因为这个方法是不一样的,就会导致一种情况————内存浪费
改进 1
var sayHello = function() {
console.log('hello ' + this.name)
}
function Person(name, age) {
this.name = name
this.age = age
this.type = 'human'
this.sayHello = sayHello
}
var p1 = new Person('lpz', 18)
var p2 = new Person('Jack', 16)
console.log(p1.sayHello === p2.sayHello) // ==> true
这样做之后我们达到了目的,但是缺点:会暴漏很多的函数,容易造成全局变量污染
改进 2
var obj ={
sayHello: function () {
console.log('hello ' + this.name)
}
}
......
function Person(name,age){
....
this.sayHello = obj.sayHello;
}
这样看貌似我们已经解决了暴露很多函数的问题,但是同样不完美,因为我们还是要定义一个对象变量来存储
因此为了避免这个缺点,接下来我们就来说一下原型
我们创建的每个函数都有一个 prototype(原型)属性,这个属性是一个指针,指向一个对象(原型对象),而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法
无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个 prototype 属性,这个属性指向函数的原型对象,在默认情况下,所有原型对象都会自动获得一个 constructor(构造函数)属性,这个属性包含一个指向 prototype 属性所在函数的指针。当调用构造函数创建一个新实例后,该实例的内部将包含一个指针proto(内部属性),指向构造函数的原型对象。参考下图理解(牢记这两张图,这是基础,后面的变种都是基于这的)
上述引自《JavaScript 高级程序设计》一书
注:构造函数中的一些属性,当实例化后只会在实例对象上有,在图上构造函数那节点上是没有的
既然在原型上定义的方法和属性是可以共享的,那就说我们可以把之前改进 2 放在原型上,因为原型就是一个对象
function Person (name, age) {
this.name = name
this.age = age
}
console.log(Person.prototype)
Person.prototype.type = 'human'
Person.prototype.sayName = function () {
console.log(this.name)
}
var p1 = new Person(...)
var p2 = new Person(...)
console.log(p1.sayName === p2.sayName) // => true
这样实例出来的 p1 和 p2 共用了原型上的 sayName 方法
// 通过构造函数.prototype可以获取
// 通过实例.__proto__可以获取(隐式原型)
// 它们指向了同一个对象构造函数.prototype === 实例.__proto__
// 注意:__proto__是浏览器的一个隐藏(私有)属性,早期的IE浏览器不支持,不要去修改它,如果要修改原型中的内容,使用构造函数.prototype去修改
原型的优点:
- 可以让所有对象实例共享它所包含的属性和方法
- 不必在构造函数中定义对象实例的信息,而是将这些信息直接添加到原型对象中(如果实例的信息是不同的,则放在构造函数里,原型对象中放的是相同的信息)
// 构造函数 原型 实例
// 1. 构造函数和原型的关系 每一个构造函数都会有一个对应的原型, 构造函数的prototype属性指向了原型对象, 原型对象的constructor属性指向了构造函数。
// 2. 构造函数 和实例的关系 实例 是由构造函数new出来的, 并且实例的属性是在构造函数中增加的
// 3. 原型对象与实例的关系 原型对象中所有的成员 都可以 被实例访问。 实例.__proto__ = Person.prototype
原型链
// 任何一个对象都会有原型,,,原型又是个对象, 原型对象还会有原型,一环扣一环,原型之间就组成了原型链。
// 一个对象,除了可以访问自己的成员, 还可以访问原型的成员,可以访问整条原型链上的成员。
// p -----> p.__proto___(Person.prototype) ----> p.__proto__.__proto__
// 属性搜索原则:
// 如果我们访问某个对象的属性
// 1. 直接在对象中查找这个属性, 如果有,就直接得到结果
// 2. 如果对象自己没有这个属性, 会查找原型中是否有这个属性
// 3. 如果还没有,会一直沿着这条原型链一直查找,直到Object.prototype
// 4. 如果还没有,就返回undefined
// 设置属性来说
// 如果对象有这个属性,就修改值
// 如果对象没有这个属性,直接增加值。
Object.prototype 中的成员
// hasOwnProperty 用于判断 某个属性是否是对象自己的,而不是继承来的
// in操作符会在通过对象能够访问给定属性时返回true,无论该属性存在于实例还是原型中。
// 同时使用hasOwnProperty()方法和in操作符,就可以确定该属性到底存在于对象中,还是存在于原型中
function hasPrototypeProperty(object, name) {
return !object.hasOwnProperty(name) && name in object
}
isPrototypeOf()方法
// A.isPrototypeOf(B) : 判断A是否是B的原型
// 判断A对象是否在B对象的原型链上。
// 返回值:true,在原型链上 false:不在原型链上。
function Person(name) {
this.name = name
}
var p = new Person()
console.log(Person.isPrototypeOf(p))
console.log(Person.prototype.isPrototypeOf(p))
console.log(Object.prototype.isPrototypeOf(p))
propertyIsEnumerable()方法
//1. 判断这个属性能不能被遍历(必须是自己的属性) 属性 是否 可以枚举 (遍历)
obj.propertyIsEnumerable(prop)
var result = Object.prototype.propertyIsEnumerable('constructor') // false
console.log(result)
// 在控制台中显示的颜色是浅色的就是不可枚举的
instanceof
// typeof: 仅仅能够判断简单的数据类型,对于复杂的数据类型,返回的全是object
// 严格的判断一个对象的类型,应该用instranceof
// instance:实例
// A instanceof B: 判断A是否是B的实例
// 验证p 是否是 Person的实例
// instanceof规则:
// 判断 Person.prototype 是否在 p的原型链上。 如果在p的原型链上,返回true
function Person() {}
var p = new Person()
console.log(p instanceof Person) // true
Person.prototype = {} // 老王
var p1 = new Person()
console.log(p instanceof Person) // false
console.log(p1 instanceof Person) // true
沙箱模式
经验之谈: 在沙箱的前面加一个分号
// 沙箱: 作用,防止全局变量污染
// 缺点:外界无法访问到沙箱内的任何内容
// 因此会让沙箱对外暴漏一些全局的变量
;(function() {
// 沙箱内部的变量都是局部变量
})()
/*
一般我们会如下这么写
window参数的作用:
把window从全局变量变成了局部变量 ,查找速度快
将来开发的时候,全局变量是无法被代码压缩 但是局部变量可以被压缩
*/
;(function(window) {
window.a = a // 把 a 暴露给全局
})(window)
/*
有时候还会给一个undefined的形参,但是不传实参
undefined的参数作用: 保证undefined真的是undefined
ie678中 undefined可以是一个变量
*/
;(function(window, undefined) {})(window)
分号问题
// js中必须加分号的五个地方
// () [] / + - 前面必须分号结束
// 因此在沙箱前的小括号前加 ";"
继承
在 js 中,继承 一个对象自己没有的属性和方法,另一个对象有,拿过来使用就实现了继承
混入式继承
- 把一个对象中所有的属性添加给自己
var ff = {
name: '灰灰',
// 继承
extend: function(obj) {
for (var k in obj) {
if (obj.hasOwnProperty(k)) {
this[k] = obj[k]
}
}
}
}
var cc = {
house: '新疆的海景房',
car: '凤凰牌自行车'
}
ff.extend(cc)
原型链继承
- 原型链继承
function Person(name, age) {
this.name = name
this.age = age
}
var lw = {
house: '独栋大别墅',
car: '玛莎拉蒂',
constructor: Person // 通常还要加上这句,让constructor重新指向Person
}
// 原型链继承 构造函数创建的对象天生会继承 Person.prototype
// 方式1: 把所有的东西都加给原型
Person.prototype.house = '新疆的海景房'
Person.prototype.money = 1000000
// 方式2: 替换原型对象
// 把构造函数的原型替换成了一个新的对象
Person.prototype = lw
var p1 = new Person('zs', 18)
var p2 = new Person('ls', 20)
console.log(p1, p2)
console.log(p1.car, p1.house)
- 原型继承+混入式继承–>给构造函数原型增加 extend 方法
function Person(name, age) {
this.name = name
this.age = age
}
var lw = {
house: '茅草屋',
car: '摩拜'
}
Person.prototype.extend = function(obj) {
for (var k in obj) {
if (obj.hasOwnProperty(k)) {
this[k] = obj[k]
}
}
}
Person.prototype.extend(lw)
Person.prototype.extend({
sayHi: function() {
console.log('哈哈')
}
})
// 需求: 让创建出来的p1 和 p2 继承 lw
var p1 = new Person('zs', 18)
var p2 = new Person('ls', 20)
经典继承
// Object.create() 快速让一个对象继承另一个对象
var obj = {
name: 'zs',
age: 18
}
// 作用:返回一个新对象, 这个新对象直接会继承自 obj
// newObj.__proto__ === obj
var newObj = Object.create(obj)
console.log(newObj.name)
console.log(newObj.age)
// Object.create(obj)原理
function object(o) {
function F() {}
F.prototype = o
return new F()
}
继承总结
// 这里只讲了三种继承,更多的继承可以参考 《JavaScript高级程序设计》这本书
// 1. 混入式继承 一个对象想要继承另一个对象 (实际上只是复制了另一个对象的属性和方法)
// 给对象增加一个extend方法
// 2. 原型链继承
// 想要给实例增加属性方法
// 2.1 直接给原型增加属性和方法 缺点: 如果要加的属性比较多,就会比较繁琐
// 2.2 直接把原型替换成一个对象, 一般要给新对象提供一个constructor
// 2.3 给原型增加一个extend方法, 原型可以继承很多的对象。
定义函数的三种方式
1、函数声明
// 声明函数
function fn() {}
// 调用函数
fn()
2、函数表达式
// 表达式声明
var fn1 = function() {}
// 函数调用
fn1()
3、构造函数的方式
- 可以实现一个简易效果—> 在文本域中输入代码,效果实时展现出来
// 参数:arg1,arg2,arg3......body
// 前面的都是函数参数,最后一个是函数体,即函数执行的代码
var fn2 = new Function()
// 例如
var fn = new Function('a', 'b', 'alert(a + b)')
fn(1, 2)
var str = 'var a = 1; var b = 2; console.log(a + b)'
var fn = new Function(str)
fn()
eval
- 可以让一段 js 代码执行 new Function()
var str = 'var num = 1; var num2 = 2; console.log(num + num2)'
// var fn = new Function(str)
// fn()
eval(str) // 相当于一下子执行了上面两行的代码
try…catch…finally 异常处理
// 和别的语言的没差
try {
// 运行的代码 可能会报错的代码
} catch (exception) {
// 抛出异常
// 如果报错了,catch的代码就会执行, exception里面会有错误的信息
} finally {
// 最后执行的代码 finally中的代码无论报不报错,都会执行 一些无论如何都要执行的代码。
}
// 捕获到异常后,不throw,那么这段代码还是会执行的
console.log(111)
函数的四种调用模式
目的:确定函数内部的 this 指向
- 函数调用模式 函数名() this 指向 window
- 方法调用模式 对象名.方法名() this 永远指向 当前对象
- 构造函数调用模式 new 函数名() this 指向新创建的对象
- 上下文调用模式
函数调用模式
// 函数内部的this,永远指向window
// 函数名() 函数调用模式 this指向window
var num = 11
function fn() {
var num = 5
console.log(this.num)
}
fn() // 11
方法调用模式
// 函数内部的this是变化的, 跟这个函数如何调用有关
// 对象名.方法名() 方法调用模式 this指向当前对象
var age = 20
var obj = {
name: 'zs',
age: 18,
sayHi: function() {
console.log(this.age)
}
}
obj.sayHi() // this指向的就是调用的那个对象 obj 18
构造函数调用模式
function Person() {
console.log(this) // Person
}
// 构造函数调用: 和new 一起使用 ---> this指向的就是这个新的对象 即 p (new的四件事)
var p = new Person()
补充 arguments 对象
// 任何一个函数内部,都自带一个对象 arguments
// arguments 存储了传递过来的所有的实参
// arguments 是一个伪数组, 里面存放了所有的实参
// arguments用在不确定参数个数
三种模式练习
// 案例一
var age = 38
var obj = {
age: 18,
getAge: function() {
console.log(this.age) //18
function foo() {
console.log(this.age) //38
}
// 函数调用模式 与函数在哪里声明是没有关系的
foo()
}
}
// 方法调用模式 this 指向
obj.getAge()
// 也是方法调用模式
obj['getAge']()
// 案例二
var length = 10
function fn() {
console.log(this.length)
}
var arr = [fn, '222', '333']
// 函数调用模式
fn() // 10
// 方法调用模式 obj.sayHi() obj['sayHi']() arr[0]() 也是方法调用使用
arr[0]() // 3 arr.length 这里的this指向的是arr对象
// 案例三
var length = 10
function fn() {
console.log(this.length)
}
var obj = {
length: 5,
fn: function() {
console.log(this.length)
}
}
var arr = [fn, obj.fn, 100, 10]
fn() // 10
obj.fn() // 5
arr[0]() // 4
arr[1]() // 4
// 案例四
function fn() {
console.log(this)
}
fn() // window
var arr = [fn]
arr[0]() // array
var obj = {
fn: fn
}
obj.fn() // object
// 案例五
var length = 10
function fn() {
console.log(this.length)
}
var obj = {
length: 5,
method: function(fn) {
console.log(this.length) // 5
fn() // 10
arguments[0]() // 3
}
}
obj.method(fn, '111', 'ssss')
上下文调用模式–方法借调模式
1、call()方法
call 方法可以调用一个函数,并且可以指定这个函数的 this 指向
// 任意一个函数, 可以通过()进行调用 所有的函数都有一个方法 call
// 也表示调用函数
// call的第一个参数可以用来指定this的指向 如果第一个参数没有,就是window
function fn() {
console.log('我执行了没有')
console.log(this)
}
fn()
var arr = [1, 2, 3]
var obj = {
name: 'zs'
}
fn.call(obj)
// call方法和()调用 最大的区别:
// call比直接调用多一个参数, 多第一个参数,第一个参数用来指定this
function fn(a, b) {}
fn(1, 2)
fn.call([], 1, 2) //这里this 指向的就是数组
- 补充——伪数组和数组
call 因为能够改变 this 的指向, call 可以借用别人的方法
// 伪数组的好处: 实质是一个对象 可以和数组一样遍历 随机给伪数组的原型添加方法
// 伪数组的缺点: 不能使用数组的方法
// 任何一个对象都可以是伪数组 有下标 和长度
var obj = {
0: 'zs',
length: 1
}
// 通过call让对象(伪数组)借调数组的方法
;[].push.call(obj, '赵云', '张飞')
Array.prototype.pop.call(obj)
console.log(obj)
// 一个伪数组怎么变成真数组?
var lis = document.querySelectorAll('li')
// 给一个数组 得到一个新数组
var arr = [].slice.call(lis)
console.log(arr)
2、apply()
// call apply : 都可以让函数执行, 第一个参数都是 this的指向
// 区别: call(this, 参数1, 参数2, 参数3)
// apply(this, [参数1, 参数2, 参数3])
// apply方法要求所有的参数都包含一个数组中
// 求数组中的最大值
var arr = [100, 300, 200, 10]
var result = Math.max.apply(null, arr)
console.log(result)
3、bind()
bind: 给返回一个新函数,新函数内部的 this 指向第一个参数
// 接收一个参数, 参数也表示this指向
// bind : 作用: 根据一个函数返回一个新的函数,并且新函数的this永远指向bind的第一个参数
// bind和call最大的区别 bind不会让函数执行 call会让函数执行
function fn() {
console.log('我执行了吗')
console.log(this) // Array
}
var newFn = fn.bind([]) // 这里绑定了Array,所以后面就是是用call指定了this为{}也是不起作用的
newFn.call({})
// 同样是指向Array的
var newFn = fn.bind([1, 2, 3])
newFn()
var arr = [newFn]
arr[0]()
// 可以修改定时器中的this指向,就不用像之前那样 var that = this
var obj = {
name: '喳喳燕',
sayHi: function() {
setInterval(
function() {
console.log('大家好,我是', this.name)
}.bind(this),
1000
)
}
}
obj.sayHi()
调用模式总结
函数的四种调用模式
- 函数调用模式 函数名() this 指向 window
- 方法调用模式 对象.方法名() this 指向当前对象
- 构造函数模式 new 函数名() this 指向当前实例
- 上下文调用模式 方法借调模式 this 永远指向第一个参数
特殊:
定时器的 this 是 window
事件对象中的 this 指向事件源
完整版原型链
函数也是对象
函数是由 new Function 创建出来的,因此函数也是一个对象,
所有的函数都是new Function的实例
// 任何对象 instanceof Object true
// 任意的函数 instance Function true
// Function ---> Function.prototype ---> Object.prototype ---> null
console.log(Function instanceof Function) // true
console.log(Function instanceof Object) //true
// // Object ------ > Function.prototype ----> Object.prototype ---> null
console.log(Object instanceof Function) // true
console.log(Object instanceof Object) // true
function fn() {}
console.log(fn instanceof Function) // true fn = new Function()
console.log(fn instanceof Object) // true 函数也是一个对象
总结:
// 1. 所有的函数都是有 Function 创建出来,,,,,'所有函数.__proto__'都是 Function.prototype
// 2. 所有的原型都是有 Object 创建出来 '所有原型.__proto__ 都是 Object.prototype
// 案例一
function fn() {}
console.log(fn.__proto__.constructor === Function)
console.log(Function.__proto__.constructor === Function)
console.log(Object.__proto__.constructor === Function)
var p = new fn()
console.log(p.__proto__.constructor === fn)
// 案例二
function Fn() {}
Fn.money = 100
Fn.prototype.car = '玛莎拉蒂'
Function.prototype.house = '别墅'
Object.prototype.girl = '小姐姐'
var f = new Fn()
// f---> Fn.prototype ----> Object.prototype
console.log(f.money) // undefined
console.log(f.car) // 玛莎拉蒂
console.log(f.house) // undefined
console.log(f.girl) // 小姐姐
// Fn ---> Function.prototype ---> Object.prototype
console.log(Fn.money) // 100
console.log(Fn.car) // undefined
console.log(Fn.house) // 别墅
console.log(Fn.girl) // 小姐姐
原型链总结
预解析与作用域
词法作用域:静态作用域
作用域在函数声明的时候就确定了,跟调用无关
函数四种调用模式 是确定 this
函数在执行的时候,如果在自己内部找不到变量,就会去外面找
当函数执行结束后,就会执行垃圾回收机制释放内存
// 案例一
var num = 123
function f1() {
console.log(num)
}
function f2() {
var num = 456
f1()
}
f2() // 123
// 案例二
var num = 10
fn1()
function fn1() {
console.log(num)
var num = 20
console.log(num)
}
console.log(num) // undefined 20 10
// 案例三
// 100 200 300 10 200 报错
var num1 = 10
var num2 = 20
function fn(num1) {
num1 = 100
num2 = 200
num3 = 300
console.log(num1)
console.log(num2)
console.log(num3)
var num3
}
fn()
console.log(num1)
console.log(num2)
console.log(num3)
// 案例四
// red blue
var color = 'red'
function outer() {
var anotherColor = 'blue'
function inner() {
var tmpColor = color
color = anotherColor
anotherColor = tmpColor
console.log(anotherColor)
}
inner()
}
outer()
console.log(color)
作用域链
只要是函数,就会形成一个作用域,如果这个函数被嵌套在其他函数中,那么外部函数也有自己的作用域,这个一直往上到全局环境,就形成了一个条作用域链。
变量的搜索原则
:
-
从当前作用域开始搜索变量,如果存在,那么就直接返回这个变量的值。
-
如果不存在,就会往上一层作用域查询,如果存在,就返回。
-
如果不存在,一直查询到全局作用域,如果存在,就返回。如果不存在说明该变量是不存在的。
-
如果一个变量不存在
- 获取这个变量的值会报错
xxx is not defined;
, - 给这个变量设置值,那么设置变量就是
隐式全局变量
- 获取这个变量的值会报错
-
全局作用域只要页面不卸载,就一直存在,不释放。
-
函数每次在调用时,都会形成一个作用域,当函数调用结束时,这个作用域就释放了。
递归函数
函数内部直接或者间接的调用自己
递归的要求:
- 自己调用自己(直接或者间接)
- 要有结束条件(出口)
递归函数主要是化归思想 ,将一个复杂的问题简单化,主要用于解决数学中的一些问题居多。
- 把要解决的问题,归结为已经解决的问题上。
- 一定要考虑什么时候结束让函数结束,也就是停止递归(一定要有已知条件)
斐波那契数列
// 方法一
function fbi(n) {
if (n == 1 || n == 2) {
return 1
}
return fbi(n - 1) + fbi(n - 2)
}
console.log(fbi(12))
/*
分析:
基本上实现了斐波那契数列,但是效率性能很差
fbi(50) 就炸了
原因:
fbi(50) = fbi(49)+fbi(48)
fbi(49) = fbi(48)+fbi(47)
fbi(48) = fbi(47)+fbi(46)
……
可以看到有些值得获取运行了多次,这些是没有必要的,因此针对这个进行优化
*/
缓存(cache):数据的缓冲区,当要读取数据时,先从缓冲中获取数据,如果找到了,直接获取,如果找不到,重新去请求数据。
使用缓存的基本步骤:
- 如果要获取数据,先查询缓存,如果有就直接使用
- 如果没有,就进行计算,并且将计算后的结果放到缓存中,方便下次使用。
// 优化一
// 给数组赋初值,0,1,1 就相当于让函数有了出口,因为下标是从0 开始的,这样就可以表示从第一个于开始到第n个月
// 也可以arr = [1,1] ,这就表示从第0 个月开始的,如果n =12 那就表示有13个月
var arr = [0, 1, 1]
function fbi(n) {
if (arr[n]) {
return arr[n]
} else {
arr[n] = fbi(n - 1) + fbi(n - 2)
return arr[n]
}
}
console.log(fbi(50))
// 还有缺点:arr是以个全局变量
// 优化二
// 使用闭包来优化斐波那契数列
var getFib = (function() {
var arr = [0, 1, 1]
function fbi(n) {
if (!arr[n]) {
arr[n] = fbi(n - 1) + fbi(n - 2)
}
return arr[n]
}
return fbi
})()
console.log(getFib(50))
// 也可以写成这样的形式
function outer() {
var arr = [1, 1]
function fn(n) {
if (!arr[n]) {
// 计算这个月的兔子数
arr[n] = fn(n - 1) + fn(n - 2)
}
return arr[n]
}
return fn
}
var getFib = outer()
console.log(getFib(11))
函数闭包
在 JavaScript 中,在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,则产生闭包。
闭包的作用 :保护私有变量不被修改
闭包缺点
闭包占用的内存是不会被释放的,因此,如果滥用闭包,会造成内存泄漏的问题。闭包很强大,但是只有在必须使用闭包的时候才使用。
- 内存泄漏: 有一块内存,一直被占用了,无法回收, 这块内存泄漏。
- js 垃圾回收机制(对于无用的内存,js 会自动回收)
- 内存:计算机中所有程序的运行都是在内存中进行的,因此内存的性能对计算机的影响非常大,运行程序需要消耗内存,当程序结束时,内存会得到释放。
- javascript 分配内存:当我们定义变量,javascript 需要分配内存存储数据。无论是值类型或者是引用类型,都需要存储在内存中。
- 垃圾回收:当代码执行结束,分配的内存已经不需要了,这时候需要将内存进行回收,在 javascript 语言中,垃圾回收机器会帮我们回收不再需要使用的内存。
1、引用记数法清除
引用记数垃圾收集:如果没有引用指向某个对象(或者是函数作用域),那么这个对象或者函数作用域就会被垃圾回收机制回收。
2、标记清除法清除
使用引用计数法进行垃圾回收的时候,会出现循环引用导致内存泄漏的问题。因此现代的浏览器都采用标记清除法来进行垃圾回收。
这个算法假定设置一个叫做根(root)的对象(在 Javascript 里,根是全局对象 Window)。定期的,垃圾回收器将从根开始,找所有从根开始引用的对象,然后找这些对象引用的对象……从根开始,垃圾回收器将找到所有可以获得的对象和所有不能获得的对象。
function outer() {
var count = 0
function fn() {
count++
console.log('执行次数' + count)
}
return fn
}
var result = outer()
result()
result = null //当函数fn没有被变量引用了,那么函数fn就会被回收,函数fn一旦被回收,那么outer调用形成的作用域也就得到了释放。
闭包练习
// 点击某个按钮,打印下标, 要求:使用闭包
// 1. 使用index属性存起来 btns[i].index = i
// 2. 使用闭包的方式来解决 (两种写法)
// 3. let解决
// 方法一
var btns = document.querySelectorAll('button')
for (var i = 0; i < btns.length; i++) {
;(function(i) {
btns[i].onclick = function() {
console.log(i)
}
})(i)
}
// 方法二
var btns = document.querySelectorAll('button')
for (var i = 0; i < btns.length; i++) {
btns[i].onclick = (function(i) {
return function() {
console.log(i)
}
})(i)
}
/*
上面两种方法解析:(个人理解,也许有问题)
原本我们的写法如下
btns[i].onclick = function(){
console.log(i)
}
这样的写法我们的function是到我们进行点击的时候才会被调用,但是那个时候,for循环的代码已经执行完成,i = btns.length
因此我们点击任意按钮输出的都是一样的值
但是闭包就不一样了,给每一个按钮注册的事件参数都是不一样的,即:第0个按钮注册的事件参数是 i=0,因此点击该按钮就输出了 0
主要是因为有一个外层函数先进行了自调用,导致这块内存就一直存在没有被释放掉,所以就可以找到对应的i,参考下图解析
*/
// 方法三
// let和var一样,都可以声明一个变量 let 在每个{}都是独立
var btns = document.querySelectorAll('button')
for (let i = 0; i < btns.length; i++) {
btns[i].onclick = function() {
console.log(i)
}
}
// 使用延时器打印 1 , 2 ,3 , 4, 5
// 使用setTimeout打印这些值
// 写法一
for (var i = 1; i <= 5; i++) {
;(function(i) {
setTimeout(function() {
console.log(i)
}, i * 1000)
})(i)
}
// 写法二
for (var i = 1; i <= 5; i++) {
setTimeout(
(function(i) {
return function() {
console.log(i)
}
})(i),
i * 1000
)
}
正则表达式
创建正则方式
// 构造函数创建
var reg = new RegExp(/\d/)
// 字面量创建 //
var reg = /\d/
正则使用
reg.test('aaaaad1')
;/\d/.test('111111') // true
常见元字符
代码 | 范围 | 说明 |
---|---|---|
. | [^\n\r] | 匹配除换行符和回车以外的任意字符 |
\d | [0-9] | 匹配数字字符 |
\D | [^0-9] | 匹配非数字字符 |
\s | [\f\n\r\v\t] | 匹配任意的空白符,不可见字符,包括空格 |
\S | [^\f\n\r\v\t] | 可见字符 |
\w | [a-zA-Z0-9 _ ] | 匹配字母或数字或下划线或汉字 |
\W | [^a-za-z0-9] | 非单词字符 |
\b | 匹配单词的开始或结束 | |
\B | 匹配不是单词开头或结束的位置 | |
| | 表示或,优先级最低 | |
() | 优先级最高,表示分组 |
字符类元字符
[]
在正则表达式中表示一个字符的位置,[ ]里面写这个位置可以出现的字符。[^]
在中扩号中的^表示非的意思。- [a-z][0-9]表示范围
console.log(/[abc]/) //匹配a,b,c
// /^表示该位置不可以出现的字符
console.log(/[^abc]/) //匹配除了a,b,c以外的其他字符
边界类元字符
- ^表示开头 []里面的^表示取反
- $表示结尾
// ^ $ 一起用表示精确匹配 以某个字符串开始,同时也要以这个字符结尾
console.log(/^chuan/.test('dachuan')) //必须以chuan开头
console.log(/chuan$/.test('chuang')) //必须以chuan结尾
console.log(/^chuan$/.test('chuan')) //精确匹配chuan
//精确匹配chuan,表示必须是这个
console.log(/^chuan$/.test('chuanchuan')) //fasle
量词类元字符
量词用来控制出现的次数,一般来说量词和边界会一起使用
*
表示能够出现 0 次或者更多次,x>=0;+
表示能够出现 1 次或者多次,x>=1?
表示能够出现 0 次或者 1 次,x=0 或者 x=1{n}
表示能够出现 n 次{n,}
表示能够出现 n 次或者 n 次以上{n,m}
表示能够出现 n-m 次
常见表单的正则验证
// 校验qq
// qq的规律
// 1. 都是数字
// 2. 长度 5-11位
// 3. 不能0开头
var reg = /^[1-9]\d{4,10}$/
console.log(reg.test('1234123'))
console.log(reg.test('8888888'))
// 校验电话号码
// xxx-xxxxxx
// 0开头
// -前面有3位或者4位 大城市 3位 小城市 4位 021 010
// - 后面 7 到 8 位
var mobileReg = /^0\d{2,3}-\d{7,8}$/
console.log(mobileReg.test('021-12345678'))
console.log(mobileReg.test('0111-12345678'))
var mobileReg = /^0\d{2}-\d{8}|0\d{3}-\d{7}$/
console.log(mobileReg.test('021-12345678'))
console.log(mobileReg.test('0211-1234578'))
// 姓名
// 必须汉字
// 长度2-4
// 汉字范围[\u4e00-\u9fa5]
var reg = /^[\u4e00-\u9fa5]{2,4}$/
console.log(reg.test('胡'))
// 手机号
// 1开头
// 11位
var reg = /^1[2-9]\d{9}$/ // 更严谨
var reg = /^1\d{10}$/ // 更宽泛
console.log(reg.test('18511112222'))
// 校验邮箱
// 必须 字母 数字_ @ 字母 数字 _ . com
var reg = /^\w+@\w+(\.[a-z]+){1,2}$/
console.log(reg.test('123@qq.com.cn'))
console.log(reg.test('123@qq.org'))
console.log(reg.test('1111@163.com'))