1.★★ 介绍一下JS的内置类型有哪些?
空类型:null
未定义:undefined
布尔:boolean
数字:number
字符串:string
符号:symbol(ES6新增)
对象:object
除了对象之外,其他为基本类型.
2.★★★★ 介绍一下 typeof 区分类型的原理
typeof原理: 不同的对象在底层都表示为二进制,
在Javascript中二进制前(低)三位存储其类型信息
000: 对象
010: 浮点数
100:字符串
110: 布尔
1: 整数
typeof null 为"object",
原因是因为 不同的对象在底层都表示为二进制,
在Javascript中二进制前(低)三位都为0的话会被判断为Object类型,null的二进制表示全为0,
自然前三位也是0,所以执行typeof时会返回"object"
3.★★★ 介绍一下类型转换
/*-------------------显式转换---------------------*/ 1. toString() // 转化为字符串,不可以转null和underfined 2. Number() // 转换为数字,字符串中有一个不是数值的字符,返回NaN 3. parseInt() // 转换为数字,第一个字符不是数字或者符号就返回NaN 4. String() // 转换为字符串, 5. Boolean() // 转换为布尔值 /*-------------------隐式转换(+-)---------------------*/ 当 JavaScript 尝试操作一个 "错误" 的数据类型时,会自动转换为 "正确" 的数据类型 1. num + "" -> String 2. num + bool -> num // 当加号运算符时,String和其他类型时,其他类型都会转为 String;其他情况,都转化为Number类型 3. string - num -> num // 其他运算符时, 基本类型都转换为 Number,String类型的带有字符的比如: 4. 'a1' - num -> NaN // 与undefined 一样。 /*-------------------隐式转换(逻辑表达式)---------------------*/ 1. 对象和布尔值比较 对象和布尔值进行比较时,对象先转换为字符串,然后再转换为数字,布尔值直接转换为数字 [] == true; //false []转换为字符串'',然后转换为数字0,true转换为数字1,所以为false 2. 对象和字符串比较 对象和字符串进行比较时,对象转换为字符串,然后两者进行比较。 [1,2,3] == '1,2,3' // true [1,2,3]转化为'1,2,3',然后和'1,2,3', so结果为true; 3. 对象和数字比较 对象和数字进行比较时,对象先转换为字符串,然后转换为数字,再和数字进行比较。 [1] == 1; // true `对象先转换为字符串再转换为数字,二者再比较 [1] => '1' => 1 所以结果为true 4. 字符串和数字比较 字符串和数字进行比较时,字符串转换成数字,二者再比较。 '1' == 1 // true 5. 字符串和布尔值比较 字符串和布尔值进行比较时,二者全部转换成数值再比较。 '1' == true; // true 6. 布尔值和数字比较 布尔值和数字进行比较时,布尔转换为数字,二者比较。 true == 1 // true
4.★★★★ 说说你对 JavaScript 的作用域的理解。什么是作用域链?
在 JavaScript 中有两种作用域类型:
1. 局部作用域:只能在函数内部访问它们
2. 全局作用域:网页的所有脚本和函数都能够访问它
JavaScript 拥有函数作用域:每个函数创建一个新的作用域。
作用域决定了这些变量的可访问性(可见性)。
函数内部定义的变量从函数外部是不可访问的(不可见的)。
作用域链:
当查找变量的时候,会先从当前上下文的变量对象中查找,
如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,
一直找到全局上下文的变量对象,也就是全局对象。
这样由多个执行上下文的变量对象构成的链表就叫做作用域链**
5.★★ 解释下 let 和 const 的块级作用域
/*------------let-----------*/ 1. let声明的仅在块级作用域内有效, 2. let不会发生变量提升的现象,所以一定要在定义后使用,否则报错。 3. 暂时性死区:只要块级作用域内存在let命令,它所声明的变量就绑定这个区域,不再受外部影响。 4. 不允许重复声明,let不允许在相同作用域内,重复声明同一个变量: /*-----------const----------*/ 1. 声明一个只读的常量。一旦声明,常量的值就不能改变。 2. 一旦声明,就要立即初始化,否则也报错。 3. const命令声明的常量也不提升,同样存在暂时性死区,只能在声明的位置后使用。 4. 也不可以重复声明。
6.★★★★ 说说你对执行上下文的理解
执行上下文有且只有三类,全局执行上下文,函数上下文,与eval上下文(eval一般不会使用)
1. 全局执行上下文:
全局执行上下文只有一个,也就是我们熟知的window对象,我们能在全局作用域中通过this直接访问到它
2. 函数执行上下文
函数执行上下文可存在无数个,每当一个函数被调用时都会创建一个函数上下文;
需要注意的是,同一个函数被多次调用,都会创建一个新的上下文。
3. 执行上下文栈(下文简称执行栈)也叫调用栈,
执行栈用于存储代码执行期间创建的所有上下文,具有LIFO(Last In First Out后进先出,也就是先进后出)的特性。
JS代码首次运行,都会先创建一个全局执行上下文并压入到执行栈中,之后每当有函数被调用,
都会创建一个新的函数执行上下文并压入栈内;由于执行栈LIFO的特性,
所以可以理解为,JS代码执行完毕前在执行栈底部永远有个全局执行上下文。
7.★★★ 对闭包的看法,为什么要用闭包?说一下闭包的原理以及应用场景?闭包的 this 指向问题?
闭包的作用: 1. 在外部访问函数内部的变量 2. 让函数内的局部变量可以一直保存下去 3. 模块化私有属性和公共属性 闭包的原理: 全局变量生存周期是永久,局部变量生存周期随着函数的调用介绍而销毁。 闭包就是 在函数中定义且成为该函数内部返回的函数的自由变量 的变量,该变量不会随着外部函数调用结束而销毁。 (注:不光是变量,函数内声明的函数也可以形成闭包) 当函数可以记住并访问所在的词法作用域,即使函数是在当前词法作用域之外执行,这时就产生了闭包。 闭包的应用场景: // 1. 返回值 最常见的一种形式 var fun_1 = function () { var name = "limo"; return function () { return name; } } var fun_1 = function () { var name = "limo"; return name; } var fu_1 = fun_1(); console.log("fu_1():" + fu_1()); // 2. 函数赋值 一种变形的形式是将内部函数赋值给一个外部变量 var f2; var fun_2 = function () { var name = "limop" var a = function () { return name; } f2 = a; } f2(); console.log(f2); // 3. 函数参数 通过函数参数引用内部函数产生闭包 var fn_3 = function (f3) { console.log(f3); } function fun_3() { var name = "limo"; var a = function () { return name; } fn_3(a) } fun_3(); // 4. IIFE(自执行函数) var fn_4 = function (f4) { console.log(f4); }; (function fun_4() { var name = "limo"; var a = function () { return name; } fn_3(a) })(); // 5. 循环赋值 function foo(){ var arr = []; for(var i = 0; i < 10; i++){ arr[i] = (function(n){ return function(){ return n; } })(i) } return arr; } var bar = foo(); console.log(bar[3]()); // 6. getter和setter // getter和setter函数来将要操作的变量保存在函数内部,防止暴露在外部 var getValue, setValue; (function () { var num = 0 getValue = function () { return num } setValue = function (v) { if (typeof v === 'number') { num = v } } })(); console.log(getValue()); //0 setValue(10); console.log(getValue()) //10 // 7.迭代器(计数器) var add = function(){ var num = 0; return function(){ return ++num; } }(); console.log(add()); console.log(add()); function setUp(arr){ var i = 0; return function(){ return arr[i++]; } } var next = setUp(['Steve','Alex','LingYi']); console.log(next()); console.log(next()); console.log(next()); // 8.触发事件 window.onload = function (){ var btn = document.querySelector('.btn') btn.onclick = function (){//事件相当于在全局触发 btn.style.color = 'red'//保持对上层作用域的引用 btn console.log('abc') // this } } 闭包的this指向问题: var myNumber = { value: 1, add: function(i){ var helper = function(i){ console.log(this); this.value += i; } helper(i); } } myNumber.add(1); 1.this指向window对象(因为匿名函数的执行具有全局性,所以其this对象指向window); 2.不能实现value加1(每个函数在被调用时都会自动取得两个特殊变量,this和arguments,内部函数在搜索这两个对象时,只会搜索到其活动对象为止,所以不能实现访问外部函数的this对象); 3.修改代码实现正确功能 第一种解决方法: var myNumber={ value:1, add:function(i){ var that=this;//定义变量that用于保存上层函数的this对象 var helper=function(i){ console.log(that); that.value+=i; } helper(i); } } myNumber.add(1); 第二种解决方法: var myNumber={ value:1, add:function(i){ var helper=function(i){ this.value+=i; } helper.apply(this,[i]);//使用apply改变helper的this对象指向,使其指向myNumber对象 } } myNumber.add(1); 第三种解决方法 var myNumber={ value:1, add:function(i){ var helper=function(i){ this.value+=i; }.bind(this,i);//使用bind绑定,和apply相似,只是它返回的是对函数的引用,不会立即执行 helper(i); } } myNumber.add(1);
8.★★★ 简述闭包的问题以及优化
闭包的缺点:占用内层空间 大量使用闭包会造成栈溢出
由于闭包会一直占用内存空间,直到页面销毁,我们可以主动将已使用的闭包销毁:
将闭包函数赋值为null 可以销毁闭包
9.★★★ 如何确定 this 指向?改变 this 指向的方式有哪些?
this 指向: 1. 全局上下文(函数外) 无论是否为严格模式,均指向全局对象。注意:严格模式下全局对象为undifined 2. 函数上下文(函数内) 默认的,指向函数的调用对象,且是最直接的调用对象: 简单调用,指向全局对象注意:严格模式下全局对象为undifined,某些浏览器未实现此标准也可能会是window 改变this指向的方式: 1. 第一种: new关键字改变this指向 //构造函数版this function Fn(){ this.user = "李某"; } var a = new Fn(); console.log(a.user); //李某 /*----------------------------------------*/ 2. 第二种: call() // 把b添加到第一个参数的环境中,简单来说,this就会指向那个对象 var a = { user:"李某", fn:function(){ console.log(this.user); //李某 } } var b = a.fn; b.call(a); //若不用call,则b()执行后this指的是Window对象 /*----------------------------------------*/ 3. 第三种:apply() // apply方法和call方法有些相似,它也可以改变this的指向,也可以有多个参数,但是不同的是,第二个参数必须是一个数组 var a = { user:"李某", fn:function(){ console.log(this.user); //李某 } } var b = a.fn; b.apply(a); /*----------------------------------------*/ 4. 第四种:bind() // bind方法返回的是一个修改过后的函数, // bind也可以有多个参数,并且参数可以执行的时候再次添加,但是要注意的是,参数是按照形参的顺序进行的。 var a = { user:"李某", fn:function(){ console.log(this.user); //李某 } } var b = a.fn; var c = b.bind(a); c();
10.★★★ 介绍箭头函数的 this
由于箭头函数不绑定this, 它会捕获其所在(即定义的位置)上下文的this值, 作为自己的this值
1. 所以 call() / apply() / bind() 方法对于箭头函数来说只是传入参数,对它的 this 毫无影响。
2. 考虑到 this 是词法层面上的,严格模式中与 this 相关的规则都将被忽略
作为方法的箭头函数this指向全局window对象,而普通函数则指向调用它的对象
11.★★★ 谈一下你对原型链的理解,画一个经典的原型链图示
原型链:
因为每个对象和原型都有原型,对象的原型指向原型对象,
而父的原型又指向父的父,这种原型层层连接起来的就构成了原型链。
12.★★★ ES5/ES6 的继承除写法以外还有什么区别?
- ES5 的继承实质上是先创建子类的实例对象,然后再将父类的方法添加 到 this 上(Parent.apply(this))
- ES6 的继承机制完全不同,实质上是先创建父类的实例对象 this(所以必 须先调用父类的super()方法),然后再用子类的构造函数修改 this。
- ES5 的继承时通过原型或构造函数机制来实现。
- ES6 通过 class 关键字定义类,里面有构造方法,类之间通过 extends 关 键字实现继承。
- 子类必须在 constructor 方法中调用 super 方法,否则新建实例报错。因 为子类没有自己的 this对象,而是继承了父类的 this 对象,然后对其进行加工。 如果不调用 super 方法,子类得不到 this 对象。
- 注意 super 关键字指代父类的实例,即父类的 this 对象。 注意:在子类构造函数中,调用 super 后,才可使用 this关键字,否则报错
13.★★★★ 你对事件循环有了解吗?说说看!
Event Loop(事件循环)中,每一次循环称为 tick, 每一次tick的任务如下:
- 执行栈选择最先进入队列的宏任务(通常是script整体代码),如果有则执行
- 检查是否存在 Microtask,如果存在则不停的执行,直至清空 microtask 队列
- 更新render(每一次事件循环,浏览器都可能会去更新渲染)
- 重复以上步骤
宏任务 > 所有微任务 > 宏任务,如下图所示:
从上图我们可以看出:
1、将所有任务看成两个队列:执行队列与事件队列。
2、执行队列是同步的,事件队列是异步的,宏任务放入事件列表,微任务放入执行队列之后,事件队列之前。
3、当执行完同步代码之后,就会执行位于执行列表之后的微任务,然后再执行事件列表中的宏任务
14.★★★★ 微任务和宏任务有什么区别?
15.★★★★★ 浏览器和 Node 事件循环的区别?
浏览器中的事件循环:
Node中的事件循环:
Node 中的 Event Loop 和浏览器中的是完全不相同的东西。Node.js 采用 V8 作为 js 的解析引擎,而 I/O 处理方面使用了自己设计的 libuv,libuv 是一个基于事件驱动的跨平台抽象层,封装了不同操作系统一些底层特性,对外提供统一的 API,事件循环机制也是它里面的实现
Node.js 的运行机制如下:
- V8 引擎解析 JavaScript 脚本。
- 解析后的代码,调用 Node API。
- libuv 库负责 Node API 的执行。它将不同的任务分配给不同的线程,形成一个 Event Loop(事件循环),以异步的方式将任务的执行结果返回给 V8 引擎。
- V8 引擎再将结果返回给用户。
16.★★★ 异步解决方案有哪些?
解决方案: /*---------1.回调函数callback:----------*/ 被作为实参传入另一函数,并在该外部函数内被调用,用以来完成某些任务的函数。如setTimeOut,ajax请求,readFile等。 例: function greeting(name) { alert('Hello ' + name); } function processUserInput(callback) { var name = prompt('请输入你的名字。'); callback(name); } processUserInput(greeting); 优点: 解决了异步的问题。 缺点: 回调地狱:多个回调函数嵌套的情况,使代码看起来很混乱,不易于维护。 /*---------2.事件发布订阅:---------*/ 当一个任务执行完成后,会发布一个事件,当这个事件有一个或多个‘订阅者’的时候,会接收到这个事件的发布,执行相应的任务,这种模式叫发布订阅模式。如node的events,dom的事件绑定 例: document.body.addEventListener('click',function(){ alert('订阅了'); },false); document.body.click(); 优点: 时间对象上的解耦。 缺点: 消耗内存,过度使用会使代码难以维护和理解 /*---------3.Promise:---------*/ Promise是es6提出的异步编程的一种解决方案。 Promise 对象有三种状态: pending: 初始状态,既不是成功,也不是失败状态。 fulfilled: 意味着操作成功完成。 rejected: 意味着操作失败。 promise的状态只能从pending变成fulfilled,和pending变成rejected,状态一旦改变,就不会再改变,且只有异步操作的结果才能改变promise的状态。 例: let promise = new Promise(function (resolve, reject) { fs.readFile('./1.txt', 'utf8', function (err, data) { resolve(data) }) }) promise .then(function (data) { console.log(data) }) 优点: 解决了回调地狱的问题,将异步操作以同步操作的流程表达出来。 缺点: 无法取消promise。如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。当执行多个Promise时,一堆then看起来也很不友好。 /*---------4.Generator:---------*/ Generator是es6提出的另一种异步编程解决方案,需要在函数名之前加一个*号,函数内部使用yield语句。Generaotr函数会返回一个遍历器,可以进行遍历操作执行每个中断点yield。 例: function * count() { yield 1 yield 2 return 3 } var c = count() console.log(c.next()) // { value: 1, done: false } console.log(c.next()) // { value: 2, done: false } console.log(c.next()) // { value: 3, done: true } console.log(c.next()) // { value: undefined, done: true } 优点: 没有了Promise的一堆then(),异步操作更像同步操作,代码更加清晰。 缺点: 不能自动执行异步操作,需要写多个next()方法,需要配合使用Thunk函数和Co模块才能做到自动执行。 /*---------5.async/await:---------*/ async是es2017引入的异步操作解决方案,可以理解为Generator的语法糖,async等同于Generator和co模块的封装,async 函数返回一个 Promise。 例: async function read() { let readA = await readFile('data/a.txt') let readB = await readFile('data/b.txt') let readC = await readFile('data/c.txt') console.log(readA) console.log(readB) console.log(readC) } read() 优点: 内置执行器,比Generator操作更简单。async/await比*yield语义更清晰。返回值是Promise对象,可以用then指定下一步操作。代码更整洁。可以捕获同步和异步的错误。
17.★★★ async 和 await 、promise的区别 和 这两个的本质
/---------Promise概念:---------/
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大,简单地说,Promise好比容器,里面存放着一些未来才会执行完毕(异步)的事件的结果,而这些结果一旦生成是无法改变的
/---------async await概念:---------/
async await也是异步编程的一种解决方案,他遵循的是Generator 函数的语法糖,他拥有内置执行器,不需要额外的调用直接会自动执行并输出结果,它返回的是一个Promise对象。
两者的区别:
Promise的出现解决了传统callback函数导致的“地域回调”问题,但它的语法导致了它向纵向发展行成了一个回调链,遇到复杂的业务场景,这样的语法显然也是不美观的。
async await代码看起来会简洁些,使得异步代码看起来像同步代码,await的本质是可以提供等同于”同步效果“的等待异步返回能力的语法糖,只有这一句代码执行完,才会执行下一句。
async await与Promise一样,是非阻塞的。
async await是基于Promise实现的,可以说是改良版的Promise,它不能用于普通的回调函数。
18.★★★ 简述 aync await 的好处
1. async/await最重要的好处是同步编程风格
2. async/await有本地浏览器支持。截至今天,所有主流浏览器 查看都完全支持异步功能。
3. async关键字。它声明 getBooksByAuthorWithAwait()函数返回值确保是一个 promise,以便调用者可以安全调用 getBooksByAuthorWithAwait().then(…)或 await getBooksByAuthorWithAwait()
19.★★★ 移动端点击事件 300ms 延迟如何去掉?原因是什么?
300毫秒原因:
当用户第一次点击屏幕后,需要判断用户是否要进行双击操作,于是手机会等待300毫秒,
解决方法:FastClick.js
FastClick 是 FT Labs 专门为解决移动端浏览器 300 毫秒点击延迟问题所开发的一个轻量级的库。FastClick的实现原理是在检测到touchend事件的时候,
会通过DOM自定义事件立即出发模拟一个click事件,并把浏览器在300ms之后的click事件阻止掉。
20.★★★ Cookie 有哪些属性?其中HttpOnly,Secure,Expire分别有什么作用?
Cookie属性: name 字段为一个cookie的名称。 value 字段为一个cookie的值。 domain 字段为可以访问此cookie的域名。 path 字段为可以访问此cookie的页面路径。 比如domain是abc.com,path是/test,那么只有/test路径下的页面可以读取此cookie。 expires/Max-Age 字段为此cookie超时时间。若设置其值为一个时间,那么当到达此时间后,此cookie失效。不设置的话默认值是Session,意思是cookie会和session一起失效。当浏览器关闭(不是浏览器标签页,而是整个浏览器) 后,此cookie失效。 Size 字段 此cookie大小。 http 字段 cookie的httponly属性。若此属性为true,则只有在http请求头中会带有此cookie的信息,而不能通过document.cookie来访问此cookie。 secure 字段 设置是否只能通过https来传递此条cookie 1 secure属性 当设置为true时,表示创建的 Cookie 会被以安全的形式向服务器传输,也就是只能在 HTTPS 连接中被浏览器传递到服务器端进行会话验证,如果是 HTTP 连接则不会传递该信息,所以不会被窃取到Cookie 的具体内容。 2 HttpOnly属性 如果在Cookie中设置了"HttpOnly"属性,那么通过程序(JS脚本、Applet等)将无法读取到Cookie信息,这样能有效的防止XSS攻击。 3 Expire属性 设置Cookie的失效时间:
21.★★★★ 如何实现函数的柯里化?比如 add(1)(2)(3)
/*-----解决方法1:-----*/ function add () { var args = Array.prototype.slice.call(arguments); var fn = function () { var sub_arg = Array.prototype.slice.call(arguments); // 把全部的参数聚集到参数的入口为一个参数: args.concat(sub_arg) return add.apply(null, args.concat(sub_arg)); } fn.valueOf = function () { return args.reduce(function(a, b) { return a + b; }) } return fn; } console.log(add(1,2)) // 3 console.log(add(1)(2)) // 3 console.log(add(1)(2)(3)) // 6 console.log(add(1,2,3)(4)) // 10 /*-----解决方法2:-----*/ function add () { var args = Array.prototype.slice.call(arguments); var fn = function () { // 把参数都放在一个相当于全局变量的 args 里面 args.push(...arguments) return fn; } fn.valueOf = function () { return args.reduce(function(a, b) { return a + b; }) } return fn; } console.log(add(1,2)) // 3 console.log(add(1)(2)) // 3 console.log(add(1)(2)(3)) // 6 console.log(add(1,2,3)(4)) // 10
22.★★★★ 什么是反柯里化
在JavaScript中,当我们调用对象的某个方法时,
其实不用去关心该对象原本是否被设计为拥有这个方法,这是动态类型语言的特点。
可以通过反柯里化(uncurrying)函数实现,让一个对象去借用一个原本不属于他的方法。
23.★★ 将 [1,2] 与 [3,[4]] 合并为 [1,2,3,[4]]
JS数组合并方法: let arr3 = [1,2].concat([3,[4]]); //[1,2,3,[4]]
24.★★ Array.forEach() 与 Array.map() 的区别,Array.slice() 与 Array.splice() 的区别?
/*-----forEach-----*/ forEach不支持return,对原来的数组也没有影响。但是我们可以自己通过数组的索引来修改原来的数组 var ary = [12,23,24,42,1]; var res = ary.forEach(function (item,index,input) { input[index] = item*10; }) console.log(res);//-->undefined; console.log(ary);//-->会对原来的数组产生改变; /*-----map-----*/ map支持return返回值,也不影响原数组,但是会返回一个新的数组 var ary = [12,23,24,42,1]; var res = ary.map(function (item,index,input) { return item*10; }) console.log(res);//-->[120,230,240,420,10]; console.log(ary);//-->[12,23,24,42,1]; array.slice(start,end)函数是取array数组中指定的一些元素: 根据数组下标start和end,两个参数为取值的开始和结束下标,取出的值不包括end位置的值,生成一个新数组作为返回值; 这里end可以为空,为空则取从start位置到数组结尾的元素,生成新数组。 array.splice(start,length,insert_one......)函数则是直接在原数组进行删除、添加、替换元素的操作: start为数组删除元素的开始下标, length为从start位置开始array删除元素的个数, 后面参数为删除之后array重新插入的数据内容,插入位置为删除位置,而非数组开头或末尾, 返回值为array删除的元素组成的数组。 显而易见,splice函数可以用来对数组元素进行替换。由splice操作后的数组array,数组中内容如果已经改变,就再也找不回array在splice之前的模样。
25.★★ 将 1234567 转换为 1,234,567
function fun(n){ return String(n).replace(/(?!^)(?=(\d{3})+\.)/g, ",") }
26.★★★ bind 的作用是什么?
bind()方法主要就是将函数绑定到某个对象,
bind()会创建一个函数,函数体内的this对象的值会被绑定到传入bind()第一个参数的值
27.★★Promise.resolve(Promise.resolve(1)).then(console.log) 输出?
// 答案:1
28.★★★ var let const的区别
- var声明的变量会挂载在window上,而let和const声明的变量不会
- var声明变量存在变量提升,let和const不存在变量提升
- let和const声明形成块作用域
- 同一作用域下let和const不能声明同名变量,而var可以
- 使用let/const声明的变量在当前作用域存在暂存死区
- const一旦声明必须赋值,不能使用null占位,声明后不能再修改,如果声明的是复合类型数据,可以修改其属性
29.★★★ document load 和 documen ready的区别
DOM文档解析:
解析html结构
加载脚本和样式文件
解析并执行脚本
构造html的DOM模型 //ready
加载图片等外部资源文件
页面加载完毕 //load
document load:
load是当页面所有资源全部加载完成后(包括DOM文档树,css文件,js文件,图片资源等),
执行一个函数,load方法就是onload事件。
documen ready:
构造html的DOM模型加载完毕后触发
30.★★★ 如何自定义事件?
自定义事件 事件是与DOM交互的最常见的方式。通过实现自定义事件,可以让事件用于非DOM代码中。 思想:创建一个管理事件的对象,让其他对象监听那些事件。 基本模式: function EventTarget(){ this.handlers = {}; } EventTarget.prototype = { constructor:EventTarget, addHandler:function(type,handler){ if(typeof this.handlers[type] === "undefined"){ this.handlers[type] = []; } this.handlers[type].push(handler); }, fire:function(event){ if(!event.target){ event.target = this; } if(this.handlers[event.type] instanceof Array){ const handlers = this.handlers[event.type]; handlers.forEach((handler)=>{ handler(event); }) } }, removeHandler:function(type,handler){ if(this.handlers[type] instanceof Array){ const handlers = this.handlers[type]; for(var i = 0,len = handlers.length; i < len; i++){ if(handlers[i] === handler){ break; } } handlers.splice(i,1); } } }
JavaScript面试题(二) https://developer.aliyun.com/article/1399325