1、DOM相关操作
1、如何修改DOM
通过innerhtml修改dom:
var p = document.getElementById('p-id');
// 设置文本为abc:
p.innerHTML = 'ABC'; // <p id="p-id">ABC</p>
// 设置HTML:
p.innerHTML = '<span style="color:red">RED</span>';
特点:innerhtml可以在DOM结点内部插入文本,也可插入DOM结点;
通过innertext修改dom:
var p = document.getElementById('p-id');
// 设置文本:
p.innerText = '<script>alert("Hi")</script>';
// HTML被自动编码,无法设置一个<script>节点:
特点:innertext只会在DOM结点内插入文本,不可以插入DOM结点
2、如何插入DOM
通过appendchild修改dom
var list = document.getElementById('list'),
var haskell = document.createElement('p');
haskell.id = 'haskell';
haskell.innerText = 'Haskell';
list.appendChild(haskell);
通过insertbefore修改dom:
var list = document.getElementById('list'),
var ref = document.getElementById('python'),
var haskell = document.createElement('p');
haskell.id = 'haskell';
haskell.innerText = 'Haskell';
list.insertBefore(haskell, ref);
修改后的html结构如下:
<div id="list">
<p id="java">Java</p>
<p id="haskell">Haskell</p>
<p id="python">Python</p>
<p id="scheme">Scheme</p>
</div>
3、删除一个DOM
拿到该节点,然后通过parentElement拿到父节点,然后通过removeChild删除孩子节点:
// 拿到待删除节点:
var self = document.getElementById('to-be-removed');
// 拿到父节点:
var parent = self.parentElement;
// 删除:
var removed = parent.removeChild(self);
removed === self; // true
2、垃圾回收机制
1、引用计数
对象每次被引用,那么次数+1。引用它的变量指向其他对象,那么它的引用次数-1.当引用次数等于0,回收该对象。注意对象本身不一定需要被引用,只要对象的某个属性被引用,该对象就不会被回收。
坏处:循环引用,函数结束后本应该销毁所有内部对象,但是他们互相引用:
function f(){
var o = {};
var o2 = {};
o.a = o2; // o 引用 o2
o2.a = o; // o2 引用 o
return;
}
f();
2、标记清除法
垃圾收集器先给所有对象加上标记,然后遍历一遍所有变量,如果发现该变量引用了某个对象,那么被引用的对象就取消标记。最终剩下的仍然被标记的对象就是需要被回收的对象。
3、深拷贝与浅拷贝
1、深拷贝与浅拷贝的概念
浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
深拷贝的区别在于,对于引用类型的属性,不能直接拷贝地址,需要开辟一块新的内存,递归地进行深拷贝。
2、能不能说赋值操作就是浅拷贝
不能。现在有两个Student对象,小明和小红。如果直接把小明赋值给小红,小明的age一旦变化,小红的age也会变化。这叫做赋值操作。
如果把小明进行浅拷贝,然后赋值给小红。我们不管修改小明的age还是小红的age,都不影响对方的age。
3、浅拷贝的方法
assign():
let obj1 = { person: {name: "kobe", age: 41},sports:'basketball' };
let obj2 = Object.assign({}, obj1);
obj2.person.name = "wade";
obj2.sports = 'football'
console.log(obj1); // { person: { name: 'wade', age: 41 }, sports: 'basketball' }
展开运算符:
let obj1 = { name: 'Kobe', address:{x:100,y:100}}
let obj2= {... obj1}
obj1.address.x = 200;
obj1.name = 'wade'
console.log('obj2',obj2) // obj2 { name: 'Kobe', address: { x: 200, y: 100 } }
对于数组,可以用concat:
let arr = [1, 3, {
username: 'kobe'
}];
let arr2 = arr.concat();
arr2[2].username = 'wade';
console.log(arr); //[ 1, 3, { username: 'wade' } ]
对于数组,可以使用slice():
let arr = [1, 3, {
username: ' kobe'
}];
let arr3 = arr.slice();
arr3[2].username = 'wade'
console.log(arr); // [ 1, 3, { username: 'wade' } ]
4、深拷贝的方法
先JSON.stringfy(),然后JSON.parse():
let arr = [1, 3, {
username: ' kobe'
}];
let arr4 = JSON.parse(JSON.stringify(arr));
arr4[2].username = 'duncan';
console.log(arr, arr4)
缺点:不能处理函数
手写深拷贝,背下来:
function deepClone(obj, hash = new WeakMap()) {
if (obj === null) return obj; // 如果是null或者undefined我就不进行拷贝操作
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
// 可能是对象或者普通的值 如果是函数的话是不需要深拷贝
if (typeof obj !== "object") return obj;
// 是对象的话就要进行深拷贝
if (hash.get(obj)) return hash.get(obj);
let cloneObj = new obj.constructor();
// 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
hash.set(obj, cloneObj);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 实现一个递归拷贝
cloneObj[key] = deepClone(obj[key], hash);
}
}
return cloneObj;
}
let obj = { name: 1, address: { x: 100 } };
obj.o = obj; // 对象存在循环引用的情况
let d = deepClone(obj);
obj.address.x = 200;
console.log(d);
5、img中alt和title的区别
alt是图片显示不出来的时候提示文字。title是鼠标画上去的时候提示文字。
6、margin中百分比的问题
首先记住margin的顺序是上右下左。它和translate相反,translate第一个值表示x轴,第二个值表示y轴。
<div id="demo">
<p>恩,注意看我所在的位置。</p>
</div>
<style>
#demo{
width: 1000px;
height: 600px;
}
#demo p{
margin: 10% 5%;
}
</style>
这段代码的10%和5%都是指的是父元素宽的比例,因此,上下100px,左右50px。
7、new的实现原理
1、创建一个空对象obj
2、obj.__proto__(原型对象)指向构造函数的prototype
3、执行构造函数中的代码。构造函数中所用到的所有this都指向刚刚创建的对象。这一步主要是获取私有属性
4、若无返回值,那么返回刚刚创建的对象。如果写了返回值,并且是个对象,那么返回指定的对象。
8、script标签阻塞HTML页面的解析
1、什么情况下会阻塞
浏览器解析HTML页面的时候,如果发现<script>标签,那么停止解析HTML页面。下载脚本,并执行完毕脚本之后才会继续解析HTML页面。
解决方法:script标签放在结尾。如此一来,不仅能解决这个问题,还能保障script脚本想要获取DOM元素的时候不会拿到null。
2、defer 属性
<script src="a.js" defer></script>
<script src="b.js" defer></script>
这样一来,浏览器一边解析HTML,一边下载脚本(但是不执行)。等到HTML解析完毕之后,才会按顺序执行脚本。
3、async属性
<script src="a.js" async></script>
<script src="b.js" async></script>
HTML解析页面,发现script,一遍解析一遍下载(并行进行)。谁先下载完毕,停止HTML解析,执行刚刚下载好的script。执行完毕之后,继续解析HTML页面
9、回流和重绘的发生时期
1、回流的发生时期
- 页面首次渲染
- 浏览器窗口大小发生变化
- 元素的宽和高发生变化
- 元素的位置发生变化
- 添加或者删除元素
2、重绘的发生时期
- 颜色变化
- Visibility: hidden visibile
- Transform:translate
10、箭头函数this指向问题
第一步:找到箭头函数定义的位置;
第二步:箭头函数的上一层如果仍然是个函数(暂且称为Parent函数),那么this就是Parent在运行时绑定的对象。 如果箭头函数的上一层不是函数,那么箭头函数的this就是window。
网上的定义:
箭头函数体内的this对象,就是定义该函数时所在的作用域指向的对象,而不是使用时所在的作用域指向的对象。
我的理解:箭头函数必定存在于一个作用域,要么是全局作用域(此时this指向window),要么是函数创造的局部作用域(此时this指向parent函数运行时绑定的对象)。
11、作用域
function foo() {
console.log(a);
a = 1;}
foo(); // ???
function bar() {
a = 1;
console.log(a);}bar(); // ???
}
第一段会报错:Uncaught ReferenceError: a is not defined。
第二段会打印:1。
当没有用var 声明a时,相当于给window加了一个a属性,其值为1
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f();
}
checkscope();
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f;
}
checkscope()();
打印结果都是local scope。秘诀,永远找函数定义的位置。
打印结果是undefined。即使在return后面定义了变量,在创建该作用域对应的AO的时候,仍然会给Active Object加上a属性,并且初始化为undefined。
12 连续赋值
var a = {n: 1};
var b = a;
a.x = a = {n: 2};
alert(a.x); // --> undefined
alert(b.x); // --> {n: 2}
先解析左边的值,然后再进行赋值操作。因此a的指向不管如何变化,在赋值之前,a.x已经被解析完成了,它永远指向最初的堆内存,因此修改的是初始的堆内存中的x成员。
参考资料:segmentfault.com/a/119000000…
14 Promise
1 promise和try catch的用法
promise抛出错误,用catch捕获错误:该Promise在2秒后抛出错误,由于try内部的代码被await阻塞了,因此会等待2秒。2秒后try内部抛出错误,则不会执行后面代码(也就是console.log('走完try')不会执行)。直接通过catch捕获错误。
let myPro = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('2s后进入reject 走');
reject();
}, 2000);
});
const test = async () => {
try {
await myPro.then(then => {
console.log('走到then回掉', then);
});
console.log('走完try');
} catch {
console.log('走到catch');
}
};
test();
打印结果:
2s后进入reject 走
走到catch
如果promise本身就捕获了错误,还会走到外部的catch吗?then方法的第二个参数是用来捕获错误。2秒后promise抛出错误,但是该错误被promise自己的回调捕获(then方法的第二个参数)。因此await顺利执行成功,在try内部没有抛出错误。
let myPro = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('2s后进入reject 走');
reject('rej');
}, 2000);
});
const test = async () => {
try {
await myPro.then(
then => {
console.log('走到then回掉', then);
},
err => {
console.log('走到err', err);
},
);
console.log('走完try');
} catch {
console.log('走到catch');
}
};
test();
2s后进入reject 走
index.tsx:65 走到err rej
index.tsx:68 走完try
同理,如果then不传第二个参数,而是用catch捕获,效果是一样。try也能顺利执行完毕,不会走到catch
let myPro = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('2s后进入reject 走');
reject('rej');
}, 2000);
});
const test = async () => {
try {
await myPro
.then(then => {
console.log('走到then回掉', then);
})
.catch(err => {
console.log('不用then,我用catch捕获 走', err);
});
console.log('走完try');
} catch {
console.log('走到catch');
}
};
test();
2s后进入reject 走
index.tsx:65 不用then,我用catch捕获 走 rej
index.tsx:67 走完try
2 Promise.resolve()和Promise.reject()
let myPro = Promise.resolve()
等同于
let myPro = new Promise((resolve,reject) => {
resolve();
}
let myPro = Promise.reject()
等同于
let myPro = new Promise((resolve,reject) => {
reject();
}
基于以上结论,把以上代码全部替换为Promise
如果自身不捕获错误,仍然会被外部catch捕获
const test = async () => {
try {
await Promise.reject();
console.log('走完try');
} catch {
console.log('走到catch');
}
};
走到catch
如果错误被Promise自身捕获,则可以顺利执行try,不会走到catch。
const test = async () => {
try {
await Promise.reject().then(
() => {
console.log('我是then');
},
err => {
console.log('我是catch');
},
);
console.log('走完try');
} catch {
console.log('走到catch');
}
};
我是catch
index.tsx:65 走完try
参考资料:www.imooc.com/qadetail/18…
3 利用Promise特性,来实现按钮的阻塞
点击按钮后,触发回调函数(decode)
const decode = async () => {
try {
//await会一直停留在Modal上,若不关闭,也不点确认,就一直停在这
await privacy.showModal();
} catch (e) {
//若关闭Modal,直接返回,不会走后续逻辑
return;
}
//成功以后的逻辑
};
import React, { useRef, useState } from 'react';
import { Modal } from '@ecom/antd';
import { noop } from '../tools/function';
function PrivacyModal({ visible, okHandler, cancelHandler }) {
return (
<Modal visible={visible} onOk={okHandler} onCancel={cancelHandler} title="查看用户个人数据">
<div>
<p>用户个人数据属于保密信息,有关访问、存储、传输等行为须严格遵守</p>
<a
href="www.baidu.com"
target="_blank"
rel="noreferrer"
style={{ color: '#1966ff' }}>
《用户个人数据使用规范》
</a>
</div>
</Modal>
);
}
export const usePrivacyModal = () => {
const [visible, setVisible] = useState(false);
const KEY = 'last-privacy-timestamp';
const promiseHandler = useRef<{ resolve: (...args: []) => any; reject: (...args: []) => any }>({
resolve: noop,
reject: noop,
});
const createPromise = () =>
new Promise((resolve, reject) => {
promiseHandler.current = {
resolve,
reject,
};
});
const okHandler = () => {
setVisible(false);
promiseHandler.current.resolve();
};
const cancelHandler = () => {
setVisible(false);
promiseHandler.current.reject();
};
const showModal = () => {
setVisible(true);
return createPromise().then(() => {
localStorage.setItem(KEY, Date.now().toString());
});
};
return {
privacyModal: <PrivacyModal visible={visible} okHandler={okHandler} cancelHandler={cancelHandler} />,
okHandler,
cancelHandler,
showModal,
};
};