基础类型:string number boolean object symbol function
对象类型:Object Array Date
不包含任何值类型:null undefined
除了判断== null 以外,其他都使用===
值类型:值一般都存放在栈中,当一个变量赋值给另一个变量时,两个变量都各有一个值存放在栈中;
引用类型:声明一个变量,该变量存放在栈中,声明一个对象,存放在堆中,将对象赋值给变量后,变量指向的是对象在堆中的地址。
什么是深拷贝?
上个知识点提到引用类型,引用类型是指变量指向的是对象的地址,所以当对对象操作后,变量的值也将会改变,那么如何让变量的值不发生改变就要用到深拷贝,其实就是将对象复制一份出来,重新开辟一块堆空间,让变量指向属于自己的对象。
function deepClone(obj = {}){
if (typeof(obj) !== 'object' || obj == null){
return obj;
}
let result;
if (obj instanceof Array){
result = [];
}else {
result = {};
}
for (let key in obj){
if (obj.hasOwnProperty(key)){
result[key] = deepClone(obj[key]);
}
}
return result;
}
1)if 语句和逻辑运算
truly变量: !!a === true 的变量
falsely变量: !!a === false 的变量
2)字符串拼接
字符串拼接,只要加了字符串,就是字符串
class JQuery {
constructor(selector){
const result = document.querySelectorAll(selector);
const length = result.length;
for (let i = 0; i < length; i++){
this[i] = result[i];
}
this.length = length;
this.selector = selector;
}
get(index){
return this[index];
}
each(fn){
for (let i = 0; i < this.length; i++){
const elem = this[i];
fn(elem);
}
}
on(type,fn){
return this.each((elem)=>{
elem.addEventListener(type,fn,false);
})
}
}
const $p = new JQuery('p');
console.log($p.get(1));
$p.on('click',(index)=>{
alert('a')
})
JQuery.prototype.dialog = function(info){
alert(info);
}
class MyJQuery extends JQuery {
add(){};
sub(){}
}
class Person {
constructor(name,age){
this.name = name;
this.age = age;
}
sayHi(){
console.log( "say hi");
}
}
class Student extends Person{
constructor(name,age,subject,num){
super(name,age);
this.name = name;
this.num = num;
this.subject = subject;
this.age = age;
}
study(){
console.log(this.name + ' study '+ this.subject + ',score is ' + this.num);
}
}
const xialuo = new Student('xialuo',18,'English' ,99);
console.log(xialuo);
xialuo.sayHi();
xialuo.study();
上面代码定义了一个父类Person,一个子类Student,一个实例xialuo。Student通过关键字extends继承了父类Person的属性。那么三者之间有什么关系呢?
通过例子可以看出来:
xiaoluo.proto === Student.prototype
Student.prototype.proto === Person.prototype
Person.prototype.proto === Object.prototype
Object.prototype.proto = null
总结:
最重要的一点是:this取值是在函数执行的时候决定,不是在函数定义的时候决定。
function fn1(){
console.log(this) //this指向window
}
fn1(); //普通函数调用
fn1.call({x:100}); //call调用返回对象
let fn2 = fn1.bind({x:200});//bind调用返回对象
fn2();
const zhangsan = {
name: '张三',
sayHi: function(){
console.log(this); //返回对象本身
},
wait(){
setTimeout(function(){
console.log(this) //window
},0)
}
}
const lisi = {
name: 'lisi',
sayHi(){
console.log(this);//返回对象本身
},
waitAgain(){
setTimeout(()=>{
console.log(this);//返回对象本身
},0)
}
}
class People{
constructor(name){
this.name = name;
this.age = 30;
}
sayHi(){
console.log(this);//class中调用返回对象本身
}
}
const wangwu = new People('wangwu');
wangwu.sayHi();
使用场景有:
Function.prototype.bind1 = function(){
//获取参数列表
const args = Array.prototype.slice.call(arguments);
//获取this指针
const t = args.shift();
//this指向函数 fn1
const self = this;
//返回函数
return function(){
return self.apply(t,args);
}
}
function fn1(a,b,c){
console.log(this);
console.log(a,b,c);
console.log("this is fn1");
}
const fn2 = fn1.bind1({x:100},200,300,400);
const res = fn2();
console.log(res);
作用域包括:全局作用域、函数作用域、块级作用域。
全局作用域和函数作用域比较好理解,一个是在全局定义,一个在函数定义。
块级作用域一般是指在if(){}、for(){}、while(){}等中。
闭包的使用场景:
//函数作为返回值
function creat(){
let a = 100;
return function(){
console.log(a);
}
}
let a = 200;
let fn = creat();
fn();
//函数作为参数被传递
function print(fn){
let b = 200;
fn(b);
}
let b = 100;
function fn1(){
console.log(b);
}
print(fn1);
上面例子是闭包的使用场景。结果为:
输出的自由变量的值都是其在定义地方的值。
需要注意的是自由变量的值是在它定义的地方,不是在执行的地方。
背景:
js是单线程语言;
浏览器和node.js已支持启动进程,如webWorker;
js和dom渲染共用同一个线程,js可修改dom结构,js执行dom停止渲染,dom渲染js停止执行。
异步的作用:
异步就是用来解决网络请求、定时任务等任务执行期间不让浏览器卡住不动的作用。
同步和异步的区别
基于js是单线程语言,异步不会阻塞代码执行,同步会阻塞代码执行。
上面代码一个函数调用另一个函数,函数中再嵌套函数,当函数嵌套层更多的时候,代码的可读性很差,所以promise就是用来解决地狱回调。
使用promise来加载一张图片的例子看一下。
function loadImg(src){
return new Promise((resolve,reject)=>{
const img = document.createElement('img');
img.onload = function(){
resolve(img);
}
img.onerror = function(){
const error = new Error(`加载图片失败 + ${src}`)
reject(error);
}
img.src = src;
})
}
let url1 = 'https://s.cn.bing.net/th?id=ODL.8a0e02e3f689b7f4f93b7bf17be4ca40&w=197&h=110&c=7&rs=1&qlt=80&o=6&dpr=1.12&pid=RichNav';
let url2 = 'https://s.cn.bing.net/th?id=ODL.1fda16309b56777cfcc70452accb321c&w=197&h=110&c=7&rs=1&qlt=80&o=6&dpr=1.12&pid=RichNav';
loadImg(url1).then(img =>{
console.log(img.width);
return img;
}).then(img =>{
console.log(img.height);
return loadImg(url2);
}).then(img1 =>{
console.log(img1.width);
}).catch(error =>{
console.log(error);
})
resolve() 方法 使得状态 pending —>fulfilled
reject() 方法 使得状态 pending —> rejected
2)promise的then和catch
then 方法返回一个 Promise 对象,其允许方法链。
Promise.resolve("foo")
// 1. 接收 "foo" 并与 "bar" 拼接,并将其结果做为下一个 resolve 返回。
.then(function(string) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
string += 'bar';
resolve(string);
}, 1);
});
})
// 2. 接收 "foobar", 放入一个异步函数中处理该字符串
// 并将其打印到控制台中,但是不将处理后的字符串返回到下一个。
.then(function(string) {
setTimeout(function() {
string += 'baz';
console.log(string);
}, 1)
return string;
})
// 3. 打印本节中代码将如何运行的帮助消息,
// 字符串实际上是由上一个回调函数之前的那块异步代码处理的。
.then(function(string) {
console.log("Last Then: oops... didn't bother to instantiate and return " +
"a promise in the prior then so the sequence may be a bit " +
"surprising");
// 注意 `string` 这时不会存在 'baz'。
// 因为这是发生在我们通过 setTimeout 模拟的异步函数中。
console.log(string);
});
// logs, in order:
// Last Then: oops... didn't bother to instantiate and return a promise in the prior then so the sequence may be a bit surprising
// foobar
// foobarbaz
catch方法返回一个Promise (en-US),并且处理拒绝的情况.
// 抛出一个错误,大多数时候将调用 catch 方法
var p1 = new Promise(function(resolve, reject) {
throw 'Uh-oh!';
});
p1.catch(function(e) {
console.log(e); // "Uh-oh!"
});
// 在异步函数中抛出的错误不会被 catch 捕获到
var p2 = new Promise(function(resolve, reject) {
setTimeout(function() {
throw 'Uncaught Exception!';
}, 1000);
});
p2.catch(function(e) {
console.log(e); // 不会执行
});
// 在 resolve() 后面抛出的错误会被忽略
var p3 = new Promise(function(resolve, reject) {
resolve();
throw 'Silenced Exception!';
});
p3.catch(function(e) {
console.log(e); // 不会执行
});
3)promise的API
resolve:返回一个以给定值解析后的 Promise 对象
reject:方法返回一个带有拒绝原因的 Promise 对象、
all:返回一个promise实例,回调结果是一个数组,所有函数执行完成后返回结果,如果reject回调立即抛出错误。
race:返回一个 promise,一旦迭代器中的某个 promise 解决或拒绝,返回的 promise 就会解决或拒绝。
any:返回一个promise,有一个成功返回promise,全部失败返回失败的promise。
4)手写promise(构造函数、then的链式调用、all、race)
/**
* @description myPromise
* @author liuer
*
*/
class MyPromise{
state = 'pending'; //pending fulfilled rejected
value = undefined; //存储成功的值
reason = undefined; //存储失败的值
resolveCallback = []; //存储pending状态 回调resolve的函数
rejectedCallback = []; //存储pending状态 回调rejected的函数、
constructor(fn){
const resolveHandler = (value) => {
if (this.state === 'pending'){
this.state = 'fulfilled';
this.value = value;
this.resolveCallback.forEach(fn => fn(this.value));
}
}
const rejectHandler = (reason) =>{
if (this.state === 'pending'){
this.state = 'rejected';
this.reason = reason;
this.rejectedCallback.forEach(fn => fn(this.reason));
}
}
try {
fn(resolveHandler,rejectHandler);
} catch (error) {
rejectHandler(error);
}
}
then(fn1,fn2){
fn1 = typeof fn1 === 'function' ? fn1 : (v)=>{v};
fn2 = typeof fn2 === 'function' ? fn2 : (e)=>{e};
//当状态为fulfilled 和 rejected时会直接执行,如果状态为pending 需要把 fn1和fn2存储起来
if (this.state === 'pending'){
return new MyPromise((resolve,reject)=>{
this.resolveCallback.push(()=>{
try {
const newValue = fn1(this.value);
resolve(newValue);
} catch (e) {
reject(e);
}
});
this.rejectedCallback.push(()=>{
try {
const newReason = fn2(this.reason);
reject(newReason);
} catch (error) {
reject(error);
}
})
})
}
//当状态为 fulfilled
if (this.state === 'fulfilled'){
return new MyPromise((resolve,reject)=>{
try {
const newValue = fn1(this.value);
resolve(newValue);
} catch (error) {
reject(error);
}
})
}
//当状态为 rejected
if (this.state === 'rejected'){
return new MyPromise((resolve,reject)=>{
try {
const newReason = fn2(this.reason);
reject(newReason);
} catch (error) {
reject(error);
}
})
}
}
//catch是then的一个语法糖
catch(fn){
this.then(null,fn);
}
}
MyPromise.resolve = function(value){
return new MyPromise((resolve,reject)=>{resolve(value)});
}
MyPromise.reject = function(reason){
return new MyPromise((resolve,reject) =>{ reject(reason)});
}
MyPromise.all = function(paramList = []){
return new MyPromise ((resolve,reject) =>{
let result = [];
let length = paramList.length;
let count = 0;
paramList.forEach(p => {
p.then(data=>{
result.push(data);
count++;
if (count === length){
resolve(result);
}
}).catch((error)=>{
reject(error);
})
})
})
}
MyPromise.race = function(paramList = []){
return new MyPromise((resolve,reject)=>{
let flag = false;
paramList.forEach(p =>{
p.then(data =>{
if (!flag){
resolve(data);
flag = true;
}
}).catch(e =>{
reject(e);
})
})
})
}
练习代码:
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Documenttitle>
head>
<body>
<script src="./22-promise.js">script>
<script>
const p1 = new MyPromise((resolve, reject) => {
//resolve(100);
setTimeout(() => {
resolve(100);
}, 500);
});
console.log(p1);
const p11 = p1.then((data) => {
return data + 1;
});
console.log(p11);
const p2 = MyPromise.resolve(100);
const p3 = MyPromise.resolve(200);
const p4 = MyPromise.reject('出错了');
const p5 = MyPromise.all([p2,p3]);
p5.then(data=>{console.log('p5: ' + data)});
const p6 = MyPromise.race([p2,p3,p4]);
p6.then(data=>{console.log('p6: ' + data)});
script>
body>
html>
event loop 事件循环/事件轮询机制。
为什么会有event loop ? 或者说event loop 和 javascript 有什么关联?
首先 javascript 是单线程语言,单线程是指在同一时间内只能执行一个事件。当有多个任务执行时,需要排队等待,如果有任务所需的时间比较长,如IO操作或网络请求等,页面就需要长时间等待会出现“假死”现象。为了解决这种情况,将任务分为了同步任务和异步任务。同步任务是指在主线程上执行的任务,异步任务是指不进入主线程,进入任务队列。当主线程空了,就会读取任务队列,这个过程会不断重复。这就是event loop.
如上图所示执行过程:
1)代码一行一行执行
2)遇到异步先记录下来,如定时器、网络请求
3)时机一到移动到callback Queue
4) 如果同步代码执行完,即call stack为空,触发eventloop机制
5)event loop 执行轮询机制,一遍一遍的去callback Queue查询,如果有则放到call stack 执行。
dom事件和event loop的关系?
1)js是单线程的
2)异步(setTimeOut、ajax)使用回调,基于event loop,setTimeout则是等待时间,时间一到就移动到Queue中
3) dom事件使用回调,基于event loop,回调的时机是点击触发事件时,会将存储在webApis中的事件放到callback Queue中。
** dom渲染和event loop 的关系?**
每次call stack清空(即每次轮训结束),即同步任务执行完,都是dom重新渲染的机会,dom结构如有改变则重新渲染,然后再去触发下一次Event loop。
async/await 是同步语法,彻底消灭回调函数。
async/await 和 promise的关系?
async function fn1(){
return 400; //async函数执行返回的是promise对象
//return Promise.resolve(400);
}
const res = fn1();
console.log(res);
!(async function fn2(){
const p1 = Promise.resolve(200);
const res1 = await p1;
console.log(res1);
})()
!(async function fn2(){
try {
const p1 = Promise.reject('error');//rejected状态需要trycatch捕获
const res1 = await p1;
} catch (error) {
console.log(error);
}
})()
!(async function fn2(){
const res1 = await 500; //await相当于promise的then
console.log(res1);
})()
async function fn3(){
return 100;
}
!(async function(){
const a = fn3();
console.log(a); //a是一个promise对象
const b = await fn3()
console.log(b);//100
})()
执行结果的一个面试题:
这里要注意的是c出错了所以不会打印。
<body>
<div id="container">div>
<script>
// console.log(100);
// setTimeout(()=>{
// console.log(200);
// })
// Promise.resolve().then(()=>{
// console.log(300);
// });
// console.log(400);
const div1 = document.getElementById('container');
const p1 = document.createElement('p');
p1.innerHTML = '这是第一段文字' + '
';
const p2 = document.createElement('p');
p2.innerHTML ='这是第二段文字' + '
';
const p3 = document.createElement('p');
p3.innerHTML = '这是第三段文字' + '
';
div1.appendChild(p1).appendChild(p2).appendChild(p3);
// alert会阻断js执行和dom渲染
Promise.resolve().then(()=>{
console.log('length1: ' + div1.children.length);
alert('promise')
})
setTimeout(()=>{
console.log('length2: ' + div1.children.length);
alert('setTimeout')
})
script>
上述代码证明微任务是在dom渲染之前执行,宏任务是在dom渲染之后执行。
下面是执行顺序的面试题
async function async1(){
console.log('async1 start'); //2
await async2();
//await后面的都作为回调内容 --微任务
console.log('async1 end'); //微任务 6
}
async function async2(){
console.log('async2'); //3
}
console.log('script start'); //1
setTimeout(function(){
console.log('setTimeout'); //宏任务 8
},0)
async1();
//初始化promise时,传入的函数会立刻被执行
new Promise(function(resolve){
console.log('promise1'); //4
resolve()
}).then(function(){
console.log('promise2') //微任务 7
})
console.log('script end'); //5