Loading...
墨滴

Aki2021

2021/09/04  阅读:30  主题:默认主题

JavaScript 基础知识

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

值类型和引用类型

值类型

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

引用类型

常见的引用类型

let a = { b100 };
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 = [123];// Array
const obj = { a1 };// object
const n = null // 特殊的引用类型,指针指向空地址

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

typeof

  • 识别所有值类型
  • 识别函数 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",
 age10,
 address: {
  city"shanghai",
  house: {
   number1
  }
 },
 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 = {
  age10,
  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 = { x100 }
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:所有自由变量的查找,是在函数定义的地方,向上级作用域查找,而不是在执行的地方

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

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

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

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

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

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

https://codepen.io/huangzonggui/pen/GRmawQW

image.png
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);

  • 參考:https://juejin.cn/post/6844903619595075592#heading-3

异步

  • 问题一: 单线程和异步(同步和异步的区别)?

    • 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
image.png

例子:https://coding.imooc.com/lesson/400.html#mid=35171

console.log('Hi');

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

console.log('Bye');
image.png
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 用于异步遍历
function muti(num{
 return new Promise((resolve) => {
  setTimeout(() => {
   resolve(num * num);
  }, 1000);
 });
}

const nums = [123];

// 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); // 异步打印
 }
})();

微任务microTask 宏任务macroTask

  • 宏任务
    • setTimeout
    • setInterval
    • Ajax
    • DOM事件
  • 微任务
    • Promise
    • async await
  • 微任务比宏任务执行的要早
    • 微任务在DOM渲染前触发
    • 宏任务在DOM渲染后触发 https://codepen.io/huangzonggui/pen/YzQyGeg
// 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
image.png
image.png

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

  • 微任务是ES6规定的
  • 宏任务是浏览器规定的
  • 执行Event Loop的时候,执行到Promise的微任务的时候,先放到micro task queue里面,与Callback Queue分开(宏任务)
image.png
image.png
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
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 (preturn 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;

参考

  • https://jsfiddle.net/6spj1059/
  • https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser
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
image.png
  • 跨域:同源策略,跨域解决方案
    • 浏览器的同源策略
      • AJAX请求时,浏览器要求当前页面和server必须同源(安全)
      • 同源:协议、域名、端口,三者必须一致。一致代表同一来源
      • 前端:浏览器浏览 http://a.com:8080/ api只能是:http://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
            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[1] (本地存储和会话存储)或 IndexedDB 。
  • 安全
    • JavaScript 通过 Document.cookie 访问 Cookie
      • JavaScript可以通过跨站脚本(xss)来窃取cookie,

      • 跨站请求伪造(CSRF)[2]

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

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

          • 对用户输入进行过滤来阻止 XSS (en-US)[3]
          • 任何敏感操作都需要确认;
          • 用于敏感信息的 Cookie 只能拥有较短的生命周期;
          • 更多方法可以查看OWASP CSRF prevention cheat sheet

localStorage和sessionStorage

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

参考资料

[1]

DOM Storage: https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Storage_API

[2]

Permalink to 跨站请求伪造(CSRF): https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Cookies#%E8%B7%A8%E7%AB%99%E8%AF%B7%E6%B1%82%E4%BC%AA%E9%80%A0%EF%BC%88csrf%EF%BC%89

[3]

Currently only available in English (US): https://developer.mozilla.org/en-US/docs/Glossary/Cross-site_scripting

Aki2021

2021/09/04  阅读:30  主题:默认主题

作者介绍

Aki2021