call()与apply()
方法重用:call() 和 apply()
- 不带参数的使用方法非常相似。
- 带参数的使用方法有所区别:
方法 | 参数 | 举例 |
---|---|---|
call() | 参数列表 | person.fullName.call(person1, “Oslo”, “Norway”); |
apply() | 数组形式 | person.fullName.apply(person1, [“Oslo”, “Norway”]); |
- 如果要使用数组而不是参数列表,则 apply() 方法非常方便。
面试题
语句var arr=[a,b,c,d];执行后,数组arr中每项都是一个整数,能得到其中最大整数语句有:
Math.max(arr[0], arr[1], arr[2], arr[3])
Math.max.call(Math, arr[0], arr[1], arr[2], arr[3])
Math.max.apply(Math,arr)
不能得到其中最大整数语句有:
1 | Math.max(arr) |
解析
Math的max()不支持传入数组,所以错误。
JavaScript 函数 Call
可以通过 call()
调用属于另一个对象的方法。
例子1:普通方法调用
- 下面的例子创建了带有三个属性的对象(firstName、lastName、fullName)。
1
2
3
4
5
6
7
8var person = {
firstName:"Bill",
lastName: "Gates",
fullName: function () {
return this.firstName + " " + this.lastName;
}
}
person.fullName(); // 将返回 "Bill Gates" - fullName 属性是一个方法。person 对象是该方法的拥有者。
- fullName 属性属于 person 对象的方法。
例子2:使用函数 Call
分别让person1与person2去调用person的fullName 方法:
1 | var person = { |
例子3:带参数的 call() 方法
让person1带着参数”Seattle”, “USA”去调用person的fullName属性(方法):
1 | var person = { |
JavaScript 函数 Apply
apply()
会改变this的指向,- 可以通过
apply()
方法调用属于另一个对象的方法。 - 但不仅限于此,当设置第一个参数为null时可以在某些本来需要写成遍历数组变量的任务中使用内建的函数,也就是说此时apply()的作用不是调用属于另一个对象的方法,而是使参数数组中的每一个元素都去执行func函数。【如:下方例子“使用Apply()在数组上模拟 max 方法”】
- call()方法的作用和 apply() 方法类似,区别就是call()方法接受的是参数列表,而apply()方法接受的是一个参数数组。
- apply() 方法调用一个具有给定this值的函数,以及作为一个数组(或类似数组对象)提供的参数。
- 语法:
func.apply(thisArg, [argsArray])
参数 | 描述 |
---|---|
thisArg | 可选的。 在 func 函数运行时使用的 this 值。 请注意,this可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 null 或 undefined 时this会自动替换为指向全局对象,原始值会被包装。 |
argsArray | 可选的。 一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 func 函数。 如果该参数的值为 null 或 undefined,则表示不需要传入任何参数。 从ECMAScript 5 开始可以使用类数组对象。 |
- 返回值:调用有指定this值和参数的函数的结果。
- 描述:在调用一个存在的函数时,你可以为其指定一个 this 对象。 this 指当前对象,也就是正在调用这个函数的对象。 使用 apply, 你可以只写一次这个方法然后在另一个对象中继承它,而不用在新对象中重复写该方法。
你也可以使用 arguments对象作为 argsArray 参数。 arguments 是一个函数的局部变量。 它可以被用作被调用对象的所有未指定的参数。 这样,你在使用apply函数的时候就不需要知道被调用对象的所有参数。 你可以使用arguments来把所有的参数传递给被调用对象。 被调用对象接下来就负责处理这些参数。
例子:使用函数Apply
让person1去调用person的fullName 方法:
1 | var person = { |
例子:带参数的 apply() 方法
apply() 方法接受数组中的参数:
1 | var person = { |
使用Apply()在数组上模拟 max 方法
Math的max()方法
首先我们了解可以使用 Math.max()
方法找到(数字列表中的)最大数字:
1 | Math.max(1,2,3); // 会返回 3 |
让数组调用Math的max()方法
但是JavaScript 数组没有 max() 方法,因此我们可以通过apply()让数组调用 Math的max() 方法:
1
Math.max.apply(null, [1,2,3]); // 也会返回 3
完整例子
第一个参数(null)无关紧要。在本例中未使用它。这些例子会给出相同的结果:
Math.max.apply(Math, [1,2,3]); // 也会返回 3
Math.max.apply(" ", [1,2,3]); // 也会返回 3
Math.max.apply(0, [1,2,3]); // 也会返回 3
JavaScript 严格模式
在 JavaScript 严格模式下,如果 apply()
方法的第一个参数不是对象,则它将成为被调用函数的所有者(对象)。在“非严格”模式下,它成为全局对象。
不能改变箭头函数this指向
- 注意: call, apply, bind 都不能改变箭头函数中的 this 指向
- 因为箭头函数的this只和定义时的作用域this有关,和调用者/调用环境无关,也永远不会改变
js对象 学习笔记(2)
AJAX异步更新
外联样式表没有效果,但是内联正常的解决方法
JS中的同步与异步
作用域
- 作用域就是代码的执行环境,全局执行环境就是全局作用域,函数的执行环境就是私有作用域,它们都是栈内存。总的来说,作用域就是代码执行时开辟的栈内存。
总结 | 描述 |
---|---|
私有作用域 | 函数执行都会形成一个私有作用域 |
全局作用域 | 页面一打开就会形成一个全局的作用域 |
私有变量 | 在私有作用域里边形成的变量 (通过 var 声明; 形参) |
全局变量 | 在全局作用域形成的变量(var a = 12 或者函数内没有声明,直接赋值的变量) |
- 某个执行环境中所有的代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁(全局执行环境直到应用程序退出时,如关闭浏览器或网页,才会被销毁)
- 每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行之后,栈将被环境弹出,把控制权返回给之前的执行环境。ECMAScript 程序中的执行流正是由这个方便的机制控制着。
作用域链
- 当代码在一个环境中执行时,会创建变量对象的一个作用域链(作用域形成的链条)
- 作用域链的前端,始终都是当前执行的代码所在环境的变量对象
- 作用域链中的下一个对象来自于外部环境,而在下一个变量对象则来自下一个外部环境,一直到全局执行环境
- 全局执行环境的变量对象始终都是作用域链上的最后一个对象
- 内部环境可以通过作用域链访问所有外部环境,但外部环境不能访问内部环境的任何变量和函数。
- 所以执行函数时,作用域链是从内到外来排序的。(有形参找形参,没有才找外部的全局变量)
形参与实参
参数 | 概念 |
---|---|
形参(形式参数) | 是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传递的参数。 |
实参(实际参数) | 是在调用时传递给函数的参数,即传递给被调用函数的值。 |
- 形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。因此,形参只在函数内部有效。函数调用结束返回主调用函数后则不能再使用该形参变量。(随着函数被调用而新建,随着函数销毁而销毁)
- 函数调用中发生的数据传送是单向的,即只能把实参的值传给形参,而不能把形参的值传给实参。因此,在函数调用的过程中,形参的值可以改变,而实参的值则不会变化。
- 参考
执行栈
例子
注意:执行函数时,作用域链是从内到外来排序的。(有形参找形参,没有才找外部的全局变量)
1 | var count = 0; |
- 函数的形参属于函数执行上下文,所以当指定这个形参后,它就随着函数被调用而新建,随着函数销毁而销毁。
- 指定了这个形参,调用函数时传递进来的实参count(0)会沿着作用域链找到私有变量 count(接下来的操作都在私有变量上进行),而不是全局变量count。
步骤
我们用执行栈来理解一下
- 函数每次被调用都会产生新的执行上下文(私有变量 count),实参count(全局变量count)的数据(0)被传入函数,私有变量 count变为0,并被压入执行栈,执行完毕后输出1,当前上下文接着被弹出执行栈,私有变量 count被销毁。
- 再次调用时又随着函数被调用而新建私有变量 count,重复第一步。
- (函数内的操作都在私有变量身上进行,随着私有变量的销毁上一次的操作就没了)所以第一次调用应该返回 1,第二次调用也应该返回 1,第 n 次调用都应该返回 1。
1 | var count = 0; |
- 函数的形参属于函数执行上下文,所以当指定这个形参后,它就随着函数被调用而新建,随着函数销毁而销毁。如果不指定这个形参,实参count(0)传进函数以后找不到形参就会沿着作用域链找到全局变量 count,它属于全局执行上下文,这个时候再去调用 foo() 函数就会读写这个全局变量(也就是函数里的所有操作都是在全局变量上进行的)。
- 全局变量不会随着函数的调用而新建,也不会随着函数的销毁而销毁。
- 每个 foo() 函数调用后,给 count(全局变量) 加一,然后被弹出执行栈,而全局执行上下文的生命周期将伴随着整个程序,所以第一次调用打印 1,第二次调用打印 2,第 n 次调用打印 n。
JS的同步与异步
为什么会有同步和异步
因为JavaScript是单线程,因此单一时间内只能处理单一任务,所有任务都需要排队,前一个任务执行完,才能继续执行下一个任务。
但是,如果前一个任务的执行时间很长,比如文件的读取操作或ajax操作,后一个任务就不得不等着,拿ajax来说,当用户向后台获取大量的数据时,不得不等到所有数据都获取完毕才能进行下一步操作,用户只能在那里干等着,严重影响用户体验。
因此,JavaScript在设计的时候,就已经考虑到这个问题,主线程可以完全不用等待文件的读取完毕或ajax的加载成功,可以先挂起处于等待中的加载任务,先运行排在后面的任务,等到文件的读取或ajax有了结果后,再回过头执行挂起的任务(这就是异步),因此,任务就可以分为同步任务和异步任务。
同步任务
同步任务是指在主线程上排队执行的任务,只有前一个任务执行完毕,才能继续执行下一个任务。
当我们打开网站时,网站的渲染过程,比如元素的渲染,其实就是一个同步任务。
异步任务
异步任务是指不进入主线程,而进入任务队列的任务,只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程。
当我们打开网站时,像图片的加载,音乐的加载,其实就是一个异步任务
js的执行机制(事件循环)
- 同步任务进入总线程,异步任务不会进入主线程,而是会先进入任务队列,任务队列其实是一个先进先出的数据结构,也是一个事件队列。
- 比如说文件读取操作,因为这是一个异步任务,因此该任务会被添加到任务队列中,等到IO(输入输出)完成后,就会在任务队列中添加一个事件,表示异步任务准备好啦,可以进入执行栈啦~但是这时候呀,主线程不一定有空,当主线程处理完其它任务有空时,就会读取任务队列,读取里面有哪些事件,排在前面的事件会被优先进行处理,如果该任务指定了回调函数,那么主线程在处理该事件时,就会执行回调函数中的代码,也就是执行异步任务啦
- 单线程从从任务队列中读取任务是不断循环的,每次栈被清空后,都会在任务队列中读取新的任务,如果没有任务,就会等,直到有新的任务,这就叫做任务循环,因为每个任务都是由一个事件触发的,因此也叫作事件循环。
步骤总结:
- 所有同步任务都在主线程上执行,行成一个执行栈
- 主线程之外,还存在一个任务队列,只要异步任务有了结果,就会在任务队列中放置一个事件
- 一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,看看里面还有哪些事件,那些对应的异步任务,于是结束等待状态,(异步任务)进入执行栈,开始执行
- 主线程不断的重复上面的第三步
注意:同步任务由JS引擎执行,异步任务挂起时由浏览器的引擎执行,直到有了结果才放入任务队列等待被执行。
宏任务和微任务
- 所有同步任务和异步任务又会分为宏任务和微任务,所以异步任务中也分先后。
- 可参考笔记宏任务和微任务
js的异步编程
回调函数
这是异步编程最基本的方法。
- 例:假定有两个函数f1和f2,后者等待前者的执行结果,如果f1是一个很耗时的任务,可以考虑改写f1,把f2写成f1的回调函数。【此时f2是异步的,她耗费的时间并不影响同步任务的执行】
1
2
3
4
5
6
7
8
9
10
11
12
13function f2() {
console.log("f2");
}
console.log("start");
function f1(callback) {
console.log("f1");
setTimeout(function () {
callback();
}, 1000);
}
f1(f2);
console.log("end");
// start f1 end f2
Promise
可参考笔记Promise1、Promise2、Promise3.
事件监听
任务的执行不取决于代码的顺序,而取决于某个事件是否发生。
- 例:为f1绑定一个事件,当f1发生done事件,就执行f2。
发布/订阅
与”事件监听”类似。
- 我们假定,存在一个”信号中心”,某个任务执行完成,就向信号中心”发布”(publish)一个信号,其他任务可以向信号中心”订阅”(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就叫做”发布/订阅模式”(publish-subscribe pattern),又称”观察者模式”(observer pattern)。
- 这种方法的性质与”事件监听”类似,但是明显优于后者。因为我们可以通过查看”消息中心”,了解存在多少信号、每个信号有多少订阅者,从而监控程序的运行。
前端使用异步的场景
- 定时任务:setTimeout,setInverval(可参考笔记“setTimeout()与setInterval()”)
- 网络请求:ajax请求,动态
<img>
加载 - 事件绑定:事件函数绑定以后并不会等触发事件函数才继续执行程序,程序照常执行,等需要触发的时候才会执行事件绑定函数。