考点+面试题
一、
三、面向对象,构造函数、原型与原型链
1. 对象
定义:无序属性的集合,其属性可以包含基本值,对象或者函数。
创建对象的方法:- new 方法
1
var obj =new Object()
- 字面量
1
var obj = {}
- 根据原型创建对象
1
2let obj=Object.create(Object.prototype)
let obj=Object.create(null)
create 函数的实现
1 | const create=(proto)=>{ |
工厂模式
就是提供一个生成一类对象的函数,通过这个函数返回一个对象。
解决代码重复代码1
2
3
4
5
6
7
8
9
10
11var createPerson = function (name, age) {
var o = new Object();
o.name = name;
o.age = age;
o.getName = function () {
return this.name
}
return o;
}
var perTom = createPerson('Tom', 20);
var perJake = createPerson('Jake', 22);缺点:
无法识别对象实例
相同的对象的方法会复制很多份,造成浪费
2. 构造函数
目的:创建一个自定义类。
使用 new 关键字调用1
2
3
4
5function demo() {
console.log(this)
}
demo() //window
new demo() //demonew 方法实现,就是一个高阶函数
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
32var Person = function (name, age) {
this.name = name;
this.age = age;
this.getName = function () {
return this.name
}
}
//以参数形式传入构造函数
function New(func) {
//声明一个中间对象,为最终返回的实例
var res = {};
if (func.prototype !== null) {
//将实例的原型指向构造函数的原型
res.__proto__ = func.prototype;
}
//通过apply将构造函数内部的this指向res,即实例对象
var ret = func.apply(res, Array.prototype.slice.call(arguments, 1));
console.log({
ret,
res
})
//当我们在构造函数中明确指定了返回对象时,那new的执行结果就是该返回对象
if ((typeof ret === 'object' || typeof ret === 'function') && ret !== null) {
return ret
}
//如果没有明确指定返回对象,则默认返回res,
return res
}
var p1 = New(Person, 'tom', 20);
console.log(p1.getName()); //tom
// 判断实例的类型
console.log(p1 instanceof Person); // truenew 关键字执行过程:
- 声明一个中间对象。
- 将该中间对象的原型指向构造函数的原型
- 将构造函数的 this,指向中间对象
- 如果构造函数存在返回值,返回该返回值,否则返回中间对象
3. 原型
谈谈你对 JS 原型和原型链的理解:
原型对象为其他对象提供共享的方法与属性的对象。在创建对象的时,每个对象都包含一个隐式引用指向它的原型对象或者null
。
原型也是对象,它也有自己的原型,这个就构成了原型链。每个函数都可以是构造函数,每个对象都可以是原型对象。函数 prototype 指向原型,实例
__proto__
指向原型1
2
3
4
5
6
7
8
9
10
11
12//构造函数
function Person(name, age) {
this.name = name;
this.age = age;
}
//通过prototype属性,将方法挂载带原型对象上
Person.prototype.getName= function(){
return this.name
}
var p1 = new Person('tim', 10);
var p2 = new Person('jak', 22);
console.log(p1.getName === p2.getName) //true
当我们访问实例对象中的属性或方法时,会优先访问实例对象自身的属性和方法。
判断对象是否拥有某个属性/放发,无论该属性、方法存在于实例对象还是原型对象。1
'name' in p1 //true
补充:in 的这种特性最常用的场景之一,就是判断当前页面是否在移动端打开。
1
let isMoblie = 'ontouchstart' in document
更简单的原型写法:
1
2
3
4
5
6Person.prototype = {
constructor:Person, // 显示constructor的指向
getName:function(){},
getAge:function(){},
sayHello:function(){}
}4. 原型链
add 是 Function 对象的实例。而 Function 的原型对象时 Object 的实例。这构成了一条原型链。原型链有什么作用
存储多个对象的共有属性和方法。我们在访问对象的属性时,实际是查找的整个原型链上的属性,返回第一个找到的对应属性值,过程:首先查找当前对象上是否包含该属性,如果包含就返回返回,否则查找当前对象的原型是否包含,。。。,如果都不包含返回undefined
。实现一个在原型链上查找属性的方法
1 | const lookupProperty = (object, propertyName) => { |
5. 继承
如何实现继承- 显示原型继承:开发者操作的继承
Object.setPropertyOf(obj1,obj2)
将obj2
设置为obj1
的原型Object.create(obj)
以obj
为原型创建新对象
- 隐式原型继承
使用new
关键字实例化时,会自动继承constructor
的prototype
对象,作为实例的原型。
内置构造函数,如:Object, Array, Boolean, String, Number,Function。
如何实现超类的继承(Class 的继承)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19const inherit=(SuperConstructor,properties)=>{
let {constructor}=properties;
let SubConstructor=function(...args){
SuperConstructor.call(this,...args);
constructor.call(this,...args);
}
SubConstructor.prototype={
...properties,
constructor:SubConstructor
}
Object.setPrototypeOf(
SubConstructor.prototype,
SuperConstructor.prototype
)
return SuperConstructor
}第一步,编写新的
constructor
,将两个constructor
通过apply/call
的方式调用,合并他们的属性,超类优先调用
第二步,为新的构造函数的原型对象设置为子类的原型,并且修改constructor
属性
第三步,设置新的原型对象继承超类的原型对象。
原型中比较少人知道的特性
对象访问__proto__
属性,其实就是调用Object.prototype
里面的访问时属性get
1 | let obj=new Object(); |
语法糖-对象字面量let obj={a:1,b:2}
通过字面量创的新对象,有两层隐式行为
- 通过
new Object()
或new Array()
等内置构造函数创建新对象 - 隐式进行原型继承
1 | //构造函数的继承 |
6. 更好的继承
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
26function Student(name, age, grade) {
//构造函数继承
Person.call(this, name, age);
this.grade = grade;
}
function create(proto, options) {
//创建一个空对象
var tmp = {};
//让新的空对象成为父类对象的实例
tmp.__proto__ = proto;
//传入的方法都挂载到新对象上,新的对象作为子类对象的原型
Object.defineProperties(tmp, options);
return tmp
}
//原型继承
Student.prototype = create(Person.prototype, {
//重新指定构造函数
constructor: Student,
getGrade: {
value: function () {
return this.grade
}
}
})在 ECMAScript5 中直接提供了一个 Object.create 方法来完成我们上面自己封装的 create 的功能。因此我们可以直接使用 Object.create.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15//原型继承
Student.prototype = Object.create(Person.prototype, {
//重新指定构造函数
constructor: {
value: Student,
enumerable: false,
writable: true,
configurable: true
},
getGrade: {
value: function () {
return this.grade
}
}
})7. 对象属性
数据属性包括:- value
- writable
- configurable
- enumerable
访问器属性包括:
- configurable
- enumerable
- get
- set
一个属性只能设为访问器属性或数据属性,所以不能同时设置 value、writable 和 get、set
1
2
3
4
5
6
7
8let a = {
name: 'ddd'
}
console.log(Object.getOwnPropertyDescriptor(a, 'name'))
Object.defineProperty(a, 'age', {
value: 13
})
console.log(Object.getOwnPropertyDescriptor(a, 'age'))
注意,直接使用点(.)操作符给对象设置属性,configurable,enumerable,writable 都为 true。
Object.defineProperties(obj,{}),这些属性默认为 false- configurable:表示该属性是否能被 delete 删除。
当值为 false 时,不能删除,其他特性也不能改变。 - enumerable:是否能枚举。能否被 for-in 遍历,Object.keys(),。
- writable:是否能修改值,
- value:属性值,默认 undefined
- get:对象访问属性时,get 被调用。
set:设置属性值的时候调用。
使用 Object.getOwnPropertyDescriptor 方法读取某一个属性的特性值。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17let student = {
name: 'ddd'
}
var initValue = 11;
Object.defineProperty(student, 'age', {
get: function () {
console.log('getter');
return initValue
},
set: function (value) {
console.log('setter');
initValue = value
}
})
console.log(student.age) //getter 11
student.age = 12 //setter
console.log(student.age) //getter 12
四、事件循环
JS 单线程,只有一个事件循环。
函数调用栈-函数执行
任务队列(task queue)-执行除函数外的代码,先进先出(FIFO),可以有多个。
- 宏任务(macro-task) 、task
任务源:script(整体代码)、setTimeout、setInterval、setImmediate、I/O、UI rendering - 微任务(micro-task)、jobs
任务源:process.nextTick、Promise、Object.observe(已废弃)、MutationObserver(html5 新特性)
1 | //setTimeout中的回调函数才是进入任务队列的任务 |
setTimeout 作为一个任务分发器,这个函数会立即执行,而它所要分发的任务,也就是它的第一个参数,才是延迟执行。
事件循环的顺序:
- 从 script 开始第一次循环
- 全局上下文进入函数调用栈
- 调用函数,创建函数执行上下文,执行完成,出栈
- 调用栈清空(只剩全局),就执行 micro-task
- micro-task 执行完后,再次执行 macro-task
…….
setTime 队列
promise 任务队列
setTime Promise Async Ajax 请求
1. 同步 vs 异步
- 同步 顺序执行
- 异步 并行处理方式,不必等待一个程序执行完成,就可以执行其他任务
六、DOM 操作与 BOM 操作
1. DOM 属性和操作
DOM 操作2. DOM 事件
DOM 事件4.事件代理(事件委托)
由于事件会在冒泡阶段向上传输到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多子元素的事件。
优点:- 使代码简洁
- 减少浏览器的内存占用
- 删除子元素的时候不用考虑删除绑定事件
应用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<ul id='list'>
<li data-index='1'>item1</li>
<li data-index='2'>item2</li>
<li data-index='3'>item3</li>
<li data-index='4'>item4</li>
</ul>
let list = document.getElementById('list');
list.addEventListener('click', e => {
var event = e || window.event; //兼容
var target = event.target || event.srcElement;
//判断是否匹配目标元素
if (target.nodeName.toLocaleLowerCase() === 'li') {
console.log(target.dataset.index)
}
})5. BOM 操作
浏览器对象模型,是浏览器本身的一些信息的设置和获取。window.screen 对象:屏幕的信息
屏幕宽高1
2screen.width
screen.heightwindow.location 对象:获取当前页面的地址(url),把浏览器重定向到新的页面
1
2
3location.href //网址https://juejin.im/timeline/frontend?a=10&b=10#some
location.protocol //协议https
location.pathname //路径//juejin.im/timeline/frontend- window.history 对象:浏览器的前进后退等
1
2history.back() //后退
history.forward() //前进- window.navigator 对象:获取浏览器信息,是否是移动端
1
2
3var ua = navigator.userAgent
var isChrome = ua.indexOf('Chrome') > -1 ? true : false;
console.log(isChrome)[window 对象](https://dorisfeng.github.io/2018/12/21/window%E5%AF%B9%
location.search //参数?a=10&b=10
location.hash //#some1
2
E8%B1%A1/)