Incheon

  • 主页
  • 分类
  • 归档
  • 简历
所有文章 友链 关于我

Incheon

  • 主页
  • 分类
  • 归档
  • 简历

JS面试汇总Ⅱ

2017-11-12

浏览器内核

浏览器内核: 支持浏览器运行的最核心的程序

  • Chrome, Safar内核i: webkit
  • firefox内核: Gecko
  • IE内核: Trident
  • 360,搜狗等国内浏览器内核: Trident + webkit

内核模块

  • 主线程:
    • JS引擎模块: 负责JS程序的编译与运行
    • Html,Css文档解析模块 : 负责页面文本的解析
    • Dom/Css模块 : 负责Dom/Css在内存中的相关处理
    • 布局和渲染模块 : 负责页面的布局和效果的绘制
    • 文本 –> 对象 –> 布局 –> 渲染
  • 分线程:
    • 定时器模块 : 负责定时器的管理
    • 网络请求模块 : 负责服务器请求(常规/Ajax)
    • 事件响应模块 : 负责事件的管理

定时器 / JS线程 / 事件循环模型问题

  • 定时器并不能真正保证定时执行(原因在于JS模型的运转流程)
  • JS是单线程 所以定时器是在JS的主线程执行的
    • JS单线程模式: 用途有关. 否则需要处理很复杂的同步问题

举个例子:

1
2
3
4
5
6
7
在执行异步代码的时候,如果定时器被正在执行的代码阻塞了,

它将会进入队列的尾部去等待执行直到下一次可能执行的时间出现(可能超过设定的延时时间)。

setTimeout 和setInterval 是有着本质区别的:setTimeout 这段代码会在每次回调函数执行之后至少需要延时“指定延迟毫秒值”再去执行(可能是更多,但是不会少)。

但是setInterval会每隔“指定延迟毫秒值”就去尝试执行一次回调函数,不管上一个回调函数是不是还在执行。

JS引擎代码

JS引擎代码的分类

  • 初始化代码
  • 回调代码

    JS引擎执行代码的基本流程

  1. 先执行初始化代码: 包含一些特别的代码
    • 启动定时器
    • 绑定Dom事件监听
    • 发送ajax请求
  2. 后执行回调代码: 处理回调逻辑

    JS模型的重要组成部分

    • 事件管理模块(在分线程执行,由浏览器管理)
    • 回调队列 callback queue
      • 任务队列 task queue
      • 消息队列 message queue
      • 事件队列 event queue
    • 事件轮询 event loop (遍历读取回调队列中的回调函数执行)

      JS模型的运转流程

    • 执行初始化代码, 将事件回调函数交给对应模块管理
    • 当事件发生时, 管理模块会将回调函数及其数据添加到回调列队中
    • 只有当初始化代码执行完后(时间不定), 才会遍历读取执行回调队列中的回调函数


Web Workers API

  • 提供了js分线程的实现
  • 分线程主要用来处理长时间的工作且不占用主线程的空间和时间
1
2
3
4
Worker: 构造函数, 加载分线程执行的js文件
Worker.prototype.onmessage: 用于接收另一个线程的回调函数(当接收消息时,回调函数执行)
Worker.prototype.postMessage: 向另一个线程发送消息
分线程里的this不是window而是[object DedicatedWorkerGlobalScope]
  • 缺点:
    • worker内代码不能操作DOM(更新UI)
    • 不能跨域加载JS
    • 不是每个浏览器都支持这个新特性

分线程设计出来是为了处理长时间的工作. 不是为了更新界面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
  //创建Worker实例
var worker = new Worker('worker1.js');

//通过主线程向分线程的js发送消息(数据)
var msg = 'abc';
worker.postMessage(msg);
console.log('主线程向分线程发送数据 : ',msg);


//绑定接收返回消息的监听回调
worker.onmessage = function (e) {
var result = e.data;
console.log('主线程接收到分线程返回的数据 : ',result);
}


//1. 接收数据
var onmessage = function (e) {
var data = e.data;
console.log('分线程接收到主线程发送的数据 : ',data);

//2. 处理数据
data = data.toUpperCase();

//3. 返回数据
postMessage(data);
console.log('分线程向主线程返回数据 : ',data);

//alert not defined 分线程里不可以更新界面的
};

主线程向分线程发送数据: abc
分线程接收到主线程发送的数据: abc

分线程向主线程返回数据: ABC
主线程接收到分线程返回的数据: ABC

对象扩展 - 属性描述符

  • 对象所有的属性都具备了属性描述符
  • Object.getOwnPropertyDescriptor()方法来获取一个属性的对应描述符
属性的描述符 (元属性:对象属性的属性)
  • configurable:true 可配置
  • enumerable:true 可枚举
  • writable:true 可读写
Object.getOwnPropertyDescriptor() 获取属性对应描述符
  • 第一个参数: 对象
  • 第二个参数: 要获取描述符的对象属性
Object.defineProperty() 为对象新增定义或修改属性
  • 第一个参数: 对象
  • 第二个参数: 新定义或修改的属性名
  • 第三个参数: 配置对象(value指定属性值)
  • defineProperty的属性描述符的默认值都是false(可配置是否为true)
描述符之 writable
  • writable决定是否可以修改属性的值,即value的值
  • 当writable为false时,对应的属性的值是无法修改的
  • 非严格模式下继续修改会静默失败. 反之会报错(typeError)
描述符之 configurable
  • configurable 来决定属性是否可以配置
  • 当configurable为false时,对应的属性是无法重新定义和无法删除的。但该对象还可以继续添加属性的。
  • 不管是否为严格模式,configurable为false时进行重新定义或删除都会报错.
  • window上的configurable是false.所以不能删
  • 例外: configurable为false时, 只允许writable从true到false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//给对象定义属性(新增)
Object.defineProperty(obj,'age',{
value:50,
writable:true
});

//重新定义属性
Object.defineProperty(obj,'age',{
value:40,
writable:false
});

//继续添加属性 {name: "wukong", age: 40, sex: "男"}
Object.defineProperty(obj,'sex',{
value:'男',
});

//writable为false无法修改 则报错!!!
Object.defineProperty(obj,'age',{
value:58,
});
描述符之 enumerable
  • enumerable是否支持属性出现在对象属性的遍历中(for in循环)
  • 不可枚举的属性不会出现在 for..in 循环中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var obj = {};

Object.defineProperty(obj,'a',{
value:'a',
enumerable:true
});
Object.defineProperty(obj,'b',{
value:'a',
});
Object.defineProperty(obj,'c',{
value:'a',
});

for(item in obj){
console.log(item); //a
}
  • obj.propertyIsEnumerable() 判断属性是否可枚举 (参数为对象属性)
  • Object.keys() 获取可枚举的属性列表 (参数为对象)
  • Object.getOwnPropertyNames() 获取所有属性列表 (参数为对象)
  • 以上三个方法都不会遍历原型链
1
2
3
console.log(obj.propertyIsEnumerable('b')); //false
console.log(Object.keys(obj)); // ['a']
console.log(Object.getOwnPropertyNames(obj));//(3) ["a", "b", "c"]
描述符小概念
  • 属性描述符: 用来形容属性的属性.
  • 数据描述符: 具有writable和value的属性描述符的属性.本质是属性
  • 访问描述符: 具有set和get属性描述符的属性. 本质是属性

对象扩展 - 对象的不变性

  • 注意! JS中所有方法的创建都是浅不变性的,只会影响目标对象和它的直接属性
  • 深层次嵌套的影响不了,原型链中的更加影响不了
对象常量属性
  • 值不可修改,不可重新定义,不可删除就是常量属性
  • 常量属性跟对象无关
  • configurable和writable为false就是常量属性
禁止对象扩展属性
  • 调用 Object.preventExtensions(obj)
    • 参数: 要求禁止扩展的对象
  • 非严格模式下为对象添加新属性会静默失败.反之则会报错(typeError)
  • 注意: 之前的属性还是可以进行重新定义和删除的.属性值也可修改
密封对象
  • 调用 Object.seal(obj)
    • 参数: 要求被密封的对象
  • 密封后原有的属性不可进行重新定义和删除.但属性值可以修改
  • 密封对象只针对一层.如果有嵌套对象不会受影响
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var obj = {
name:'wukong',
wife:{
w1:'小a',
w2:'小b'
}
};

"use strict";
Object.seal(obj); //密封对象
delete obj.name; //无法删除

obj.name = 'bajie'; //可修改属性值
obj.wife.w1= 1; //嵌套对象不受影响
delete obj.wife.w2; //删除嵌套对象内的属性
console.log(obj); //name:"bajie"wife:{w1:1}
冻结对象
  • 调用 Object.freeze(obj)
    • 参数: 要求被冻结的对象
  • 冻结后禁止了对象扩展属性.原有属性不可重新定义和删除并且属性值也不可以修改

对象扩展 - 对象的访问描述符

  • 当给属性定义get和set时,这个属性会被定义为 访问描述符
  • set和get为属性描述符.可以定义方法
  • 当前属性的属性值是根据一个或多个其他属性计算得来时,属性动态查找或获取时调用get方法
  • 当其他属性根据当前属性值变化而变化时,监视当前属性时调用set方法 (参数传入value. set中不能有原生属性)
  • set和get方法里可以处理更多的逻辑.并且给属性创造更安全的环境
1
2
3
4
5
6
7
8
9
10
var obj = {
get a(){
return 1;
},
set a(val){
console.log('set');
}
};
obj.a = 2; //只认set
console.log(obj.a); //'set' 1
  • 当给属性定义value和writable时,这个属性会被定义为 数据描述符
  • 当有访问描述符时,JS会忽略value和writable特性. 这两对无法兼容
  • 因为有value和writable时属性称为数据描述符. 而有set和get时属性称为访问描述符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var obj = {};

Object.defineProperty(obj,'age',{
//value:1,
//writable:true, 冲突会报错

set : function (value) {
if (value<0){ //如果年龄小于0无意义则就等于0
console.log('set');
this._a_ = 0;
}else{
this._a_ = value;
}
},
get : function () {
return this._a_;
}
});
obj.age = -1; //调用set方法
console.log(obj.age); //'set' 0

对象扩展 - 存在性检查

  • 不能确定属性是否存在于对象中
  • in 操作符进行存在性检查 (会遍历原型链)
  • obj.hasOwnProperty(); 不会遍历原型链,只在对象中查找(参数是要确认存在性的属性名)

对象的扩展 - 对象属性的获取

前提:属性是数据描述符.且属性的writable为true
  • 先从对象的直接属性去找.找不到去原型链找 (原型链包括对象的直接属性)
  • 如果整条原型链上都没有就返回 undefined
1
2
3
4
5
6
7
8
9
Foo.prototype.age = 10;
function Foo() {
this.age = 8
}

var foo = new Foo(); //实例对象

console.log(foo.age);//8
console.log(Foo.prototype.age);//10
前提: 属性是访问描述符
  • 调用get方法获取
  • 如果get方法没有,则返回undefined
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
    function Foo() {}

var foo = new Foo();

Object.defineProperty(foo,'age',{
get:function () {
return this._age;
},
set:function (value) {
this._age = value
}
});
foo.age = 15;
console.log(foo.age); //15
````

---

### 对象的扩展 - 对象属性的设置
##### 前提I:属性直接存在于对象中不在原型链上
- 属性为数据描述符:
- writbale为true: 直接修改对象中的属性
- writbale为false: 静默失败或严格模式下报错
- 属性为访问描述符:
- 调用set方法 (属性设置只看set)

##### 前提II: 属性直接存在于对象中.也在原型链上
- 属性为数据描述符:
- 原型链中的属性不会被修改
- writbale为true: 直接修改对象中的属性
- writbale为false: 静默失败或严格模式下报错
- 属性为访问描述符:
- 调用set方法 (属性设置只看set)

##### 前提III:属性不直接存在于对象中也不在原型链上
- 属性为数据描述符:
- writbale为true: 在对象的直接属性中直接添加属性
- writbale为false: 静默失败或严格模式下报错
- 属性为访问描述符:
- 调用set方法 (属性设置只看set)

##### 前提IV: 属性不直接存在于对象中.但在原型链上
- 原型链上属性为数据描述符
- writbale为true: 在对象的直接属性中添加一个属性,我们称之为屏蔽属性
- writbale为false: 不会生成屏蔽属性(静默失败).严格模式下报错
- 原型链上属性为访问描述符
- 调用set方法,不会生成屏蔽属性. 具体看set方法的实现


##### 注意事项
- 属性是数据描述符在writable为false时. 有可能在做属性赋值操作时, 属性的值会赋值不上
- 对象中没有该属性. 原型链上有该属性,且属性为访问描述符.或属性是数据描述符且writable为false时,可能在做属性赋值操作时, 属性添加不到对象中

----------

#### 自定义 getElementsByClassName 兼容IE8
- 获取所有className并返回数组
- 功能:获取指定父节点下指定className下的指定节点
- 技术点: 正则表达式杠b (匹配独立单词两边的空白字符)

function getElementsByClassName(parentNode,className,targetName) {
var arr = [];

//找到父节点下的指定class

var allNodes = parentNode.getElementsByTagName(targetName);

//遍历所有的节点
for(var i=0;i<allNodes.length;i++){

//每一个节点里的class中是否有className
var reg = new RegExp(“\b” + className + “\b”);

if(reg.test(allNodes[i].className)){
arr.push(allNodes[i])
}
}
return arr //直接返回出一个数组
}

1
#### 自定义 addClass 方法

function addClass(node,className) {
//匹配className两边的空白字符
var reg = new RegExp(“\b” + className + “\b”);
//原来的class中没有就添加class. 有的话直接忽略
if(!reg.test(node.className)){ //属性的查找走原型链
node.className += (“ “+ className) //变量的查找 走的是作用域链
}
}

1
#### 自定义 removeClass 方法

function removeClass(node,className) {
//匹配className两边的空白字符
var reg = new RegExp(“\b” + className + “\b”,”g”);
//原来的class中有就删掉 (用空白字符替换)
if(reg.test(node.className)){
node.className = node.className.replace(reg,””);
if(/^\s+$/.test(node.className)){
node.removeAttribute(“class”)
}
}else{
node.removeAttribute(“class”)
}
}


----------
  • js
  • 面试系列
  • js
JS面试汇总Ⅲ
JS面试汇总Ⅰ
  1. 1. 浏览器内核
  • 定时器 / JS线程 / 事件循环模型问题
  • JS引擎代码
    1. 0.1. JS引擎代码的分类
    2. 0.2. JS引擎执行代码的基本流程
    3. 0.3. JS模型的重要组成部分
    4. 0.4. JS模型的运转流程
  • Web Workers API
    1. 1. 对象扩展 - 属性描述符
      1. 1.0.1. 属性的描述符 (元属性:对象属性的属性)
      2. 1.0.2. Object.getOwnPropertyDescriptor() 获取属性对应描述符
      3. 1.0.3. Object.defineProperty() 为对象新增定义或修改属性
      4. 1.0.4. 描述符之 writable
      5. 1.0.5. 描述符之 configurable
      6. 1.0.6. 描述符之 enumerable
      7. 1.0.7. 描述符小概念
  • 2. 对象扩展 - 对象的不变性
    1. 2.0.1. 对象常量属性
    2. 2.0.2. 禁止对象扩展属性
    3. 2.0.3. 密封对象
    4. 2.0.4. 冻结对象
  • 3. 对象扩展 - 对象的访问描述符
  • 4. 对象扩展 - 存在性检查
  • 5. 对象的扩展 - 对象属性的获取
    1. 5.0.1. 前提:属性是数据描述符.且属性的writable为true
    2. 5.0.2. 前提: 属性是访问描述符
  • © 2019 Incheon
    Hexo Theme Yilia by Litten
    • 所有文章
    • 友链
    • 关于我

    tag:

    • js
    • 面试系列
    • css
    • other
    • html

      缺失模块。
      1、请确保node版本大于6.2
      2、在博客根目录(注意不是yilia根目录)执行以下命令:
      npm i hexo-generator-json-content --save

      3、在根目录_config.yml里添加配置:

        jsonContent:
          meta: false
          pages: false
          posts:
            title: true
            date: true
            path: true
            text: false
            raw: false
            content: false
            slug: false
            updated: false
            comments: false
            link: false
            permalink: false
            excerpt: false
            categories: false
            tags: true
      

    • denyu95
    很惭愧

    只做了一点微小的工作
    谢谢大家