1. JavaScript 数组的函数 map/forEach/reduce/filter
map
// map
//作用:对数组进行遍历
//返回值:新的数组
// 是否改变:否
var arr = [2, 5, 3, 4];
var ret = arr.map(function(value) {
return value + 1;
});
console.log(ret); //[3,6,4,5]
console.log(arr); //[2,5,3,4]
forEach
// forEach 方法
// 作用:遍历数组的每一项
// 返回值:undefined
// 是否改变:否
var arr = [2, 5, 3, 4];
var ret = arr.forEach(function(value) {
console.log(value); // 2, 5, 3, 4
});
console.log(ret); //undefined
console.log(arr); //[2,5,3,4]
reduce
// reduce 方法
// 作用:对数组进行迭代,然后两两进行操作,最后返回一个值
// 返回值:return出来的结果
// 是否改变:不会
var arr = [1, 2, 3, 4];
var ret = arr.reduce(function(a, b) {
return a * b;
});
console.log(ret); // 24
console.log(arr); // [1, 2, 3, 4]
filter
// filter 过滤
// 作用: 筛选一部分元素
// 返回值: 一个满足筛选条件的新数组
// 是否改变原有数组:不会
var arr = [2, 5, 3, 4];
var ret = arr.filter(function(value) {
return value › 3;
});
console.log(ret); //[5,4]
console.log(arr); //[2,5,3,4]
2. JS 块级作用域、变量提升
块级作用域
JS 中作用域有:全局作用域、函数作用域。没有块作用域的概念。ECMAScript 6(简称 ES6)中新增了块级作用域。块作用域由 { } 包括,if 语句和 for 语句里面的{ }也属于块作用域。
变量提升
如果变量声明在函数里面,则将变量声明提升到函数的开头
如果变量声明是一个全局变量,则将变量声明提升到全局作用域的开头
解析:
‹script type="text/javascript"›
{
var a = 1;
console.log(a); // 1
}
console.log(a); // 1
// 可见,通过var定义的变量可以跨块作用域访问到。
(function A() {
var b = 2;
console.log(b); // 2
})();
// console.log(b); // 报错,
// 可见,通过var定义的变量不能跨函数作用域访问到
if(true) {
var c = 3;
}
console.log(c); // 3
for(var i = 0; i ‹ 4; i++) {
var d = 5;
};
console.log(i); // 4 (循环结束i已经是4,所以此处i为4)
console.log(d); // 5
// if语句和for语句中用var定义的变量可以在外面访问到,
// 可见,if语句和for语句属于块作用域,不属于函数作用域。
{
var a = 1;
let b = 2;
const c = 3;
{
console.log(a); // 1 子作用域可以访问到父作用域的变量
console.log(b); // 2 子作用域可以访问到父作用域的变量
console.log(c); // 3 子作用域可以访问到父作用域的变量
var aa = 11;
let bb = 22;
const cc = 33;
}
console.log(aa); // 11 // 可以跨块访问到子 块作用域 的变量
// console.log(bb); // 报错 bb is not defined
// console.log(cc); // 报错 cc is not defined
}
‹/script›
拓展:var、let、const 的区别
**var **定义的变量,没有块的概念,可以跨块访问, 不能跨函数访问。
let 定义的变量,只能在块作用域里访问,不能跨块访问,也不能跨函数访问。
const 用来定义常量,使用时必须初始化(即必须赋值),只能在块作用域里访问,而且不能修改。
同一个变量只能使用一种方式声明,不然会报错
‹script type="text/javascript"›
// 块作用域
{
var a = 1;
let b = 2;
const c = 3;
// c = 4; // 报错
// let a = 'a'; // 报错 注:是上面 var a = 1; 那行报错
// var b = 'b'; // 报错:本行报错
// const a = 'a1'; // 报错 注:是上面 var a = 1; 那行报错
// let c = 'c'; // 报错:本行报错
var aa;
let bb;
// const cc; // 报错
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
console.log(aa); // undefined
console.log(bb); // undefined
}
console.log(a); // 1
// console.log(b); // 报错
// console.log(c); // 报错
// 函数作用域
(function A() {
var d = 5;
let e = 6;
const f = 7;
console.log(d); // 5
console.log(e); // 6 (在同一个{ }中,也属于同一个块,可以正常访问到)
console.log(f); // 7 (在同一个{ }中,也属于同一个块,可以正常访问到)
})();
// console.log(d); // 报错
// console.log(e); // 报错
// console.log(f); // 报错
‹/script›
3. null/undefined 的区别
null: Null 类型,代表“空值",代表一个空对象指针,使用 typeof 运算得到 “object",所以你可以认为它是一个特殊的对象值。
undefined: Undefined 类型,当一个声明了一个变量未初始化时,得到的就是 undefined。
4. JS 哪些操作会造成内存泄露
- 1)意外的全局变量引起的内存泄露
function leak() {
leak = "xxx"; //leak成为一个全局变量,不会被回收
}
- 2)闭包引起的内存泄露
function bindEvent() {
var obj = document.createElement("XXX");
obj.οnclick = function() {
//Even if it's a empty function
};
}
闭包可以维持函数内局部变量,使其得不到释放。 上例定义事件回调时,由于是函数内定义函数,并且内部函数--事件回调的引用外暴了,形成了闭包。
解决之道,将事件处理函数定义在外部,解除闭包,或者在定义事件处理函数的外部函数中,删除对 dom 的引用。
//将事件处理函数定义在外部
function onclickHandler() {
//do something
}
function bindEvent() {
var obj = document.createElement("XXX");
obj.οnclick = onclickHandler;
}
//在定义事件处理函数的外部函数中,删除对dom的引用
function bindEvent() {
var obj = document.createElement("XXX");
obj.οnclick = function() {
//Even if it's a empty function
};
obj = null;
}
- 3)没有清理的 DOM 元素引用
var elements={
button: document.getElementById("button"),
image: document.getElementById("image"),
text: document.getElementById("text")
};
function doStuff(){
image.src="http://some.url/image";
button.click():
console.log(text.innerHTML)
}
function removeButton(){
document.body.removeChild(document.getElementById('button'))
}
- 4)被遗忘的定时器或者回调
var someResouce = getData();
setInterval(function() {
var node = document.getElementById("Node");
if (node) {
node.innerHTML = JSON.stringify(someResouce);
}
}, 1000);
这样的代码很常见, 如果 id 为 Node 的元素从 DOM 中移除, 该定时器仍会存在, 同时, 因为回调函数中包含对 someResource 的引用, 定时器外面的 someResource 也不会被释放。
- 5)子元素存在引起的内存泄露黄色是指直接被 js 变量所引用,在内存里,红色是指间接被 js 变量所引用,
- 6)IE7/8 引用计数使用循环引用产生的问题
function fn() {
var a = {};
var b = {};
a.pro = b;
b.pro = a;
}
fn();
fn()执行完毕后,两个对象都已经离开环境,在标记清除方式下是没有问题的,但是在引用计数策略下,因为 a 和 b 的引用次数不为 0,所以不会被垃圾回收器回收内存,如果 fn 函数被大量调用,就会造成内存泄漏。在 IE7 与 IE8 上,内存直线上升。
IE 中有一部分对象并不是原生 js 对象。
例如,其内存泄漏 DOM 和 BOM 中的对象就是使用 C++以 COM 对象的形式实现的,而 COM 对象的垃圾回收机制采用的就是引用计数策略。因此,即使 IE 的 js 引擎采用标记清除策略来实现,但 js 访问的 COM 对象依然是基于引用计数策略的。换句话说,只要在 IE 中涉及 COM 对象,就会存在循环引用的问题。
var element = document.getElementById("some_element");
var myObject = new Object();
myObject.e = element;
element.o = myObject;
上面的例子在一个 DOM 元素(element)与一个原生 js 对象(myObject)之间创建了循环引用。其中,变量 myObject 有一个名为 e 的属性指向 element 对象;而变量 element 也有一个属性名为 o 回指 myObject。由于存在这个循环引用,即使例子中的 DOM 从页面中移除,它也永远不会被回收。看上面的例子,有人会觉得太弱了,谁会做这样无聊的事情,但是其实我们经常会这样做
window.οnlοad=function outerFunction(){
var obj=document.getElementById("element"):
obj.οnclick=function innerFunction(){};
};
这段代码看起来没什么问题,但是 obj 引用了 document.getElementById(“element”),而 document.getElementById(“element”)的 onclick 方法会引用外部环境中的变量,自然也包括 obj,是不是很隐蔽啊。最简单的解决方式就是自己手工解除循环引用,比如刚才的函数可以这样
myObject.element=null;
element.o=null;
window.οnlοad=function outerFunction(){
var obj=document.getElementById("element"):
obj.οnclick=function innerFunction(){};
obj=null;
};
将变量设置为 null 意味着切断变量与它此前引用的值之间的连接。当垃圾回收器下次运行时,就会删除这些值并回收它们占用的内存。 要注意的是,IE9+并不存在循环引用导致 Dom 内存泄漏问题,可能是微软做了优化,或者 Dom 的回收方式已经改变
5.重排与重绘的区别,什么情况下会触发?
简述重排的概念
浏览器下载完页面中的所有组件(HTML、JavaScript、CSS、图片)之后会解析生成两个内部数据结构(DOM 树和渲染树),DOM 树表示页面结构,渲染树表示 DOM 节点如何显示。重排是 DOM 元素的几何属性变化,DOM 树的结构变化,渲染树需要重新计算。
简述重绘的概念
重绘是一个元素外观的改变所触发的浏览器行为,例如改变 visibility、outline、背景色等属性。浏览器会根据元素的新属性重新绘制,使元素呈现新的外观。由于浏览器的流布局,对渲染树的计算通常只需要遍历一次就可以完成。但 table 及其内部元素除外,它可能需要多次计算才能确定好其在渲染树中节点的属性值,比同等元素要多花两倍时间,这就是我们尽量避免使用 table 布局页面的原因之一。
简述重绘和重排的关系
重绘不会引起重排,但重排一定会引起重绘,一个元素的重排通常会带来一系列的反应,甚至触发整个文档的重排和重绘,性能代价是高昂的。
什么情况下会触发重排?
- 页面渲染初始化时;(这个无法避免)
- 浏览器窗口改变尺寸;
- 元素尺寸改变时;
- 元素位置改变时;
- 元素内容改变时;
- 添加或删除可见的 DOM 元素时。
重排优化有如下五种方法
- 将多次改变样式属性的操作合并成一次操作,减少 DOM 访问。
- 如果要批量添加 DOM,可以先让元素脱离文档流,操作完后再带入文档流,这样只会触发一次重排。(fragment 元素的应用)
- 将需要多次重排的元素,position 属性设为 absolute 或 fixed,这样此元素就脱离了文档流,它的变化不会影响到其他元素。例如有动画效果的元素就最好设置为绝对定位。
- 由于 display 属性为 none 的元素不在渲染树中,对隐藏的元素操作不会引发其他元素的重排。如果要对一个元素进行复杂的操作时,可以先隐藏它,操作完成后再显示。这样只在隐藏和显示时触发两次重排。
- 在内存中多次操作节点,完成后再添加到文档中去。例如要异步获取表格数据,渲染到页面。可以先取得数据后在内存中构建整个表格的 html 片段,再一次性添加到文档中去,而不是循环添加每一行。
6.call 与 apply 区别
第二个参数的类型不同解析:call 和 apply 的作用,完全一样,唯一的区别就是在参数上面。call 接收的参数不固定,第一个参数是函数体内 this 的指向,第二个参数以下是依次传入的参数。apply 接收两个参数,第一个参数也是函数体内 this 的指向。第二个参数是一个集合对象(数组或者类数组)
7.发布订阅设计模式
发布/订阅模式(Publish Subscribe Pattern)属于设计模式中的行为(Behavioral Patterns)
8. jsonp 优缺点?
jsonp 优缺点
优点
- 1 它不像 XMLHttpRequest 对象实现的 Ajax 请求那样受到同源策略的限制,JSONP 可以跨越同源策略;
- 2 它的兼容性更好,在更加古老的浏览器中都可以运行,不需要 XMLHttpRequest 或 ActiveX 的支持
- 3 在请求完毕后可以通过调用 callback 的方式回传结果。将回调方法的权限给了调用方。这个就相当于将 controller 层和 view 层终于分 开了。我提供的 jsonp 服务只提供纯服务的数据,至于提供服务以 后的页面渲染和后续 view 操作都由调用者来自己定义就好了。如果有两个页面需要渲染同一份数据,你们只需要有不同的渲染逻辑就可以了,逻辑都可以使用同 一个 jsonp 服务。
缺点
- 1 它只支持 GET 请求而不支持 POST 等其它类型的 HTTP 请求
- 2 它只支持跨域 HTTP 请求这种情况,不能解决不同域的两个页面之间如何进行 JavaScript 调用的问题。
- 3 jsonp 在调用失败的时候不会返回各种 HTTP 状态码。
- 4 缺点是安全性。万一假如提供 jsonp 的服务存在页面注入漏洞,即它返回的 javascript 的内容被人控制的。那么结果是什么?所有调用这个 jsonp 的网站都会存在漏洞。于是无法把危险控制在一个域名下…所以在使用 jsonp 的时候必须要保证使用的 jsonp 服务必须是安全可信的
9.兼容各种浏览器版本的事件绑定
/*
兼容低版本IE,ele为需要绑定事件的元素,
eventName为事件名(保持addEventListener语法,去掉on),fun为事件响应函数
*/
function addEvent(ele, eventName, fun) {
if (ele.addEventListener) {
ele.addEventListener(eventName, fun, false);
} else {
ele.attachEvent("on" + eventNme, fun);
}
}
10.typescript 遇到过什么坑
main.js 报错( Cannot find module './App.vue'.)
原因:typescript 不能识别.vue 文件
解决办法:引入 vue 的 typescript declare 库
11.require 与 import 的区别
两者的加载方式不同、规范不同
第一、两者的加载方式不同,
require 是在运行时加载,而 import 是在编译时加载
require('./a')(); // a 模块是一个函数,立即执行 a 模块函数
var data = require('./a').data; // a 模块导出的是一个对象
var a = require('./a')[0]; // a 模块导出的是一个数组,哪都行
import $ from 'jquery';
import * as _ from '_';
import {a,b,c} from './a';
import {default as alias, a as a_a, b, c} from './a'; //用在开头
第二、规范不同,
require 是 CommonJS/AMD 规范,import 是 ESMAScript6+规范
第三、
require 特点:社区方案,提供了服务器/浏览器的模块加载方案。非语言层面的标准。只能在运行时确定模块的依赖关系及输入/输出的变量,无法进行静态优化。
import 特点:语言规格层面支持模块功能。支持编译时静态分析,便于 JS 引入宏和类型检验。动态绑定。
12.什么是原型链?
通过一个对象的proto可以找到它的原型对象,原型对象也是一个对象,就可以通过原型对象的proto,最后找到了我们的 Object.prototype,从实例的原型对象开始一直到 Object.prototype 就是我们的原型链
1 3. 在 css/js 代码上线之后开发人员经常会优化性能,从用户刷新网页开始,一次 js 请求一般情况下有哪些地方会有缓存处理?
dns 缓存,cdn 缓存,浏览器缓存,服务器缓存。
14.iframe 跨域通信和不跨域通信
不跨域通信主页面
‹!DOCTYPE html›
‹html›
‹head›
‹meta charset="utf-8" /›
‹title›‹/title›
‹/head›
‹body›
‹iframe
name="myIframe"
id="iframe"
class=""
src="flexible.html"
width="500px"
height="500px"
›
‹/iframe›
‹/body›
‹script type="text/javascript" charset="utf-8"›
function fullscreen() {
alert(1111);
}
‹/script›
‹/html›子页面 flexible.html
‹!DOCTYPE html›
‹html›
‹head›
‹meta charset="utf-8" /›
‹title›‹/title›
‹/head›
‹body›
我是子页面
‹/body›
‹script type="text/javascript" charset="utf-8"›
// window.parent.fullScreens()
function showalert() {
alert(222);
}
‹/script›
‹/html›
- 1、主页面要是想要调取子页面的 showalert 方法
myIframe.window.showalert(); - 2、子页面要掉主页面的 fullscreen 方法
window.parent.fullScreens(); - 3、js 在 iframe 子页面获取父页面元素:
window.parent.document.getElementById("元素id"); - 4、js 在父页面获取 iframe 子页面元素代码如下:
window.frames["iframe_ID"].document.getElementById("元素id"); - 跨域通信使用postMessage(官方用法)子页面window.parent.postMessage("hello", "http://127.0.0.1:8089");
- 父页面接收
window.addEventListener("message", function(event) { alert(123);});
15.H5 与 Native 如何交互
jsBridge
1 6.什么是面向对象?
面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
解析:
面向对象和面向过程的异同
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。
面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
17.你对松散类型的理解
JavaScript 中的变量为松散类型,所谓松散类型就是指当一个变量被申明出来就可以保存任意类型的值,就是不像 SQL 一样申明某个键值为 int 就只能保存整型数值,申明 varchar 只能保存字符串。一个变量所保存值的类型也可以改变,这在 JavaScript 中是完全有效的,只是不推荐。相比较于将变量理解为“盒子“,《JavaScript 编程精解》中提到应该将变量理解为“触手”,它不保存值,而是抓取值。这一点在当变量保存引用类型值时更加明显。JavaScript 中变量可能包含两种不同的数据类型的值:基本类型和引用类型。基本类型是指简单的数据段,而引用类型指那些可能包含多个值的对象。
1 8.JS 严格模式和正常模式
严格模式使用"use strict";
作用:
- 消除 Javascript 语法的一些不合理、不严谨之处,减少一些怪异行为;
- 消除代码运行的一些不安全之处,保证代码运行的安全;
- 提高编译器效率,增加运行速度;
- 为未来新版本的 Javascript 做好铺垫。
表现:
- 严格模式下, delete 运算符后跟随非法标识符(即 delete 不存在的标识符),会抛出语法错误; 非严格模式下,会静默失败并返回 false
- 严格模式中,对象直接量中定义同名属性会抛出语法错误; 非严格模式不会报错
- 严格模式中,函数形参存在同名的,抛出错误; 非严格模式不会
- 严格模式不允许八进制整数直接量(如:023)
- 严格模式中,arguments 对象是传入函数内实参列表的静态副本;非严格模式下,arguments 对象里的元素和对应的实参是指向同一个值的引用
- 严格模式中 eval 和 arguments 当做关键字,它们不能被赋值和用作变量声明
- 严格模式会限制对调用栈的检测能力,访问 arguments.callee.caller 会抛出异常
- 严格模式 变量必须先声明,直接给变量赋值,不会隐式创建全局变量,不能用 with,
- 严格模式中 call apply 传入 null undefined 保持原样不被转换为 window
解析:
一、概述除了正常运行模式,
ECMAscript 5 添加了第二种运行模式:"严格模式"(strict mode)。顾名思义,这种模式使得 Javascript 在更严格的条件下运行。设立"严格模式"的目的,主要有以下几个:
- 消除 Javascript 语法的一些不合理、不严谨之处,减少一些怪异行为;
- 消除代码运行的一些不安全之处,保证代码运行的安全;
- 提高编译器效率,增加运行速度;
- 为未来新版本的 Javascript 做好铺垫。
"严格模式"体现了 Javascript 更合理、更安全、更严谨的发展方向,包括 IE 10 在内的主流浏览器,都已经支持它,许多大项目已经开始全面拥抱它。另一方面,同样的代码,在"严格模式"中,可能会有不一样的运行结果;一些在"正常模式"下可以运行的语句,在"严格模式"下将不能运行。掌握这些内容,有助于更细致深入地理解 Javascript,让你变成一个更好的程序员。本文将对"严格模式"做详细介绍。
二、进入标志
进入"严格模式"的标志,是下面这行语句:"use strict";老版本的浏览器会把它当作一行普通字符串,加以忽略。
三、如何调用"严格模式"
有两种调用方法,适用于不同的场合。
1 针对整个脚本文件将"use strict"放在脚本文件的第一行,则整个脚本都将以"严格模式"运行。
如果这行语句不在第一行,则无效,整个脚本以"正常模式"运行。如果不同模式的代码文件合并成一个文件,这一点需要特别注意。(严格地说,只要前面不是产生实际运行结果的语句,"use strict"可以不在第一行,比如直接跟在一个空的分号后面。)
‹script›
"use strict";
console.log("这是严格模式。");
‹/script›
‹script›
console.log("这是正常模式。");
kly, it's almost 2 years ago now.
I can admit it now - I run it on my school's network that has about 50 computers.
‹/script›
上面的代码表示,一个网页中依次有两段 Javascript 代码。前一个 script 标签是严格模式,后一个不是。
2 针对单个函数将"use strict"放在函数体的第一行,则整个函数以"严格模式"运行。
function strict() {
"use strict";
return "这是严格模式。";
}
function notStrict() {
return "这是正常模式。";
}
3 脚本文件的变通写法因为第一种调用方法不利于文件合并,所以更好的做法是,借用第二种方法,将整个脚本文件放在一个立即执行的匿名函数之中。
(function() {
"use strict"; // some code here
})();
四、语法和行为改变
严格模式对 Javascript 的语法和行为,都做了一些改变。
1 全局变量显式声明在正常模式中,如果一个变量没有声明就赋值,默认是全局变量。严格模式禁止这种用法,全局变量必须显式声明。"use strict";
v = 1; // 报错,v未声明
for (i = 0; i ‹ 2; i++) {
// 报错,i未声明
}
因此,严格模式下,变量都必须先用 var 命令声明,然后再使用。
2 静态绑定Javascript 语言的一个特点,就是允许"动态绑定",即某些属性和方法到底属于哪一个对象,不是在编译时确定的,而是在运行时(runtime)确定的。
严格模式对动态绑定做了一些限制。某些情况下,只允许静态绑定。也就是说,属性和方法到底归属哪个对象,在编译阶段就确定。这样做有利于编译效率的提高,也使得代码更容易阅读,更少出现意外。具体来说,涉及以下几个方面。
- (1)禁止使用 with 语句因为 with 语句无法在编译时就确定,属性到底归属哪个对象。 "use strict";
var v = 1;
with (o){ // 语法错误
v = 2;
}
19.移动端 click 事件、touch 事件、tap 事件的区别
click事件在移动端会有 200-300ms ms 的延迟,主要原因是苹果手机在设计时,考虑到用户在浏览网页时需要放大,所以,在用户点击的 200-300ms 之后,才触发 click,如果 200-300ms 之内还有 click,就会进行放大缩小。
touch 事件是针对触屏手机上的触摸事件。现今大多数触屏手机 webkit 内核提供了 touch 事件的监听,让开发者可以获取用户触摸屏幕时的一些信息。其中包括:touchstart,touchmove,touchend,touchcancel 这四个事件,touchstart touchmove touchend 事件可以类比于 mousedown mouseover mouseup 的触发
tap 事件在移动端,代替 click 作为点击事件,tap 事件被很多框架(如 zepto)封装,来减少这延迟问题, tap 事件不是原生的,所以是封装的,那么具体是如何实现的呢?
‹script›
function tap(ele, callback) {
// 记录开始时间
var startTime = 0,
// 控制允许延迟的时间
delayTime = 200,
// 记录是否移动,如果移动,则不触发tap事件
isMove = false;
// 在touchstart时记录开始的时间
ele.addEventListener('touchstart', function (e) {
startTime = Date.now();
});
// 如果touchmove事件被触发,则isMove为true
ele.addEventListener('touchmove', function (e) {
isMove = true;
});
// 如果touchmove事件触发或者中间时间超过了延迟时间,则返回,否则,调用回调函数。
ele.addEventListener('touchend', function (e) {
if (isMove || (Date.now() - startTime › delayTime)) {
return;
} else {
callback(e);
}
})
}
var btn = document.getElementById('btn');
tap(btn, function () {
alert('taped');
});
‹/script›
20.JS 单线程还是多线程,如何显示异步操作
JS 本身是单线程的,他是依靠浏览器完成的异步操作。
解析:具体步骤,
- 1、主线程 执行 js 中所有的代码。
- 2、主线程 在执行过程中发现了需要异步的任务任务后扔给浏览器(浏览器创建多个线程执行),并在 callback queque 中创建对应的回调函数(回调函数是一个对象,包含该函数是否执行完毕等)。
- 3、主线程 已经执行完毕所有同步代码。开始监听 callback queque 一旦 浏览器 中某个线程任务完成将会改变回调函数的状态。主线程查看到某个函数的状态为已完成,就会执行该函数。