JavaScript基础知识点

124 阅读5分钟

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)

缺点:不能处理函数

image.png

手写深拷贝,背下来:

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。秘诀,永远找函数定义的位置。

参考资料:github.com/mqyqingfeng…

image.png 打印结果是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,
  };
};