# 13.知识点整理③
# 1.简述关系型数据库和非关系型数据库
https://www.jianshu.com/p/fd7b422d5f93
# 2.100道常见的面试题
https://blog.csdn.net/zjw_python/article/details/82078702
# 3.关于对象
当将非字符串类型作为对象的key
时,都会调用toString
方法;而对象调用toString
方法都会变成[object, Object]
注意了,第一个o
小写,第二个O
大写
# 4.浅克隆和深克隆
浅克隆:
克隆出来的obj2
里面的引用类型属性,的引用地址并没有发生改变,仍然指向同一块堆内存,所以即使克隆了obj2
但是一旦obj2
的引用类型属性发生改变,原来的obj
对象相应的应用类型属性也会发生变化;
let obj ={
a:100,
b: [10, 20, 30],
c: {
x: 10,
}
}
//浅拷贝
let obj2 = {}
for(let key in obj){
if(!obj.hasOwnProperty(key)) {
break;
}else{
obj2[key] = obj[key]
}
}
//也可以使用ES6的扩展运算符,直接进行浅克隆
let obj3 = {...obj};
//深克隆
//1.可以先把obj对象转换为字符串
let obj2 = JSON.stringify(obj)
//2.再将字符串变回对象
JSON.parse(obj2)
//合起来写为
let obj2 = JSON.parse(JSON.stringify(obj))
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
但是,JSON.stringify(obj)
在解析对象中的函数和正则表达式属性时会出错,但是对象中很少会出现这些属性
所以这是一种常用的深拷贝方法;
更加完整的写法为:
function deepClone(obj){
//过滤特殊情况->obj[key]不为对象时
if(obj === null) return null
if(typeof obj !== "object")return obj
//判断是否为正则
if(obj instanceof RegExp){
return new RegExp(obj)
}
if(obj instanceof Date){
return new Date(obj)
}
//不直接创建空对象->使克隆的结果和之前保持相同的所属类
let newObj = new obj.constructor;
for(let key in obj){
if(obj.hasOwnProperty(key)){
//此处采用递归调用,直到boj对象的key不再是一个对象为止
newObj[key] = deepClone(obj[key])
}
}
//最后返回克隆后的newObj
return newObj
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 5.实现call方法
对于实现以下几个函数,可以从几个方面思考
- 不传入第一个参数,那么默认为
window
- 改变了
this
指向,让新的对象可以执行该函数。那么思路是否可以变成给新的对象添加一个函数,然后在执行完以后删除?
Function.prototype.myCall = function (context) {
var context = context || window
// 给 context 添加一个属性
// getValue.call(a, 'yck', '24') => a.fn = getValue
context.fn = this
// 将 context 后面的参数取出来;由于arguments对象是一个伪数组,所以要手动加[],转换为数组才可以调用数组的slice方法
var args = [...arguments].slice(1)
// getValue.call(a, 'yck', '24') => a.fn('yck', '24')
var result = context.fn(...args)
// 删除 fn
delete context.fn
return result
}
2
3
4
5
6
7
8
9
10
11
12
13
# 6.实现apply方法
Function.prototype.myApply = function (context) {
var context = context || window
context.fn = this
var result
// 需要判断是否存储第二个参数
// 如果存在,就将第二个参数展开
if (arguments[1]) {
result = context.fn(...arguments[1])//这里不懂可以采用call方法的处理方式
/*
var args = [...arguments].slice(1)
result = context.fn(...args)
*/
} else {
result = context.fn()
}
delete context.fn
return result
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 7.实现bind方法
Function.prototype.myBind = function (context) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
var _this = this
var args = [...arguments].slice(1)
// 返回一个函数
return function F() {
// 因为返回了一个函数,我们可以 new F(),所以需要判断
if (this instanceof F) {
return new _this(...args, ...arguments)
}
return _this.apply(context, args.concat(...arguments))
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 8.Vue2中的双向数据绑定
使用Object.defineProperty
实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
姓名:<span id="spanName"></span>
<br>
<input type="text" id="inputName">
<script>
//Vue2.0采用的是Object.defineProperty来实现双向数据绑定
let obj = {
name: ""
};
//为了不影响原对象,需要克隆一个新对象
//使用深拷贝靠谱一点
let newObj = JSON.parse(JSON.stringify(obj))
Object.defineProperty(obj, 'name', {
get(){
return newObj.name
},
set(val){
if(val === obj.name)return
newObj.name = val
observer()
}
})
//创建观察函数=>改变DOM元素
function observer(){
spanName.innerHTML = obj.name
inputName.value = obj.name
}
//设置定时器1s更新一次
setTimeout(() => {
obj.name = "张三的传奇一生 "
}, 1000);
//监听input输入框的变化
inputName.oninput = function(){
obj.name = this.value
}
</script>
</body>
</html>
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
# 9.Vue3中的双向数据绑定
使用Object.defineProperty
实现双向数据绑定的缺点:
- 需要对原始数据进行克隆;
- 需要分别给对象中的每一个属性设置监听;
使用proxy
进行实现:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
姓名:<span id="spanName"></span>
<br>
<input type="text" id="inputName">
<script>
let obj = {}
obj = new Proxy(obj, {
get(target, prop){
return target[prop]
},
set(target, prop, value){
target[prop] = value
observer()
}
})
//同样封装观察函数,更新DOM元素
function observer(){
spanName.innerHTML = obj.name
inputName.value = obj.name
}
setTimeout(() => {
obj.name = "王二麻子的哲学思想"
}, 1000);
//监听input的输入
inputName.oninput = function(){
obj.name = this.value
}
</script>
</body>
</html>
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
# 10.字符串->
数组
**方法一:**使用扩展运算符...
:
[...'hello']
// [ "h", "e", "l", "l", "o" ]
2
同时,也可以将各种伪数组(类似数组的对象),通过扩展运算符转换为真正的数组:
let nodeList = document.querySelectorAll('div');
let array = [...nodeList];
2
**注意:**前提是定义了遍历器的对象;
没有定义遍历器的对象可以通过Array.from
,转换为真正的数组;
**方法二:**使用Array.from
方法;
# 11.文档注释:
/**
*这样写的注释后,在调用test()时会显示这里的注释,告知别人该函数的用途
*/
function test(){
}
test()
2
3
4
5
6
7
# 12.== 与 === 的区别
==:
可以转换的情况下,先转换为同一数据类型再比较值;
===:
不会进行类型转换,先比较类型是否相同,再比较值;两者都相等才返回true
练习:
注意了在null = undefined
时才为true
NAN
不等于任何数,包括自身;
引用类型比较引用的地址;
# 13.typeof 和 instanceof 的区别
typeof
:意思为类型,检测数据类型,返回一个小写字母的类型字符串,只需要一个操作数简单数据类型(number
、string
、boolean
、undefined
、object
)或者函数。其中null
属于object
顺便一提
NAN
属于number
类型
typeof 123
"number"
typeof NaN
"number"
typeof "abc"
"string"
typeof 'acb'
"string"
hello = function(){}
typeof hello
"function"
typeof Symbol
"function"
typeof new Function()
"function"
//复杂数据类型
typeof []
"object"
typeof {}
"object"
typeof null
"object"
typeof new Array()
"object"
typeof new Number()
"object"
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
instanceof
:意思为实例,检测对象之间的关联性(判断你是不是我的实例)。需要两个操作数。
a = new Number(888);
a instanceof Number
true
b = new String("abc")
b instanceof String
true
c = new Object()
c instanceof Object
true
2
3
4
5
6
7
8
9
10
11
但是,instanceof 左边的操作数一定要是引用类型,否则报错。因为instanceof表示:...的实例是...
"coder" instanceof String
false
888 instanceof Number
false
true instanceof Boolean
false
2
3
4
5
6
7
8
引用类型举例:
[1, 2] instanceof Array
true
let coder = {name: "yxz", age: 18}
coder instanceof Object
true
new Number(888) instanceof Number
true
2
3
4
5
6
7
8
9
总结:
typeof | instanceof | |
---|---|---|
作用 | 检测数据类型 | 检测对象之间的关联性 |
返回 | 小写字母字符串 | 布尔值 |
操作数 | 简单数据类型、函数或对象 | 左边必须是引用类型,右边必须是函数 |
操作数数量 | 1个 | 2个 |
# 14.document.write() 与 innerHTML 的区别
document.write():
引入代码时:只有在页面加载完后再引入才会覆盖原来内容。
- 插入
HTML
标签
- 插入函数
innerHTML:
总结:
document.write() | innerHTML | |
---|---|---|
类型 | document 对象中的方法 | 存在于Element 对象中的属性 |
插入位置 | 脚本元素script 的位置 | 指定的元素内 |
拼接方法 | 多次调用 | 利用+= |
覆盖问题 | 文档解析完再调用会覆盖,否则不会 | 直接调用会覆盖原内容 |
插入内容 | HTML 标签和函数 | HTML 标签和函数 |
# 15.apply和call
call和apply的基本用法
- apply:
apply(thisObj,[argArray])
,参数必须是数组,调用一个对象的方法,用另一个对象替换当前对象。例如:B.apply(A,arguments);即A对象应用B对象的方法; - call:
call(thisObj,Object)
,参数必须是对象,调用一个对象的一个方法,用另一个对象替换当期对象。例如B.call(A,args1,args2);即A对象调用B对象的方法;
即 call 的作用无非就是当执行一个方法的时候希望能够使用另一个对象来作为作用域对象而已,简单来说就是当我执行 A 方法的时候,希望通过传入参数的形式将一个对象 B 传进去,用以将 A 方法的作用域对象替换为对象 B
二者不传入参数时,都指向window;
apply基本用法
function add(a, b){
return a + b
}
function sub(a, b){
return a - b
}
//apply方法以数组的形式传入参数
let a1 = add.apply(sub, [8, 4])//12
let a2 = sub.apply(add, [8, 4])//4
2
3
4
5
6
7
8
9
代码中a1的值为12是因为根据apply的定义sub调用了add方法;同理a2中add调用了sub方法
call基本用法
let name = '张三'
let age = 28
let obj = {
name: '李四',
myfunction: function(){
console.log(this.name + this.age)
}
}
let db = {
name: "王五",
age: 30
}
obj.myfunction()//李四undefined
obj.myfunction.call()//张三28,此处call中没传参数,默认指向window,由于call方法,使window对象可以调用obj对象的myfunction方法;由于在window的作用域下调用obj的myfunction方法,所以该方法中的this指向window,所以输出全局的name和age属性
obj.myfunction.call(db)//王五30,由于call方法的作用,使对象db可以调用对象obj的myfunction方法;由于在db的作用域中db调用obj的myfunction方法,所以该方法中的this指向db
/*即使用call参数db的作用域来调用,调用call方法的obj对象的myfunction方法;*/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
继承
function A(){
this.name = 'kevin'
this.showname = function(){
console.log(this.name)
}
}
function B(){
this.name = 'kobe'
A.call(this)//这里的this指向(代表)B,由于call方法,相当于B调用A里面的方法
}
let B = new B()
B.showname()//由于call方法,这里的B调用的是A里面的showname方法,所以输出kevin;因为通过call方法,B中的name属性相对于A中的name属性位于外层,所以先在A中寻找name
2
3
4
5
6
7
8
9
10
11
12
13
14
# 16.原型对象prototype
被创建出来的对象里会有一个_proto_
属性,他们的关系是这样的:
可以看到每个对象的实例a,b,c(儿子)都有一个_proto_
属性,原来是[[prototype]]
只不过被浏览器解析为了_proto_
;该属性指向爷爷;
每个对象String,Number,Object
(父亲)都有一个prototype
属性指向各自的原型(爷爷);即:
b._proto_ = Number.prototype
而每个原型对象都有一个constructor
构造函数指向相应的对象(父亲);并且所有原型对象的_proto_
都指向Object
原型;
经典面试题:new一个新对象的过程
书上写的是四步:
- 创建一个新对象;
- 将构造函数的作用域赋值给新对象(因此this就指向这个新对象);
- 执行构造函数中的代码(为这个新对象添加属性);
- 返回新对象;
关于第5步我们来看一看下面的例子:
可以看到,构造函数可以自动返回新对象;而不同函数需要手动返回;如上面的return this
这个this
就是新对象。
# 17.原型链
经典面试题之,如何判断一个变量是数组对象
# 18.关于Map和Set
https://blog.csdn.net/c_kite/article/details/72819221
← 12.知识点整理② 14.宏任务与微任务 →