JavaScript 基础知识

324 阅读13分钟

面试爱问一些不是很具体的问题,比较泛:说出一点,或者说的很全面,反映出不同的水平

值类型和引用类型

值类型

let a = 100;
let b = a;
console.log(b)// 100

引用类型

常见的引用类型

let a = { b: 100 };
let c = a;
a.b = 200;
console.log(c.b);// 200

总结

  • 值类型 存储到栈中
  • 引用类型,(由于json或object存储到栈中会 太大,复制太慢,所以)通过一个堆地址,存储在堆中。需要的时候通过地址从堆查找(所有的计算机程序设计都是这个思想)

常见的值类型

let a;// undefined
const b = 10;// number
const c = 'string';
const d = Symbol('sym');// symbol
const e = true;// boolean

常见的引用类型

const arr = [1, 2, 3];// Array
const obj = { a: 1 };// object
const n = null // 特殊的引用类型,指针指向空地址

// 特殊引用类型,但不用于存储数据,所以没有“拷贝 复制函数”的说法
function fn() {} // 函数类型 可执行 

typeof

  • 识别所有值类型
    • typeof null === 'object'
    • typeof undefined === 'undefined'
    • typeof ${value} !== 'object' 可以判断是除null和undefined的基本类型
    • obj == null: 判断obj是null或undefined
  • 识别函数 typeof function () {} === 'function'
  • 判断是否是引用类型(但是不可以细分)
function deepClone(obj) {
	if (typeof obj !== "object" || obj == null) {
		return obj;
	}
	let result;

	if (obj instanceof Object) {
		result = {};
	} else {
		result = [];
	}

	for (let key in obj) {
		if (obj.hasOwnProperty(key)) {
			result[key] = deepClone(obj[key]);
		}
	}

	return result;
}

const person = {
	name: "zhangsan",
	age: 10,
	address: {
		city: "shanghai",
		house: {
			number: 1
		}
	},
	arr: ["a", 1, "b"]
};
const Lisi = deepClone(person);
person.name = "lisi";
person.address.city = "guangzhou";
console.log(Lisi.name);
console.log(Lisi.address.city);

instanceof

  • 检测prototype属性是否出现在某个实例对象的原型链上
function Car(make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
}

let instanseCar = new Car('make', 'model', 1924);
console.log(instanseCar instanceof Car); // true
console.log(instanseCar instanceof Object); // true

深拷贝的对象

  • 值类型
  • 引用类型
let objA = {
  age: 10,
  name: 'zhangsan',
  address: {
    city: 'shanghai'
  },
  arr: [1, 'str', 2]
}

TODO:手写深拷贝

思路:

  • 值类型或null,直接返回
  • 判断是对象或数组
  • 遍历对象或数组,注意是自己的属性,而不是原型链的属性,递归

变量计算

字符串拼接

const a = 100 + 10 // 110
const b = 100 + '10' // '10010'
const c = true + '10' // 'true10'
const d = 100 + parseInt('10') // 110

== 运算符转换

100 == '100' // true
0 == '' // true
false == '' // true
0 == false // true
null == undefined // true

// 技巧:除了 == null 之外,其他一律用 ===
// eg:
const obj = { x: 100 }
console.log(obj.a == null) // true,相当于 obj.a === null || obj.a === undefined

if语句和逻辑计算(&& || ! 与或非)

if语句判断的就是truly和falsely变量,而不是true和false

  • truly变量: !!a === true
  • falsely变量: !!b === false
// 以下是falsely变量,剩下的都是truly变量
!!undefined === false 
!!0 === false
!!'' === false
!!false === false

!!NaN === false
!!null === false

原型和原型链

问题

  • 如何准确判断一个变量是不是数组: a instanceof Array
  • class的原型的本质,怎么理解?
  • 手写一个简易的jQuery,考虑插件和扩展性: 学习Class和原型的好方法
    • 实现基本的api:get each on
      • 先熟悉jQuery这些方法: eachgeton
      • 实现简易的jQuery
      • 考虑插件
      • 考虑扩展性:集成
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);
		});
	}
	// 扩展很多 DOM API
}

// 插件
jQuery.prototype.dialog = function (info) {
	alert(info);
};

// “造轮子”
class myJQuery extends jQuery {
	constructor(selector) {
		super(selector);
	}
	// 扩展自己的方法
	addClass(className) {}
	style(data) {}
}

// const $p = new jQuery('p')
// $p.get(1)
// $p.each((elem) => console.log(elem.nodeName))
// $p.on('click', () => alert('clicked'))

ES6中的class

继承

  • extends
  • super
  • 扩展或重写方法

隐式原型和显示原型

通过例子来理解

codepen 代码

// 父类
class People {
	constructor(name) {
		this.name = name;
	}

	eat() {
		console.log(this.name + " eat something");
	}
}

// 子类
class Student extends People {
	constructor(name) {
		super(name);
	}

	sayHi() {
		console.log(this.name + " say hi!!!");
	}
}

class Teacher extends People {
	constructor(name) {
		super(name);
	}
}

let xiaoming = new Student("xiaoming");

console.log(xiaoming instanceof Student); // true

// ES6 class的实质是function,可见语法糖
console.log(typeof Student); // "function"
console.log(typeof People); // "function"

// 原型链:实例的隐式原型指向对应的class的prototype
console.log(xiaoming.__proto__ === Student.prototype); // true
console.log(Student.prototype.__proto__ === People.prototype); // true

console.log(JSON.stringify(People.prototype));
console.log(xiaoming.sayHi());
console.log(xiaoming.eat());

原型关系

  • 每个class都有一个显示原型prototype
  • 每个实例都有隐式原型__proto__
  • 实例的__proto__指向对应class的prototype

基于原型执行规则,获取属性或执行方法时

  • 先从自身的属性或方法寻找
  • 如果找不到,则去__proto__中查找。
  • 如果还找不到,就顺着原型链继续寻找,直到Object.prototype.__proto__为null

eg: 比如上图中,xialuo.sayHi()执行时,先在xialuo实例自身中寻找,没有找到,就回去xialuo.__proto__中寻找

理解画出原型链图

作用域和闭包

问题

  • 什么是作用域?什么是自由变量?
  • 什么是闭包?闭包的应用场景,作用?
  • this有哪几种赋值情况?应用场景,如何取值?
  • 手写bind函数

作用域

  • 全局作用域
  • 函数作用域
  • 块级作用域(ES6新增): let, const 定义的变量有块级作用域,{}内部的区域可以使用
if (true) {
    let a = 1;
}
console.log(a);// 报错,在块级作用域外无法读取a的值,a is not defined

自由变量

在A作用域中使用的变量x,却没有在A作用域中声明(即在其他作用域中声明的),对于A作用域来说,x就是一个自由变量。

  1. 一个变量在当前作用域没有定义,但是被使用了
  2. 向上级作用域一层一层的找,直到找到为止
  3. 如果到全局作用域都没有找到,就会报错: x is not defined

下方闭包的函数作为参数会体现: 所有自由变量的查找,是在函数定义的地方,向上级作用域查找,而不是在执行的地方

闭包

  • 闭包是作用域的特殊情况

    • 函数作为返回值
    function create() {
        let a = 100;
        return function() {
            console.log(a)
        }
    }
    
    let fn = create()
    let a = 200;
    fn();// 结果是多少?
    
    
    
    // 100
    
    • 函数作为参数
    function create(fn) {
        let a = 200;
        fn();
    }
    let a = 100;
    let fn = function() {
        console.log(a)
    }
    create(fn);
    
    
    
    // 结果是100:所有自由变量的查找,是在函数定义的地方,向上级作用域查找,而不是在执行的地方
    
    function bar() {
    console.log(myName);
    }
    function foo() {
            var myName = "极客邦";
            bar();
    }
    var myName = "极客时间";
    foo();
    
    
    
    // 极客时间
    

说出下面几种情况中 this的取值

  • 作为普通函数去调用
  • 使用call bind apply
  • 作为对象方法被调用
  • 在class方法中调用
  • 箭头函数

this取什么样的值,是在函数中执行的时候确定的,而不是在定义的时候,与自由变量刚好相反

function fn1() {
    console.log(this)
}
fn1() // window

fn1.call({ x: 100})// 直接执行:{ x: 100 }

const fn2 = fn1.bind({ x: 200 })
fn2()// bind只绑定,不执行,所以需要手动执行 { x: 200 }

codepen.io/huangzonggu…

image.png

作用域的相关问题

  • this的不同应用场景,如何取值?
    • 作为普通函数去调用: this指向window
    • 使用call bind apply: 指向绑定者
    • 作为对象方法被调用:指向当前对象
    • 在class方法中调用:class实例本身
    • 箭头函数: 指向上一作用域的值
      • 箭头函数表达式的语法比函数表达式更简洁,并且没有自己的thisargumentssupernew.target
  • 手写bind函数
  • 实际开发中闭包的应用场景,举例说明

原型链中的this

class People {
	constructor(name) {
		this.name = name;
	}
}

class Student extends People {
	constructor(name, number) {
		super(name);
		this.number = number;
	}
	sayHi() {
		console.log(`姓名 ${this.name} 學號 ${this.number}`);
	}
}

const stu1 = new Student("xialuo", "123");
stu1.sayHi();// "姓名 xialuo 學號 123"
stu1.__proto__.sayHi();// "姓名 undefined 學號 undefined"

// obj.__proto__.fun() 的时候this指向了obj.__proto__,这个对象没有name和number
// 实际obj.fun() 执行原理类似obj.__proto__.fun.call(obj) this指向obj

// console.log(stu1);
// console.log(stu1.__proto__);

任务: 闭包的应用场景

1.防抖

// 防抖:连续触发多次,只执行最后一次; 應用場景:input裡連續輸入,button多次點擊
// 节流:连续触发多次,一定时间内只触发一次;應用場景:鼠標移動,讀取鼠標的位置
const fn = () => console.log("fn");

window.onresize = debounce(fn, 500);

function debounce(fn) {
	let timer;

	return function () {
		if (timer) {//timer第一次执行后会保存在内存里 永远都是执行器 直到最后被触发
			clearTimeout(timer);
		}

		timer = setTimeout(() => fn(), 500);
	};
}

2.使用闭包设计单例模式

// 单例模式:创建多个实例的时候,只能创建单个实例

class createUser {
	constructor(name) {
		this.name = name;
	}
}

// let a = new createUser("aa");
// console.log(a.name);

// 代理创建单例模式
const ProxyFun = (() => {
	let instance = null;

	// 通過閉包 訪問閉包通過返回函數來訪問內部變量
	return function (name) {
		if (!instance) {
			instance = new createUser(name);
		}
		return instance;
	};
})(); // 立刻執行,返回一個function

const b = ProxyFun("b");
const c = ProxyFun("c");
console.log(b.name, c.name);
console.log(b === c);


异步

  • 问题一: 单线程和异步(同步和异步的区别)?
    • js是单线程语言,只能同时做一件事
    • 浏览器和nodejs已支持JS启动进程Web Worker
    • JS和DOM渲染共同一个线程,因为JS可修改DOM结构
    • 遇到等待(网络请求,定时任务)不可以卡住
    • 需要异步解决卡住的问题
      • alert是同步,会阻塞代码执行
      • setTimeout是异步,不阻塞执行
    • 回调callback函数形式
  • 问题二:手写用Promise加载一张图片
// 加载图片Fun 成功后和失败后的处理

const loadImage = (url) => {
	return new Promise((resolve, reject) => {
		let img = document.createElement("img");
		img.onload = () => {
			resolve(img);
			document.body.appendChild(img);
		};
		img.onerror = () => {
			reject(new Error("load image error"));
		};
		img.src = url;
	});
};

const url =
	"https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885__480.jpg";
loadImage(url)
	.then((data) => {
		console.log('width: ', data.width);
		return data;
	})
	.then((data) => {
		console.log('height: ', data.height);
	})
	.catch((error) => console.error(error));

  • 问题三:前端使用异步的应用场景
    • 网络请求 如ajax 图片加载
    • 定时任务 setTimeout、setInteral

相关知识

  • 单线程和异步
  • 应用场景
  • callback hell 和 Promise

event loop

要会画出图,并讲述过程

图组成部分

  • Browser console
  • Call Stack
  • Web APIs(类似setTimeout的时候,存放timer用的)
  • Callback Queue(Event Loop)

event Loop(事件轮询/事件循环)流程

  • 同步代码,一行一行放在Call Stack执行
  • 遇到异步代码,先记录下来,等待时机(定时、网络请求等)
  • 时机到了,将异步代码移动到Callback Queue中
  • 如Call Stack 为空的时候,Event Loop开始工作
  • 轮询查找Callback Queue,如有则移动到Call Stack执行
  • 继续轮询查找(永动机一样)

DOM事件和event loop 的关系

$('#btn1').click(function (e) {// 1. 执行到这一行的时候,将callback function暂存到Web APIs里
    console.log('click event'); // 2. 当点击的时候,立刻将callback function 提交到Callback Queue中,event loop轮询的时候,将callback function移到call Stack中执行
})
  • 异步(setTimeout, ajax等)使用回调,基于event loop
  • DOM事件(不是异步)使用回调,基于event loop,

DOM渲染

  • 当Call Stack空闲的时候,会尝试渲染DOM,再出发Event Loop、
  • JS是单线程的,而且和DOM渲染共用一个线程

image.png

例子:coding.imooc.com/lesson/400.…

console.log('Hi');

setTimeout(() => {
    console.log('callback1');
}, 5000);

console.log('Bye');

image.png

Promise

主要是为了解决回调地狱嵌套(callback hell)的问题

ES6解决异步问题常用方法(语法糖):async await

const p1 = Promise.reject("reject")
	.then((data) => {
		console.log("then 1:", data);
	})
	.catch((error) => {
		console.log("error 1:", error);
	})
	.then((data) => {
		console.log("then 2:", data);
	})
	.catch((error) => {
		console.log("error 1:", error);
	});

console.log('p1: ', p1);// fulfilled

// error 1: reject
// then 2

// 
const p2 = Promise.reject('error').catch(err => {
	console.error(err)
})

console.log('p2:', p2)// fulfilled

const p3 = Promise.reject('error').catch(err => {
	throw new Error('error on catch')
})

console.log('p3:', p3)// rejected

题目

// 题目
// Promise.resolve().then(() => {
// 	console.log(1)
// }).catch(() => {
// 	console.log(2)
// }).then(() => {
// 	console.log(3)
// })
// 1 3

//题目
// Promise.resolve().then(() => {
// 	console.log(1)
// 	throw new Error('error')
// }).catch(() => {
// 	console.log(2)
// }).then(() => {
// 	console.log(3)
// })

// // 1 2 3

async/await 和 Promise 的关系

  • 执行async函数,返回的是Promise对象
  • await相当于Promise的then
!(async function() {
    const prom = Promise.reject('1')
    const res = await prom// 这一行不执行,因为await相当于then,reject的时候不走then
    console.log('res', res)// 这一行不会执行,因为在上一行已经报错
})()

async await

  • 同步的语法,实现异步
  • await需要async包裹
  • await后可以是promise、async function
async function async1() {
	console.log("async1 start");// 这一行还没有到异步
	await async2();
	// await后面的都可以看做是callback里面的内容,异步
	console.log("async1 end");
}

async function async2() {
	console.log("async2");
}

console.log("script start");

setTimeout(() => {
	console.log("setTimeout");
}, 0);

async1();

new Promise((resolve) => {
	console.log("promise resolve");
	resolve();
}).then(() => {
	console.log("promise then");
});

console.log("script end");

// script start
// async1 start
// async2
// promise resolve
// script end
// async1 end
// promise then
// setTimeout

for-of的应用场景

  • for...in forEach for是常规的同步遍历
  • for...of 用于异步遍历
  • 注意区分:网络的同步阻塞,异步HTTP请求
    • for...in forEach for同步遍历指的是,多个循环同时执行
    • for...of 异步遍历指的是,第一循环执行完,才会执行第二个,不同时即不同步,即异步
function muti(num) {
	return new Promise((resolve) => {
		setTimeout(() => {
			resolve(num * num);
		}, 1000);
	});
}

const nums = [1, 2, 3];

// nums.forEach(async (i) => {
// const res = await muti(i);
// console.log(res);// 一下子打印
// });

!(async function () {
	for (let j of nums) {
		const res = await muti(j);
		console.log(res); // 异步打印 阻塞打印,等待一秒后打印1,1s后打印4,1s后打印9
                // 有结果后再执行下一个
	}
})();

微任务microTask 宏任务macroTask

  • 宏任务
    • setTimeout
    • setInterval
    • Ajax
    • DOM事件
  • 微任务
    • Promise
    • async await
  • 微任务比宏任务执行的要早
// 1.
// console.log("length:", document.getElementById("container").children.length);
// alert("本次Call stack 結束,DOM结构已更新,但尚未触发渲染");

// 2. 微任务在DOM渲染之前
Promise.resolve().then(() => {
	console.log("length:", document.getElementById("container").children.length);
	alert("promise then"); // 未渲染,页面看不到
});

//setTimeout(() => {
//console.log("length:", document.getElementById("container").children.length); // 3
//alert("setTimout  "); // 已渲染
//});

image.png

image.png

为什么微任务比宏任务执行的要早:

  • 微任务是ES6规定的
  • 宏任务是浏览器规定的
  • 执行Event Loop的时候,执行到Promise的微任务的时候,先放到micro task queue里面,与Callback Queue分开(宏任务)

image.png

image.png

console.log(100);
// 宏任务
setTimeout(() => console.log(200), 0);

// 微任务
Promise.resolve().then(() => console.log(300));

console.log(400);
// 100 400 300 200

JS-Web-API

DOM

vue和react封装了DOM的操作

带着问题学习DOM

  • DOM是哪种数据结构
  • DOM操作的常用API
  • attr和property的区别
  • 如何一次性插入多个DOM节点,考虑性能

知识点

  • DOM的本质
  • DOM节点操作
    • document.createElement(name)

    • document.getElementById(id)

    • document.getElementsByTagName(name)

    • document.getElementsByClassName(className)

    • document.getElementsByTagName(tagName)

    • document.querySelectorAll :返回一个 NodeList ,IE8+(含)。

    • document.forms :获取当前页面所有form,返回一个 HTMLCollection ;

  • DOM结构操作
    • 新建节点
      • createElement('p')
    • 插入节点
      • appendChild(${新键节点})
    • 移动节点
      • 对现有的节点进行appendChild(${现有节点})
    • 获取子元素节点
      • 每个node都包含有nodeType属性。
      let NodeListRes = document.getElementById("div1").childNodes;
      console.log(NodeListRes); // 有七个
      // nodeType取值:
      // 元素节点:1 属性节点:2 文本节点:3 注释节点:8
      NodeListRes = Array.prototype.slice
              .call(NodeListRes)
              .filter((item) => item.nodeType === 1);
      console.log("filter result:", NodeListRes);
      
    • 获取父元素节点
    document.getElementById("p2").parentNode;
    
    • 删除节点
    document.getElementById("div1").removeChild(document.getElementById("p3"));
    
  • DOM性能
    • DOM操作是比较耗费性能的
    • 避免频繁查询,查询做缓存
    // 频繁查询DOM
    // for (let i = 0; i < document.getElementsByTagName("p").length; i++) {
    // console.log(i);
    // }
    
    // 做缓存 一次查询
    let tagLength = document.getElementsByTagName("p").length;
    for (let i = 0; i < tagLength; i++) {
            console.log(i);
    }
    
    
    • 避免频繁操作

attr和property的区别

<div id='div1' class='container'>
	<p>this is a p tag</p>
	<p>this is a p tag</p>
	<p>this is a p tag</p>
</div>
let pList = document.querySelectorAll("p");

// set property
pList[0].a = "a";
// set attribute
pList[1].setAttribute("a", "a");
  • attribute修改HTML属性,会反映到HTML结构上
  • property修改自定义的JS属性,不会反映到HTML结构上
    • 需要注意一点,style 是一个比较特殊的例子,修改它的属性,会触发 DOM 样式的改动,会体现到 DOM 中。而我们日常所说的 property,更多是自定义的属性,例如 p1.a = 100; p1.b = 200 这样子。
  • 修改attribute会导致DOM渲染,修改property有可能导致DOM渲染,但是建议尽量使用property,DOM渲染会耗费性能

BOM (Browser Object Model)

题目

  • 如何识别浏览器
  • 分拆url的各个部分

知识点

  • navigator
  • screen
  • location
  • history
识别浏览器

一般用navigator.userAgent来判断浏览器,但是,这个判断是不严谨的,就好像看到一个鸭子,走路和叫声像鸭子,就判定是鸭子。应该使用各个浏览器的特征来判断

另外,浏览器为了网页能在自己网页中运行,userAgent加了很多标识。

例如,Chrome的userAgent

Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36

image.png

// Opera 8.0+
var isOpera = (!!window.opr && !!opr.addons) || !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0;

// Firefox 1.0+
var isFirefox = typeof InstallTrigger !== 'undefined';

// Safari 3.0+ "[object HTMLElementConstructor]" 
var isSafari = /constructor/i.test(window.HTMLElement) || (function (p) { return p.toString() === "[object SafariRemoteNotification]"; })(!window['safari'] || (typeof safari !== 'undefined' && safari.pushNotification));

// Internet Explorer 6-11
var isIE = /*@cc_on!@*/false || !!document.documentMode;

// Edge 20+
var isEdge = !isIE && !!window.StyleMedia;

// Chrome 1 - 71
var isChrome = !!window.chrome && (!!window.chrome.webstore || !!window.chrome.runtime);

// Blink engine detection
var isBlink = (isChrome || isOpera) && !!window.CSS;


var output = 'Detecting browsers by ducktyping:<hr>';
output += 'isFirefox: ' + isFirefox + '<br>';
output += 'isChrome: ' + isChrome + '<br>';
output += 'isSafari: ' + isSafari + '<br>';
output += 'isOpera: ' + isOpera + '<br>';
output += 'isIE: ' + isIE + '<br>';
output += 'isEdge: ' + isEdge + '<br>';
output += 'isBlink: ' + isBlink + '<br>';
document.body.innerHTML = output;

参考

Screen(window.screen)
  • screen.width
  • screen.height
location
console.log('href:', location.href);
console.log('protocol:', location.protocol);
console.log('host:', location.host);
console.log('hostname:', location.hostname);
console.log('hast:', location.hash);
console.log('port:', location.port);
console.log('search:', location.search);
history
  • history.back()
  • history.forwork()
  • history.go(1)

事件

题目

  • 编写一个通用的事件绑定函数
  • 描述冒泡的过程
  • 无线下拉的图片列表,如何监听每个图片点击

知识点

  • 事件绑定
  • 事件冒泡
  • 事件代理

事件绑定

let btnElement = document.getElementById("btn");

// 普通的事件绑定
// btnElement.addEventListener("click", () => alert("alert"));

// 通用的事件绑定(未完善,见下方兼容事件代理的 事件绑定函数)
const myBind = function (type, callback, target) {
	target.addEventListener(type, callback);
};

myBind("click", () => alert("myBind"), btnElement);

改造事件绑定

<div id='div1' class='container'>
            <p id="p1">点击我 阻止冒泡 </p>
            <p>this is a p tag</p>
            <p>this is a p tag</p>
            <p>this is a p tag</p>
        </div>

        <div id='div2' class='container'>
            <a href="#">a1</a>
            <a href="#">a2</a>
            <a href="#">a3</a>
            <a href="#">a4</a>
            <button>i am button</button>
        </div>
// 冒泡

// 通用的事件绑定:考虑事件代理
const bindEvent = function (elem, eventType, selector, callback) {

	if (callback == null) {
		callback = selector;
		selector = null;
	}

	elem.addEventListener(eventType, (event) => {
		console.log('event :>> ', event);
		if (selector) {
			// 事件代理
			if (event.target.matches(selector)) {
				callback.call(event.target, event)// 调用的时候,this可以指向callback里的function
				// callback()
			}
		} else {
			// 普通绑定
			callback.call(event.target, event)
				// callback()
		}
	});
};

const bodyElem = document.body;
// const bodyElem = document.getElementById("div1");
bindEvent(bodyElem, "click", function (event) {
	alert(this.innerHTML);
});

// 普通绑定
const p1Elem = document.getElementById("p1");
bindEvent(p1Elem, "click", function (event) {
	event.stopPropagation();// 阻止冒泡
	alert(this.innerHTML);
});

// 事件代理:基于冒泡事件实现
// 子元素太多,逐个绑定事件会很复杂,所以通过事件代理到父元素上

const div2 = document.getElementById('div2');
// 未传selector的时候,自己处理判断是否是触发的目标元素
// bindEvent(div2, 'click', function (event) {
// 	event.stopPropagation();
// 	event.preventDefault();// 阻止a标签的跳转: #
// 	if (event.target.nodeName === 'A') {
// 		console.log('是a標籤')
// 		alert(this.innerHTML)
// 	}
// })

// 传selector, 改造 事件绑定,兼容事件代理
bindEvent(div2, 'click', 'A', function (event) {
	event.stopPropagation();
	event.preventDefault();// 阻止a标签的跳转: #
	// if (event.target.nodeName === 'A') {
	// console.log('是a標籤')
	alert(this.innerHTML)
	// }
})

答:描述事件冒泡的过程

  • 基于DOM树形结构
  • 事件顺着触发元素向上冒泡
  • 应用:事件代理

答:无线下拉的图片列表,如何监听每个图片点击

  • 事件代理
  • 用event.target来获取触发的元素
  • 通过matches来判断 触发元素 是否是图片

AJAX(Asynchronous JavaScript And XML)

AJAX(Asynchronous JavaScript And XML )是一种使用 XMLHttpRequest 技术构建更复杂,动态的网页的编程实践。

目标

  • 手写一个简易的AJAX(用Promise)
    • 不用具体考虑所有的方法和状态码(重复造轮子)
    function myAjax(url) {
            return new Promise((resolve, reject) => {
                    let xhr = new XMLHttpRequest(url);
                    xhr.open("GET", url, true);
                    xhr.onreadystatechange = function () {
                            if (xhr.readyState === 4) {
                                    if (xhr.code === 200) {
                                            resolve(JSON.parse(xhr.responseText));
                                    } else if (xhr.code === 404) {
                                            reject(new Error("error"));
                                    }
                            }
                    };
                    xhr.send(null);
            });
    }
    
    let res = myAjax("./myAjax.json");
    res.then((data) => {
            console.log("data:", data);
    });
    
  • 跨域常用的实现方式

知识点

  • XMLHttpRequest

XMLHttpRequest (XHR) 是一种创建 AJAX 请求的 JavaScript API 。它的方法提供了在浏览器服务器之间发送请求的能力。

  • 状态码 XMLHttpRequest.status

status码是标准的HTTP status codes XMLHttpRequest.onreadystatechange

XMLHttpRequest.readyState: 4的時候,才可以获取到response

分类分类描述
0(未初始化)
1(载入)
2(载入完成)
3(交互)
4(完成)响应内容解析完成

image.png

  • 跨域:同源策略,跨域解决方案
    • 浏览器的同源策略
      • AJAX请求时,浏览器要求当前页面和server必须同源(安全)
      • 同源:协议、域名、端口,三者必须一致。一致代表同一来源
      • 前端:浏览器浏览 a.com:8080/ api只能是:a.com:8080/api/xxx 如果server不是,则会被浏览器拦截
        • 但是服务器访问服务器的数据,是不会拦截的,因为没有经过浏览器,所以出现服务器向服务器发起攻击的安全事件
    • 加载图片、css、js不受浏览器的同源策略限制
      • <img src=跨域的图片地址 /> :通过图片实现统计打点,使用第三方统计服务
      • <link href=跨域的css地址 /> :CDN
      • <script src=跨域的js地址></script>:CDN 实现JSONP
    • 跨域(CORS: Cross-Origin requests 跨域资源共享 )
      • 所有的跨域,都必须经过server端允许和配合
        • server配置: Access-Control-Allow-Origin(访问控制允许来源)、
    • 解决跨域方案
      • 纯服务器

        • 服务器 配置http header: Access-Control-Allow-Origin
        // 第二个参数填写允许跨域的名称,不建议*
        response.serHeader("Access-Control-Allow-Origin", "http://localhost:8011");
        response.serHeader("Access-Control-Allow-Headers", "X-Requested-With");
        response.serHeader("Access-Control-Allow-Origin", "PUT,POST,GET,DELETE,OPTIONS");
        response.serHeader("Access-Control-Allow-Credentials", "true");
        
      • 需要服务端配合

        • JSONP:JSON with Padding (填充式 JSON) (不推荐使用)
          • 实现方式

            • 通过script获取数据,返回
            // 后台处理返回
            callback({
                name: 'zhangsan',
                age: 12
            })
            
            • 前端处理script(跨域的URL)请求数据
            window.callback = function(data) {
                console.log(data)// 成功取到跨域的数据
                // {
                //     name: 'zhangsan',
                //     age: 12
                // }
            }
            

            image.png

          • 缺点

            • 只支持GET
            • 安全性不够
              • JSONP暴露了很多漏洞,它假定了传送回来的代码是可信的,进一步暴露了CSRF(跨站点请求伪造)漏洞。
            • 没有错误处理,调试困难
      • 纯前端

        • 代理

思考🤔

  • 是不是可以通过跨域的方法,谁都可以做一个山寨淘宝出来?如果是这样,那么跨域好像就没有什么意义了吧。虽然有同源策略限制,但是大家都知道解决这个限制的方法,那就相当于没有限制?

AJAX的一些插件

  • jQuery
  • Fetch
    • 注意: 当接收到一个代表错误的 HTTP 状态码时,从 fetch() 返回的 Promise 不会被标记为 reject,  即使响应的 HTTP 状态码是 404 或 500。相反,它会将 Promise 状态标记为 resolve (但是会将 resolve 的返回值的 ok 属性设置为 false ),仅当网络故障时或请求被阻止时,才会标记为 reject。
  • axios

存储

Cookie

  • 特点
    • 存储大小,最大4k
    • 随着http请求,
  • 用途
    • 会话状态管理(如用户登录信息、购物车、游戏分数或其他需要记录的信息)
    • 个性化设置(如用户的个性化设置,主题等)
    • 浏览器行为跟踪(如跟踪分析用户行为等)
  • 观点
    • 现在随着现代浏览器开始支持各种各样的存储方式,Cookie 渐渐被淘汰。由于服务器指定 Cookie 后,浏览器的每次请求都会携带 Cookie 数据,会带来额外的性能开销(尤其是在移动环境下)。新的浏览器API已经允许开发者直接将数据存储到本地,如使用 Web storage API (本地存储和会话存储)或 IndexedDB 。
  • 安全
    • JavaScript 通过 Document.cookie 访问 Cookie
      • JavaScript可以通过跨站脚本(xss)来窃取cookie,

      • 跨站请求伪造(CSRF)

        维基百科已经给了一个比较好的 CSRF 例子。比如在不安全聊天室或论坛上的一张图片,它实际上是一个给你银行服务器发送提现的请求:

        <img src="http://bank.example.com/withdraw?account=bob&amount=1000000&for=mallory">
        
        • 当你打开含有了这张图片的 HTML 页面时,如果你之前已经登录了你的银行帐号并且 Cookie 仍然有效(还没有其它验证步骤),你银行里的钱很可能会被自动转走。有一些方法可以阻止此类事件的发生:

参考美团文章

localStorage和sessionStorage

  • 特点
    • HTML5专门为存储而设计,大小限制为5M
    • API易用:getItem('key', 'value')、setItem('key')、removeItem('key')、clear()
    • 不会随着http请求发送出去
  • 区别
    • localStorage会持久化存储,除非代码删除或手动删除
    • sessionStorage 关闭对应浏览器窗口(Window)/ tab,会清除对应的sessionStorage

TODO: 拓展思考🤔

  • 不用cookie的话,用什么方式实现登录信息的保存?这个方式如何避免cookie的安全问题?
  • token登录的过程是如何实现的?
  • 如何持久化保存用户的登录信息?

常见正则

掌握基本的场景正则,手写

  • 用户名(判断字符串已字母开头,后面字母数字下划线,长度6-30)
  • 邮件
  • 小写英文字母
  • 英文字母
  • 邮政编码
  • 简单的ip地址
  • 日期格式(2021.09.11)

正则学习网站:deerchao.cn/tutorials/r…

思考🤔(题目)

Object.create() 和 new Object()的区别

new Object 和 {} 新键对象相同,原型都是Object.prototype

Object.create()使用现有的对象来提供新键对象的__proto__

let obj = {
	a: 100
};

let objectCreateObj = Object.create(null);
console.log(objectCreateObj.__proto__); // undefined

let objectCreateWithObj = Object.create(obj);
console.log(objectCreateWithObj.a); // 100
console.log(objectCreateWithObj.__proto__ === obj); //true

JS如何实现继承

  • class继承(ES6引入class)
class Animal {
    constructor(name) {
        this.name = name;
    }
}

class Cat extends Animal {
    constructor(name) {
        super(name);
    }

    say () {
       return 'Hello, ' + this.name + '!'
    }
}

// 测试:
var kitty = new Cat('Kitty');
var doraemon = new Cat('哆啦A梦');
if ((new Cat('x') instanceof Animal)
    && kitty 
    && kitty.name === 'Kitty'
    && kitty.say
    && typeof kitty.say === 'function'
    && kitty.say() === 'Hello, Kitty!'
    && kitty.say === doraemon.say)
{
    console.log('测试通过!');
} else {
    console.log('测试失败!');
}

参考 www.liaoxuefeng.com/wiki/102291…

  • prototype继承
function Person(first, last, age, gender) {
	this.name = {
		first,
		last
	};
	this.age = age;
	this.gender = gender;
}

function Teacher(first, last, age, gender, subject) {
	Person.call(this, first, last, age, gender);

	this.subject = subject;
}

let teacher1 = new Teacher("aki", "huang", 20, "男", "王牌讲师");
console.log(teacher1);

codepen.io/huangzonggu…

参考资料 developer.mozilla.org/zh-CN/docs/…

参考