- 回调函数
- 自己定义的
- 自己没有显式调用
- 最终执行了
- 常见回调函数
- DOM事件回调函数
- 定时器回调函数
- AJAX请求回调函数
- 生命周期回调函数
- IIFE
// 就是匿名函数自调用 (function () { alert("test"); })();
- 函数中的this
- 任何函数本质上都是通过某个对象来调用的,不直接指定就是window
- 所有函数内部都有一个变量this
- 它的值是调用函数的当前对象
- 如何确定this
- 直接调用就是 window
- 对象.函数就是 对象本身
- new 构造函数() 就是创建的实例对象
- call(obj) apply(obj) 传的谁this就是谁
- 函数高级
- 原型与原型链
- 原型对象
- 每个对象都有一个__proto__属性(隐式原型)
- 每个函数都有一个prototype属性(显式原型)
// 定义一个函数fun // fun有prototype属性 function fun() { console.log("log"); } // 如果这个函数时以构造函数的方式运行,new // 创建出来的对象的__proto__就会指向这个函数的prototype // 也就是fun.prototype === f.__proto__ var f = new fun(); console.log(fun.prototype); console.log(f.__proto__);
- prototype就是原型对象,它默认指向一个Object空对象
- 原型对象中有一个constructor属性,指向函数本身
- 给原型对象添加属性(一般是添加函数),函数的所有实例对象自动拥有原型中的属性(方法)
- 显式原型和隐式原型
- 每个对象都有一个__proto__属性(隐式原型)
- 每个函数都有一个prototype属性(显式原型)
- 开发者可以直接操作显式原型,但不能直接操作隐式原型(ES6之前)
- Object的隐式原型是null
- 原型链
- 访问一个对象的属性,查找的方式
- 现在自身属性中查找
- 1没有就沿着__proto__这条链向上找,找到返回
- 最终没找到返回undefined
- 访问一个对象的属性,查找的方式
- 原型对象
- 探索instanceof
- A instanceof B 如果B函数的显式原型对象在A对象的原型链上,返回true,否则返回false
- 执行上下文与执行上下文栈
- 变量提升与函数提升
- 通过var定义的变量,在定义语句之前就可以访问到(undefined)
- 通过function定义的函数,在定义之前就可以直接调用
console.log(a); var a = 3; fn(); function fn() { console.log("log"); }
- 全局执行上下文
- 在执行全局代码前,将window确定为全局执行上下文
- 对全局数据进行预处理
- var定义的变量,添加为window的属性
- function定义的全局函数,添加为window的方法
- this赋值window
- 函数执行上下文(调用时产生)
- 在调用函数,准备执行函数体之前,创建对应的函数执行上下文对象
- 对局部数据进行预处理
- 形参变量赋值,添加为执行上下文的属性
- var定义的局部变量,添加为执行上下文的属性
- function定义的函数,添加为执行上下文的方法
- this赋值调用函数的对象
- 执行上下文栈
- 在全局代码执行前,JS引擎就会创建一个栈来管理所有的执行上下文对象
- 在全局执行上下文(window)确定后,将其添加到栈中
- 在函数执行上下文创建(发生了函数调用)后,将其添加到栈中
- 在当前函数执行完成后,将栈顶对象移出
- 当所有代码执行完成后,栈中只剩window
// 1. 首先生成全局执行上下文对象window,进栈 var bar = function(x) { // 3. 2执行了,导致foo(x)被调用 // 这时候才生成函数执行上下文对象,进栈 foo(x); } var foo = function(x) { console.log(x); } // 2. 函数调用,生成bar的函数执行上下文对象,进栈 // 因为这里时真正的函数调用,所以不会先产生foo的函数执行上下文对象 // 3那里只是在定义bar函数时要用到foo函数而已,并不是在定义bar的时候要执行 bar(1);
- 变量提升与函数提升
- 作用域与作用域链
- 变量取值在这个变量的函数的作用域中取值
- 如果在当前作用域中没有查到值,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链
- 闭包
- 问题引出
// 循环遍历+监听 // 有三个按钮,点击弹出点击的第几个按钮 var btns = document.getElementsByTagName("botton"); for (var i = 0, length = btns.length; i < length; i++) { var btn = btns[i]; // 添加一个属性保存下标 btn.onclick = finction() { // 这里的i时循环结束的最终值 alert("第"+ (i + 1) + "个"); } } for (var i = 0, length = btns.length; i < length; i++) { var btn = btns[i]; // 添加一个属性保存下标 btn.index = i; btn.onclick = finction() { alert("第"+ (this.index + 1) + "个"); } } // 利用闭包 for (var i = 0, length = btns.length; i < length; i++) { // 两个不是一个i (function(i){ var btn = btns[i]; // 添加一个属性保存下标 btn.onclick = finction() { alert("第"+ (i + 1) + "个"); } })(i); }
- 什么是闭包
- 当一个嵌套的内部子函数引用了外部父函数的变量(函数)时,就产生了闭包
- 闭包的作用
- 在函数执行完后,函数内部的变量仍然存活在内存中
- 让函数外部可以操作到函数内部的数据
- 闭包的缺点
- 函数执行完成后,函数内的局部变量没有释放,占用内存时间会变长
- 容易造成内存泄漏(占用的内存没有被即使释放,时间长了有内存溢出的风险)
- 问题引出
- 原型与原型链
- 对象创建方式
//------------------------------------- // Object构造函数 // 适用于开始不确定对象内部数据 var p = new Object(); p.name = "Tom"; p.age = 12; p.setName = function(name) { this.name = name; } p.setName("Jack"); //------------------------------------- // 对象字面量 // 适用于开始可以确定对象内部数据 var p = { name: "Tom", age: 12, setName: function(name) { this.name = name; } } p.setName("Jack"); //------------------------------------- // 工厂模式 通过工厂函数动态创建对象并返回 // 需要创建多个对象 // 对象没有具体的类型,都是Object类型 function factory(name, age) { var obj = { name: name, age: age, setName: function(name) { this.name = name; } } return obj; } var p = factory("Tome", 12); p.setName("Jack"); //------------------------------------- // 自定义构造函数,通过new创建对象 // 需要创建多个确定类型的对象 // 每个对象都有自己的setName方法 function Person(name, age) { this.name: name, this.age: age, this.setName: function(name) { this.name = name; } } var p = new Person("Tome", 12); p.setName("Jack"); //------------------------------------- // 自定义构造函数,通过new创建对象,方法添加到原型中 // 需要创建多个确定类型的对象 function Person(name, age) { this.name: name, this.age: age, } Person.prototype.setName = function(name) { this.name = name; } var p = new Person("Tome", 12); p.setName("Jack");
- 继承模式
- 原型链继承
/* 子类型的原型指向Object,所以能调用Object的方法 改成让他指向父类型,这样就可以调用父类型的方法 父类型的原型还是指向Object,所以Object的方法也可以调 *** 这样子类型本身的原型对象的constructor属性就会被父类型的覆盖掉 */ function Supper() { this.supper = "supper"; } Supper.prototype.showSupper = function() { console.log(this.supper); } function Sub() { this.sub = "sub"; } // 子类型的原型是父类型的一个实例对象 Sub.prototype = new Supper(); // 修正子类型原型对象constructor的指向 Sub.prototype.constructor = Sub; Sub.prototype.showSub = function() { console.log(this.sub); } var sub = new Sub(); sub.showSupper();
- 借用构造函数继承
// 在子类型中用call()调用父类型的构造函数 function Supper(name) { this.name = name; } function Sub(name) { // 把父类型的构造函数的上下文对象变成子类型 Supper.call(this, name); this.name = name }
- 组合继承(原型链+借用构造函数)
function Supper(name, age) { this.name = name; this.age = age; } Supper.prototype.setName = function(name) { this.name = name; } function Sub(name, age, post) { Supper.call(name, age); // 为了得到属性 this.post = post; } // 子类型的原型是父类型的一个实例对象 Sub.prototype = new Supper(); // 为了能看到父类型的方法 // 修正子类型原型对象constructor的指向 Sub.prototype.constructor = Sub; Sub.prototype.setPost = function(post) { this.post = post; } var sub = new Sub("Tom", 12, "123456"); sub.setPost("789"); sub.setName("Jack");
- 原型链继承
- 线程机制与事件机制
- JavaScript是单线程运行的
- 使用H5中的Web Workers可以多线程运行
- JS引擎执行代码
- 先执行初始化代码
- 设置定时器
- 绑定监听
- 发送AJAX请求
- 之后在某个时刻才会执行回调函数
- 先执行初始化代码
- 事件循环机制
- 所有代码分类
- 初始化执行代码(同步代码):包含绑定DOM事件监听,设置定时器,发送AJAX请求的代码
- 回调执行代码(异步代码):处理回调逻辑
- JS引擎执行代码的基本流程
- 初始化代码->回调代码
- 模型的两个重要组成部分
- 事件管理模块
- 回调队列
- 模型的运转流程
- 执行初始化代码,将事件回调函数交给对应模块管理
- 当事件发生时,管理模块会将回调函数及其数据添加到回调队列中
- 只有当初始化代码执行完成后(一段时间),才会遍历读取回调队列中的回调函数执行
- 所有代码分类
Tips
- instanceof 判断对象的具体类型
- 定时器不能保证真正的定时执行
- 一般延迟一点,也有可能延迟很长时间
- 定时器的回调函数是在主线程执行的,JS是单线程的
- 定时器的回调函数只有在运行栈中的代码全部执行完毕后才有可能执行
- 定时器是通过事件循环模型实现的