前端小智

前端小智 查看完整档案

厦门编辑Plymouth  |  前端 编辑大迁世界  |  Web前端攻城狮 编辑 github.com/qq449245884/xiaozhi 编辑
编辑

我不是什么大牛,我其实想做的就是一个传播者。内容可能过于基础,但对于刚入门的人来说或许是一个窗口,一个解惑之窗。我要先坚持分享20年,大家来一起见证吧。

个人动态

前端小智 发布了文章 · 4月13日

try..catch 不能捕获的错误有哪些?注意事项又有哪些?

作者:Ashish Lahoti
译者:前端小智
来源:codingnconcept
点赞再看,微信搜索大迁世界,B站关注【前端小智】这个没有大厂背景,但有着一股向上积极心态人。本文 GitHubhttps://github.com/qq44924588... 上已经收录,文章的已分类,也整理了很多我的文档,和教程资料。**

最近开源了一个 Vue 组件,还不够完善,欢迎大家来一起完善它,也希望大家能给个 star 支持一下,谢谢各位了。

github 地址:https://github.com/qq44924588...

今天的内容中,我们来学习一下使用trycatchfinallythrow进行错误处理。我们还会讲一下 JS 中内置的错误对象(Error, SyntaxError, ReferenceError等)以及如何定义自定义错误。

1.使用 try..catch..finally..throw

在 JS 中处理错误,我们主要使用trycatchfinallythrow关键字。

  • try块包含我们需要检查的代码
  • 关键字throw用于抛出自定义错误
  • catch块处理捕获的错误
  • finally 块是最终结果无论如何,都会执行的一个块,可以在这个块里面做一些需要善后的事情

1.1 try

每个try块必须与至少一个catchfinally块,否则会抛出SyntaxError错误。

我们单独使用try块进行验证:

try {
  throw new Error('Error while executing the code');
}
ⓧ Uncaught SyntaxError: Missing catch or finally after try

1.2 try..catch

建议将trycatch块一起使用,它可以优雅地处理try块抛出的错误。

try {
  throw new Error('Error while executing the code');
} catch (err) {
  console.error(err.message);
}
➤ ⓧ Error while executing the code

1.2.1 try..catch 与 无效代码

try..catch 无法捕获无效的 JS 代码,例如try块中的以下代码在语法上是错误的,但它不会被catch块捕获。

try {
  ~!$%^&*
} catch(err) {
  console.log("这里不会被执行");
}
➤ ⓧ Uncaught SyntaxError: Invalid or unexpected token

1.2.2 try..catch 与 异步代码

同样,try..catch无法捕获在异步代码中引发的异常,例如setTimeout

try {
  setTimeout(function() {
    noSuchVariable;   // undefined variable
  }, 1000);
} catch (err) {
  console.log("这里不会被执行");
}

未捕获的ReferenceError将在1秒后引发:

➤ ⓧ Uncaught ReferenceError: noSuchVariable is not defined

所以 ,我们应该在异步代码内部使用 try..catch 来处理错误:

setTimeout(function() {
  try {
    noSuchVariable;
  } catch(err) {
    console.log("error is caught here!");
  }
}, 1000);

1.2.3 嵌套 try..catch

我们还可以使用嵌套的trycatch块向上抛出错误,如下所示:

try {
  try {
    throw new Error('Error while executing the inner code');
  } catch (err) {
    throw err;
  }
} catch (err) {
  console.log("Error caught by outer block:");
  console.error(err.message);
}
Error caught by outer block:
➤ ⓧ Error while executing the code

1.3 try..finally

不建议仅使用 try..finally 而没有 catch 块,看看下面会发生什么:

try {
  throw new Error('Error while executing the code');
} finally {
  console.log('finally');
}
finally
➤ ⓧ Uncaught Error: Error while executing the code

这里注意两件事:

  • 即使从try块抛出错误后,也会执行finally
  • 如果没有catch块,错误将不能被优雅地处理,从而导致未捕获的错误

1.4 try..catch..finally

建议使用try...catch块和可选的finally块。

try {
  console.log("Start of try block");
  throw new Error('Error while executing the code');
  console.log("End of try block -- never reached");
} catch (err) {
  console.error(err.message);
} finally {
  console.log('Finally block always run');
}
console.log("Code execution outside try-catch-finally block continue..");
Start of try block
➤ ⓧ Error while executing the code
Finally block always run
Code execution outside try-catch-finally block continue..

这里还要注意两件事:

  • try块中抛出错误后往后的代码不会被执行了
  • 即使在try块抛出错误之后,finally块仍然执行

finally块通常用于清理资源或关闭流,如下所示:

try {
  openFile(file);
  readFile(file);
} catch (err) {
  console.error(err.message);
} finally {
  closeFile(file);
}

1.5 throw

throw语句用于引发异常。

throw <expression>
// throw primitives and functions
throw "Error404";
throw 42;
throw true;
throw {toString: function() { return "I'm an object!"; } };

// throw error object
throw new Error('Error while executing the code');
throw new SyntaxError('Something is wrong with the syntax');
throw new ReferenceError('Oops..Wrong reference');

// throw custom error object
function ValidationError(message) {
  this.message = message;
  this.name = 'ValidationError';
}
throw new ValidationError('Value too high');

2. 异步代码中的错误处理

对于异步代码的错误处理可以Promiseasync await

2.1 Promise 中的 then..catch

我们可以使用then()catch()链接多个 Promises,以处理链中单个 Promise 的错误,如下所示:

Promise.resolve(1)
  .then(res => {
      console.log(res);  // 打印 '1'

      throw new Error('something went wrong');  // throw error

      return Promise.resolve(2);  // 这里不会被执行
  })
  .then(res => {
      // 这里也不会执行,因为错误还没有被处理
      console.log(res);    
  })
  .catch(err => {
      console.error(err.message);  // 打印 'something went wrong'
      return Promise.resolve(3);
  })
  .then(res => {
      console.log(res);  // 打印 '3'
  })
  .catch(err => {
      // 这里不会被执行
      console.error(err);
  })

我们来看一个更实际的示例,其中我们使用fetch调用API,该 API 返回一个promise对象,我们使用catch块优雅地处理 API 失败。

function handleErrors(response) {
    if (!response.ok) {
        throw Error(response.statusText);
    }
    return response;
}

fetch("http://httpstat.us/500")
    .then(handleErrors)
    .then(response => console.log("ok"))
    .catch(error => console.log("Caught", error));
Caught Error: Internal Server Error
    at handleErrors (<anonymous>:3:15)

2.2 try..catchasync await

async await 中 使用 try..catch 比较容易:

(async function() {
    try {
        await fetch("http://httpstat.us/500");
    } catch (err) {
        console.error(err.message);
    }
})();

让我们看同一示例,其中我们使用fetch调用API,该API返回一个promise对象, 我们使用try..catch块优雅地处理API失败。

function handleErrors(response) {
    if (!response.ok) {
        throw Error(response.statusText);
    }
}

(async function() {
    try {
      let response = await fetch("http://httpstat.us/500");
      handleErrors(response);
      let data = await response.json();
      return data;
    } catch (error) {
        console.log("Caught", error)
    }
})();
Caught Error: Internal Server Error
    at handleErrors (<anonymous>:3:15)
    at <anonymous>:11:7

3. JS 中的内置错误

3.1 Error

JavaScript 有内置的错误对象,它通常由try块抛出,并在catch块中捕获,Error 对象包含以下属性:

  • name:是错误的名称,例如 “Error”, “SyntaxError”, “ReferenceError” 等。
  • message:有关错误详细信息的消息。
  • stack:是用于调试目的的错误的堆栈跟踪。

我们创建一个Error 对象,并查看它的名称和消息属性:

const err = new Error('Error while executing the code');

console.log("name:", err.name);
console.log("message:", err.message);
console.log("stack:", err.stack);
name: Error
message: Error while executing the code
stack: Error: Error while executing the code
    at <anonymous>:1:13

JavaScript 有以下内置错误,这些错误是从 Error 对象继承而来的

3.2 EvalError

EvalError 表示关于全局eval()函数的错误,这个异常不再由 JS 抛出,它的存在是为了向后兼容。

3.3 RangeError

当值超出范围时,将引发RangeError

➤ [].length = -1
ⓧ Uncaught RangeError: Invalid array length

3.4 ReferenceError

当引用一个不存在的变量时,将引发 ReferenceError

➤ x = x + 1;
ⓧ Uncaught ReferenceError: x is not defined

3.5 SyntaxError

当你在 JS 代码中使用任何错误的语法时,都会引发SyntaxError

➤ function() { return 'Hi!' }
ⓧ Uncaught SyntaxError: Function statements require a function name

➤ 1 = 1
ⓧ Uncaught SyntaxError: Invalid left-hand side in assignment

➤ JSON.parse("{ x }");
ⓧ Uncaught SyntaxError: Unexpected token x in JSON at position 2

3.6 TypeError

如果该值不是预期的类型,则抛出TypeError

➤ 1();
ⓧ Uncaught TypeError: 1 is not a function

➤ null.name;
ⓧ Uncaught TypeError: Cannot read property 'name' of null

3.7 URIError

如果以错误的方式使用全局 URI 方法,则会抛出URIError

➤ decodeURI("%%%");
ⓧ Uncaught URIError: URI malformed

4. 定义并抛出自定义错误

我们也可以用这种方式定义自定义错误。

class CustomError extends Error {
  constructor(message) {
    super(message);
    this.name = "CustomError";
  } 
};

const err = new CustomError('Custom error while executing the code');

console.log("name:", err.name);
console.log("message:", err.message);
name: CustomError
message: Custom error while executing the code

我们还可以进一步增强CustomError对象以包含错误代码

class CustomError extends Error {
  constructor(message, code) {
    super(message);
    this.name = "CustomError";
    this.code = code;
  } 
};

const err = new CustomError('Custom error while executing the code', "ERROR_CODE");

console.log("name:", err.name);
console.log("message:", err.message);
console.log("code:", err.code);
name: CustomError
message: Custom error while executing the code
code: ERROR_CODE

try..catch块中使用它:

try{
  try {
    null.name;
  }catch(err){
    throw new CustomError(err.message, err.name);  //message, code
  }
}catch(err){
  console.log(err.name, err.code, err.message);
}
CustomError TypeError Cannot read property 'name' of null

我是小智,我们下期见!


代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

原文:https://codings.com/javascrip...

交流

文章每周持续更新,可以微信搜索「 大迁世界 」第一时间阅读和催更(比博客早一到两篇哟),本文 GitHub https://github.com/qq449245884/xiaozhi 已经收录,整理了很多我的文档,欢迎Star和完善,大家面试可以参照考点复习,另外关注公众号,后台回复福利,即可看到福利,你懂的。

查看原文

赞 3 收藏 3 评论 0

前端小智 发布了文章 · 4月12日

所以你认为你知道 JavaScript 吗?

作者:Chidume Nnamdi
译者:前端小智
来源:smashingmagazine

点赞再看,养成习惯

本文 GitHubhttps://github.com/qq44924588... 上已经收录,更多往期高赞文章的分类,也整理了很多我的文档,和教程资料。欢迎Star和完善,大家面试可以参照考点复习,希望我们一起有点东西。

JavaScript 是一种有趣的语言,我们都喜欢它,因为它的性质。浏览器是JavaScript的主要运行的地方,两者在我们的服务中协同工作。JS有一些概念,人们往往会对它掉以轻心,有时可能会忽略不计。原型、闭包和事件循环等概念仍然是大多数JS开发人员绕道而行的晦涩领域之一。正如我们所知,无知是一件危险的事情,它可能会导致错误。

想阅读更多优质文章请[猛戳GitHub博客][1]\,一年百来篇优质文章等着你!

接下来,来看看几个问题,你也可以试试想想,然后作答。

问题1:浏览器控制台上会打印什么?

var a = 10;
function foo() {
    console.log(a); // ??
    var a = 20;
}
foo();

问题2:如果我们使用 let 或 const 代替 var,输出是否相同

var a = 10;
function foo() {
    console.log(a); // ??
    let a = 20;
}
foo();

问题3:“newArray”中有哪些元素?

var array = [];
for(var i = 0; i <3; i++) {
 array.push(() => i);
}
var newArray = array.map(el => el());
console.log(newArray); // ??

问题4:如果我们在浏览器控制台中运行'foo'函数,是否会导致堆栈溢出错误?

function foo() {
  setTimeout(foo, 0); // 是否存在堆栈溢出错误?
};

问题5: 如果在控制台中运行以下函数,页面(选项卡)的 UI 是否仍然响应

function foo() {
  return Promise.resolve().then(foo);
};

问题6: 我们能否以某种方式为下面的语句使用展开运算而不导致类型错误

var obj = { x: 1, y: 2, z: 3 };
[...obj]; // TypeError

问题7:运行以下代码片段时,控制台上会打印什么?

var obj = { a: 1, b: 2 };
Object.setPrototypeOf(obj, {c: 3});
Object.defineProperty(obj, 'd', { value: 4, enumerable: false });

// what properties will be printed when we run the for-in loop?
for(let prop in obj) {
    console.log(prop);
}

问题8:xGetter() 会打印什么值?

var x = 10;
var foo = {
  x: 90,
  getX: function() {
    return this.x;
  }
};
foo.getX(); // prints 90
var xGetter = foo.getX;
xGetter(); // prints ??

答案

现在,让我们从头到尾回答每个问题。我将给您一个简短的解释,同时试图揭开这些行为的神秘面纱,并提供一些参考资料。

问题1: undefined

解析:

使用var关键字声明的变量在JavaScript中会被提升,并在内存中分配值undefined。 但初始化恰发生在你给变量赋值的地方。 另外,var声明的变量是[函数作用域的][2],而letconst是块作用域的。 所以,这就是这个过程的样子:

var a = 10; // 全局使用域
function foo() {
// var a 的声明将被提升到到函数的顶部。
// 比如:var a

console.log(a); // 打印 undefined

// 实际初始化值20只发生在这里
   var a = 20; // local scope
}
    • -

问题 2:ReferenceError:a undefined

解析:

letconst声明可以让变量在其作用域上受限于它所使用的块、语句或表达式。与var不同的是,这些变量没有被提升,并且有一个所谓的暂时死区(TDZ)。试图访问TDZ中的这些变量将引发ReferenceError,因为只有在执行到达声明时才能访问它们。

var a = 10; // 全局使用域
function foo() { // TDZ 开始

// 创建了未初始化的'a'
    console.log(a); // ReferenceError

// TDZ结束,'a'仅在此处初始化,值为20
    let a = 20;
}

下表概述了与JavaScript中使用的不同关键字声明的变量对应的提升行为和使用域:

clipboard.png

    • -

问题 3: [3, 3, 3]

解析:

for循环的头部声明带有var关键字的变量会为该变量创建单个绑定(存储空间)。 阅读更多关于[闭包][3]的信息。 让我们再看一次for循环。

// 误解作用域:认为存在块级作用域
var array = [];
for (var i = 0; i < 3; i++) {
  // 三个箭头函数体中的每个`'i'`都指向相同的绑定,
  // 这就是为什么它们在循环结束时返回相同的值'3'。
  array.push(() => i);
}
var newArray = array.map(el => el());
console.log(newArray); // [3, 3, 3]

如果使用 let 声明一个具有块级作用域的变量,则为每个循环迭代创建一个新的绑定。

// 使用ES6块级作用域
var array = [];
for (let i = 0; i < 3; i++) {
  // 这一次,每个'i'指的是一个新的的绑定,并保留当前的值。
 // 因此,每个箭头函数返回一个不同的值。
  array.push(() => i);
}
var newArray = array.map(el => el());
console.log(newArray); // [0, 1, 2]

解决这个问题的另一种方法是使用[闭包][4]。

let array = [];
for (var i = 0; i < 3; i++) {

  array[i] = (function(x) {
    return function() {
      return x;
    };
  })(i);
}
const newArray = array.map(el => el());
console.log(newArray); // [0, 1, 2]
    • -

问题4 : 不会溢出

解析:

JavaScript并发模型基于“事件循环”。 当我们说“浏览器是 JS 的家”时我真正的意思是浏览器提供运行时环境来执行我们的JS代码。

浏览器的主要组件包括调用堆栈事件循环**,任务队列Web API**。 像setTimeoutsetIntervalPromise这样的全局函数不是JavaScript的一部分,而是 Web API 的一部分。 JavaScript 环境的可视化形式如下所示:

clipboard.png

JS调用栈是后进先出(LIFO)的。引擎每次从堆栈中取出一个函数,然后从上到下依次运行代码。每当它遇到一些异步代码,如setTimeout,它就把它交给Web API(箭头1)。因此,每当事件被触发时,callback 都会被发送到任务队列(箭头2)。

事件循环(Event loop)不断地监视任务队列(Task Queue),并按它们排队的顺序一次处理一个回调。每当调用堆栈(call stack)为空时,Event loop获取回调并将其放入堆栈(stack )(箭头3)中进行处理。请记住,如果调用堆栈不是空的,则事件循环不会将任何回调推入堆栈

现在,有了这些知识,让我们来回答前面提到的问题:

步骤

  1. 调用 foo()会将foo函数放入调用堆栈(call stack)
  2. 在处理内部代码时,JS引擎遇到setTimeout
  3. 然后将foo回调函数传递给WebAPIs(箭头1)并从函数返回,调用堆栈再次为空
  4. 计时器被设置为0,因此foo将被发送到
    任务队列
    (箭头2)。
  5. 由于调用堆栈是空的,事件循环将选择foo回调并将其推入调用堆栈进行处理。
  6. 进程再次重复,堆栈不会溢出。

运行示意图如下所示:

\![图片描述][5]

    • -

问题5 : 不会响应

解析:

大多数时候,开发人员假设在
事件循环图中只有一个任务队列。但事实并非如此,我们可以有多个任务队列。由浏览器选择其中的一个队列并在该队列中处理回调



在底层来看,JavaScript中有宏任务和微任务。setTimeout回调是宏任务,而Promise回调是微任务

主要的区别在于他们的执行方式。宏任务在单个循环周期中一次一个地推入堆栈,但是微任务队列总是在执行后返回到事件循环之前清空。因此,如果你以处理条目的速度向这个队列添加条目,那么你就永远在处理微任务。只有当微任务队列为空时,事件循环才会重新渲染页面、

现在,当你在控制台中运行以下代码段

function foo() {
  return Promise.resolve().then(foo);
};

每次调用'foo'都会继续在微任务队列上添加另一个'foo'回调,因此事件循环无法继续处理其他事件(滚动,单击等),直到该队列完全清空为止。 因此,它会阻止渲染。

    • -

问题6 : 会导致TypeError错误

解析:

[展开语法][6] 和 [for-of][7] 语句遍历iterable对象定义要遍历的数据。ArrayMap 是具有默认迭代行为的内置迭代器。对象不是可迭代的,但是可以通过使用[iterable][8]和[iterator][9]协议使它们可迭代。

Mozilla文档中,如果一个对象实现了@@iterator方法,那么它就是可迭代的,这意味着这个对象(或者它原型链上的一个对象)必须有一个带有@@iterator键的属性,这个键可以通过常量Symbol.iterator获得。

上述语句可能看起来有点冗长,但是下面的示例将更有意义:

var obj = { x: 1, y: 2, z: 3 };
obj[Symbol.iterator] = function() {

  // iterator 是一个具有 next 方法的对象,
  // 它的返回至少有一个对象
  // 两个属性:value&done。

  // 返回一个 iterator 对象
  return {
    next: function() {
      if (this._countDown === 3) {
        const lastValue = this._countDown;
        return { value: this._countDown, done: true };
      }
      this._countDown = this._countDown + 1;
      return { value: this._countDown, done: false };
    },
    _countDown: 0
  };
};
[...obj]; // 打印 [1, 2, 3]

还可以使用 [generator][10] 函数来定制对象的迭代行为:

var obj = {x:1, y:2, z: 3}
obj[Symbol.iterator] = function*() {
  yield 1;
  yield 2;
  yield 3;
}
[...obj]; // 打印 [1, 2, 3]
    • -

问题7 : a, b, c

解析:

for-in循环遍历对象本身的[可枚举属性][11]以及对象从其原型继承的属性。 可枚举属性是可以在for-in循环期间包含和访问的属性。

var obj = { a: 1, b: 2 };
var descriptor = Object.getOwnPropertyDescriptor(obj, "a");
console.log(descriptor.enumerable); // true
console.log(descriptor);
// { value: 1, writable: true, enumerable: true, configurable: true }

现在你已经掌握了这些知识,应该很容易理解为什么我们的代码要打印这些特定的属性

var obj = { a: 1, b: 2 }; //a,b 都是 enumerables 属性

// 将{c:3}设置为'obj'的原型,并且我们知道
// for-in 循环也迭代 obj 继承的属性
// 从它的原型,'c'也可以被访问。
Object.setPrototypeOf(obj, { c: 3 });

// 我们在'obj'中定义了另外一个属性'd',但是 
// 将'enumerable'设置为false。 这意味着'd'将被忽略。
Object.defineProperty(obj, "d", { value: 4, enumerable: false });

for (let prop in obj) {
  console.log(prop);
}
// 打印
// a
// b
// c
    • -

\#\#\#\# 问题8 : 10

解析:

在全局范围内初始化x时,它成为window对象的属性(不是严格的模式)。看看下面的代码:

var x = 10; // global scope
var foo = {
  x: 90,
  getX: function() {
    return this.x;
  }
};
foo.getX(); // prints 90
let xGetter = foo.getX;
xGetter(); // prints 10

咱们可以断言:

window.x === 10; // true

this 始终指向调用方法的对象。因此,在foo.getx()的例子中,它指向foo对象,返回90的值。而在xGetter()的情况下,this指向 window对象, 返回 window 中的x的值,即10

要获取 foo.x的值,可以通过使用Function.prototype.bindthis的值绑定到foo对象来创建新函数。

let getFooX = foo.getX.bind(foo);
getFooX(); // 90

就这样! 如果你的所有答案都正确,那么干漂亮。 咱们都是通过犯错来学习的。 这一切都是为了了解背后的“原因”。

    • -

代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

原文:https://dev.to/aman_singh/so-...

交流

干货系列文章汇总如下,觉得不错点个Star,欢迎 加群 互相学习。

https://github.com/qq44924588...

我是小智,公众号「大迁世界」作者,对前端技术保持学习爱好者。我会经常分享自己所学所看的干货,在进阶的路上,共勉!

关注公众号,后台回复福利,即可看到福利,你懂的。

clipboard.png

[1]: https://www.fundebug.com/?utm\_source=xiaozhi

查看原文

赞 3 收藏 3 评论 0

前端小智 发布了文章 · 4月6日

记得有一次面试被虐的题,Promise 完整指南

作者:Adrian Mejia
译者:前端小智
来源:adrianmjia

点赞再看,微信搜索大迁世界,B站关注前端小智这个没有大厂背景,但有着一股向上积极心态人。本文 GitHubhttps://github.com/qq44924588... 上已经收录,文章的已分类,也整理了很多我的文档,和教程资料。

最近开源了一个 Vue 组件,还不够完善,欢迎大家来一起完善它,也希望大家能给个 star 支持一下,谢谢各位了。

github 地址:https://github.com/qq44924588...

这篇文章算是 JavaScript Promises 比较全面的教程,该文介绍了必要的方法,例如 thencatchfinally。 此外,还包括处理更复杂的情况,例如与Promise.all并行执行Promise,通过Promise.race 来处理请求超时的情况,Promise 链以及一些最佳实践和常见的陷阱。

1.JavaScript Promises

Promise 是一个允许我们处理异步操作的对象,它是 es5 早期回调的替代方法。

与回调相比,Promise 具有许多优点,例如:

  • 让异步代码更易于阅读。
  • 提供组合错误处理。
    * 更好的流程控制,可以让异步并行或串行执行。

回调更容易形成深度嵌套的结构(也称为回调地狱)。 如下所示:

a(() => {
  b(() => {
    c(() => {
      d(() => {
        // and so on ...
      });
    });
  });
});

如果将这些函数转换为 Promise,则可以将它们链接起来以生成更可维护的代码。 像这样:

Promise.resolve()
  .then(a)
  .then(b)
  .then(c)
  .then(d)
  .catch(console.error);

在上面的示例中,Promise 对象公开了.then.catch方法,我们稍后将探讨这些方法。

1.1 如何将现有的回调 API 转换为 Promise?

我们可以使用 Promise 构造函数将回调转换为 Promise。

Promise 构造函数接受一个回调,带有两个参数resolvereject

  • Resolve:是在异步操作完成时应调用的回调。
  • Reject:是发生错误时要调用的回调函数。

构造函数立即返回一个对象,即 Promise 实例。 当在 promise 实例中使用.then方法时,可以在Promise “完成” 时得到通知。 让我们来看一个例子。

Promise 仅仅只是回调?

并不是。承诺不仅仅是回调,但它们确实对.then.catch方法使用了异步回调。 Promise 是回调之上的抽象,我们可以链接多个异步操作并更优雅地处理错误。来看看它的实际效果。

Promise 反面模式(Promises 地狱)

a(() => {
  b(() => {
    c(() => {
      d(() => {
        // and so on ...
      });
    });
  });
});

不要将上面的回调转成下面的 Promise 形式:

a().then(() => {
  return b().then(() => {
    return c().then(() => {
      return d().then(() =>{
        // ⚠️ Please never ever do to this! ⚠️
      });
    });
  });
});

上面的转成,也形成了 Promise 地狱,千万不要这么转。相反,下面这样做会好点:

a()
  .then(b)
  .then(c)
  .then(d)

超时

你认为以下程序的输出的是什么?

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('time is up ⏰');
  }, 1e3);

  setTimeout(() => {
    reject('Oops 🔥');
  }, 2e3);
});

promise
  .then(console.log)
  .catch(console.error);

是输出:

time is up ⏰
Oops! 🔥

还是输出:

time is up ⏰

是后者,因为当一个Promise resolved 后,它就不能再被rejected

一旦你调用一种方法(resolvereject),另一种方法就会失效,因为 promise 处于稳定状态。 让我们探索一个 promise 的所有不同状态。

1.2 Promise 状态

Promise 可以分为四个状态:

  • ⏳ Pending:初始状态,异步操作仍在进行中。
  • ✅ Fulfilled:操作成功,它调用.then回调,例如.then(onSuccess)
  • ⛔️ Rejected: 操作失败,它调用.catch.then的第二个参数(如果有)。 例如.catch(onError).then(..., onError)
  • 😵 Settled:这是 promise 的最终状态。promise 已经死亡了,没有别的办法可以解决或拒绝了。 .finally方法被调用。

clipboard.png

大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】

1.3 Promise 实例方法

Promise API 公开了三个主要方法:thencatchfinally。 我们逐一配合事例探讨一下。

Promise then

then方法可以让异步操作成功或失败时得到通知。 它包含两个参数,一个用于成功执行,另一个则在发生错误时使用。

promise.then(onSuccess, onError);

你还可以使用catch来处理错误:

promise.then(onSuccess).catch(onError);

Promise 链

then 返回一个新的 Promise ,这样就可以将多个Promise 链接在一起。就像下面的例子一样:

Promise.resolve()
  .then(() => console.log('then#1'))
  .then(() => console.log('then#2'))
  .then(() => console.log('then#3'));

Promise.resolve立即将Promise 视为成功。 因此,以下所有内容都将被调用。 输出将是

then#1
then#2
then#3

Promise catch

Promise .catch方法将函数作为参数处理错误。 如果没有出错,则永远不会调用catch方法。

假设我们有以下承诺:1秒后解析或拒绝并打印出它们的字母。

const a = () => new Promise((resolve) => setTimeout(() => { console.log('a'), resolve() }, 1e3));
const b = () => new Promise((resolve) => setTimeout(() => { console.log('b'), resolve() }, 1e3));
const c = () => new Promise((resolve, reject) => setTimeout(() => { console.log('c'), reject('Oops!') }, 1e3));
const d = () => new Promise((resolve) => setTimeout(() => { console.log('d'), resolve() }, 1e3));

请注意,c使用reject('Oops!')模拟了拒绝。

Promise.resolve()
  .then(a)
  .then(b)
  .then(c)
  .then(d)
  .catch(console.error)

输出如下:

图片描述

在这种情况下,可以看到abc上的错误消息。

我们可以使用then函数的第二个参数来处理错误。 但是,请注意,catch将不再执行。

Promise.resolve()
  .then(a)
  .then(b)
  .then(c)
  .then(d, () => console.log('c errored out but no big deal'))
  .catch(console.error)

图片描述

由于我们正在处理 .then(..., onError)部分的错误,因此未调用catchd不会被调用。 如果要忽略错误并继续执行Promise链,可以在c上添加一个catch。 像这样:

Promise.resolve()
  .then(a)
  .then(b)
  .then(() => c().catch(() => console.log('error ignored')))
  .then(d)
  .catch(console.error)

图片描述

当然,这种过早的捕获错误是不太好的,因为容易在调试过程中忽略一些潜在的问题。

Promise finally

finally方法只在 Promise 状态是 settled 时才会调用。

如果你希望一段代码即使出现错误始终都需要执行,那么可以在.catch之后使用.then

Promise.resolve()
  .then(a)
  .then(b)
  .then(c)
  .then(d)
  .catch(console.error)
  .then(() => console.log('always called'));

或者可以使用.finally关键字:

Promise.resolve()
  .then(a)
  .then(b)
  .then(c)
  .then(d)
  .catch(console.error)
  .finally(() => console.log('always called'));

1.4 Promise 类方法

我们可以直接使用 Promise 对象中四种静态方法。

  • Promise.all
  • Promise.reject
  • Promise.resolve
  • Promise.race

Promise.resolve 和 Promise.reject

这两个是帮助函数,可以让 Promise 立即解决或拒绝。可以传递一个参数,作为下次 .then 的接收:

Promise.resolve('Yay!!!')
  .then(console.log)
  .catch(console.error)

上面会输出 Yay!!!

Promise.reject('Oops 🔥')
  .then(console.log)
  .catch(console.error)

使用 Promise.all 并行执行多个 Promise

通常,Promise 是一个接一个地依次执行的,但是你也可以并行使用它们。

假设是从两个不同的api中轮询数据。如果它们不相关,我们可以使用Promise.all()同时触发这两个请求。

在此示例中,主要功能是将美元转换为欧元,我们有两个独立的 API 调用。 一种用于BTC/USD,另一种用于获得EUR/USD。 如你所料,两个 API 调用都可以并行调用。 但是,我们需要一种方法来知道何时同时完成最终价格的计算。 我们可以使用Promise.all,它通常在启动多个异步任务并发运行并为其结果创建承诺之后使用,以便人们可以等待所有任务完成。

const axios = require('axios');

const bitcoinPromise = axios.get('https://api.coinpaprika.com/v1/coins/btc-bitcoin/markets');
const dollarPromise = axios.get('https://api.exchangeratesapi.io/latest?base=USD');
const currency = 'EUR';

// Get the price of bitcoins on
Promise.all([bitcoinPromise, dollarPromise])
  .then(([bitcoinMarkets, dollarExchanges]) => {
    const byCoinbaseBtc = d => d.exchange_id === 'coinbase-pro' && d.pair === 'BTC/USD';
    const coinbaseBtc = bitcoinMarkets.data.find(byCoinbaseBtc)
    const coinbaseBtcInUsd = coinbaseBtc.quotes.USD.price;
    const rate = dollarExchanges.data.rates[currency];
    return rate * coinbaseBtcInUsd;
  })
  .then(price => console.log(`The Bitcoin in ${currency} is ${price.toLocaleString()}`))
  .catch(console.log);

如你所见,Promise.all接受了一系列的 Promises。 当两个请求的请求都完成后,我们就可以计算价格了。

我们再举一个例子:

const a = () => new Promise((resolve) => setTimeout(() => resolve('a'), 2000));
const b = () => new Promise((resolve) => setTimeout(() => resolve('b'), 1000));
const c = () => new Promise((resolve) => setTimeout(() => resolve('c'), 1000));
const d = () => new Promise((resolve) => setTimeout(() => resolve('d'), 1000));

console.time('promise.all');
Promise.all([a(), b(), c(), d()])
  .then(results => console.log(`Done! ${results}`))
  .catch(console.error)
  .finally(() => console.timeEnd('promise.all'));

解决这些 Promise 要花多长时间? 5秒? 1秒? 还是2秒?

这个留给你们自己验证咯。

Promise race

Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。

const a = () => new Promise((resolve) => setTimeout(() => resolve('a'), 2000));
const b = () => new Promise((resolve) => setTimeout(() => resolve('b'), 1000));
const c = () => new Promise((resolve) => setTimeout(() => resolve('c'), 1000));
const d = () => new Promise((resolve) => setTimeout(() => resolve('d'), 1000));

console.time('promise.race');
Promise.race([a(), b(), c(), d()])
  .then(results => console.log(`Done! ${results}`))
  .catch(console.error)
  .finally(() => console.timeEnd('promise.race'));

输出是什么?

输出 b。使用 Promise.race,最先执行完成就会结果最后的返回结果。

你可能会问:Promise.race的用途是什么?

我没胡经常使用它。但是,在某些情况下,它可以派上用场,比如计时请求或批量处理请求数组。

Promise.race([
  fetch('http://slowwly.robertomurray.co.uk/delay/3000/url/https://api.jsonbin.io/b/5d1fb4dd138da811182c69af'),
  new Promise((resolve, reject) => setTimeout(() => reject(new Error('request timeout')), 1000))
])
.then(console.log)
.catch(console.error);

图片描述

如果请求足够快,那么就会得到请求的结果。

图片描述

大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】

1.5 Promise 常见问题

串行执行 promise 并传递参数

这次,我们将对Node的fs使用promises API,并将两个文件连接起来:

const fs = require('fs').promises; // requires node v8+

fs.readFile('file.txt', 'utf8')
  .then(content1 => fs.writeFile('output.txt', content1))
  .then(() => fs.readFile('file2.txt', 'utf8'))
  .then(content2 => fs.writeFile('output.txt', content2, { flag: 'a+' }))
  .catch(error => console.log(error));

在此示例中,我们读取文件1并将其写入output 文件。 稍后,我们读取文件2并将其再次附加到output文件。 如你所见,writeFile promise返回文件的内容,你可以在下一个then子句中使用它。

如何链接多个条件承诺?

你可能想要跳过 Promise 链上的特定步骤。有两种方法可以做到这一点。

const a = () => new Promise((resolve) => setTimeout(() => { console.log('a'), resolve() }, 1e3));
const b = () => new Promise((resolve) => setTimeout(() => { console.log('b'), resolve() }, 2e3));
const c = () => new Promise((resolve) => setTimeout(() => { console.log('c'), resolve() }, 3e3));
const d = () => new Promise((resolve) => setTimeout(() => { console.log('d'), resolve() }, 4e3));

const shouldExecA = true;
const shouldExecB = false;
const shouldExecC = false;
const shouldExecD = true;

Promise.resolve()
  .then(() => shouldExecA && a())
  .then(() => shouldExecB && b())
  .then(() => shouldExecC && c())
  .then(() => shouldExecD && d())
  .then(() => console.log('done'))

如果你运行该代码示例,你会注意到只有ad被按预期执行。

另一种方法是创建一个链,然后仅在以下情况下添加它们:

const chain = Promise.resolve();

if (shouldExecA) chain = chain.then(a);
if (shouldExecB) chain = chain.then(b);
if (shouldExecC) chain = chain.then(c);
if (shouldExecD) chain = chain.then(d);

chain
  .then(() => console.log('done'));

如何限制并行 Promise?

要做到这一点,我们需要以某种方式限制Promise.all

假设你有许多并发请求要执行。 如果使用 Promise.all 是不好的(特别是在API受到速率限制时)。 因此,我们需要一个方法来限制 Promise 个数, 我们称其为promiseAllThrottled

// simulate 10 async tasks that takes 5 seconds to complete.
const requests = Array(10)
  .fill()
  .map((_, i) => () => new Promise((resolve => setTimeout(() => { console.log(`exec'ing task #${i}`), resolve(`task #${i}`); }, 5000))));

promiseAllThrottled(requests, { concurrency: 3 })
  .then(console.log)
  .catch(error => console.error('Oops something went wrong', error));

输出应该是这样的:

图片描述

以上代码将并发限制为并行执行的3个任务。

实现promiseAllThrottled 一种方法是使用Promise.race来限制给定时间的活动任务数量。

/**
 * Similar to Promise.all but a concurrency limit
 *
 * @param {Array} iterable Array of functions that returns a promise
 * @param {Object} concurrency max number of parallel promises running
 */
function promiseAllThrottled(iterable, { concurrency = 3 } = {}) {
  const promises = [];

  function enqueue(current = 0, queue = []) {
    // return if done
    if (current === iterable.length) { return Promise.resolve(); }
    // take one promise from collection
    const promise = iterable[current];
    const activatedPromise = promise();
    // add promise to the final result array
    promises.push(activatedPromise);
    // add current activated promise to queue and remove it when done
    const autoRemovePromise = activatedPromise.then(() => {
      // remove promise from the queue when done
      return queue.splice(queue.indexOf(autoRemovePromise), 1);
    });
    // add promise to the queue
    queue.push(autoRemovePromise);

    // if queue length >= concurrency, wait for one promise to finish before adding more.
    const readyForMore = queue.length < concurrency ? Promise.resolve() : Promise.race(queue);
    return readyForMore.then(() => enqueue(current + 1, queue));
  }

  return enqueue()
    .then(() => Promise.all(promises));
}

promiseAllThrottled一对一地处理 Promises 。 它执行Promises并将其添加到队列中。 如果队列小于并发限制,它将继续添加到队列中。 达到限制后,我们使用Promise.race等待一个承诺完成,因此可以将其替换为新的承诺。 这里的技巧是,promise 自动完成后会自动从队列中删除。 另外,我们使用 race 来检测promise 何时完成,并添加新的 promise 。

人才们的 【三连】 就是小智不断分享的最大动力,如果本篇博客有任何错误和建议,欢迎人才们留言,最后,谢谢大家的观看。


代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

原文:https://adrianmejia.com/promi...

交流

文章每周持续更新,可以微信搜索「 大迁世界 」第一时间阅读和催更(比博客早一到两篇哟),本文 GitHub https://github.com/qq449245884/xiaozhi 已经收录,整理了很多我的文档,欢迎Star和完善,大家面试可以参照考点复习,另外关注公众号,后台回复福利,即可看到福利,你懂的。

查看原文

赞 15 收藏 9 评论 0

前端小智 发布了文章 · 3月30日

10 个最常见的 Javascript 问题

作者:Duomly
译者:前端小智
来源:dev

点赞再看,微信搜索大迁世界,B站关注前端小智这个没有大厂背景,但有着一股向上积极心态人。本文 GitHubhttps://github.com/qq44924588... 上已经收录,文章的已分类,也整理了很多我的文档,和教程资料。

为初学者介绍一下这 10 个最常被问到的 JavaScript 问题

在本文中,我收集了关于Javascript 最常被问到的 10 个问题及其答案。

这10 个问题大多涉及 Javascript 的基础知识,所以如果你刚刚开始学习 JS,最好理解并掌握它们并。

这个 10 问题涉及 JS 中闭包promise变量提升等等。尽管这些知识不是很难,但是知道答案是一件好事,因为其中一些经常在面试中会被问到。

Javascript 中的闭包是什么?

闭包是封闭在一起的函数的组合,其中内部函数可以访问其变量和外部函数的变量。

最简单的解释方法就是上例子:

function outer() {
  var name = 'Maria';
  function inner() {
    console.log(name);
  }
  inner();
}
outer();
// 'Maria'

在上面的代码中,你可以看到inner()函数可以访问其父函数变量name。 因此,如果调用outer()函数,那么inner()函数的console.log()将返回name的值Maria

内部函数可以访问外部函数参数对象,但是内部函数参数与外部一样,则内部的参数对象会覆盖外部的参数对象。如下所示:

function outer(a, b) {
  const inner = (a, b) => console.log(a, b);
  inner(1, 2);
}
outer('Alice', 'Mark');
// returns 1, 2

我们使用闭包的主要原因是返回可以返回其他函数的函数。

Javascript中的 DOM 是什么

DOM 是文档对象模型,它是网站的面向对象的表示形,可以使用 Javascript 进行修改。

使用 JS 可以操纵 DOM 元素,例如颜色,位置,大小。 为了选择页面的特定元素,Javascript 提供了一些方法:

  • getElementById() - 通过id属性选择一个元素
  • getElementsByName() - 通过name属性选择一个元素
  • getElementsByTagName() - 选择所选标签的所有元素,
  • getElementsbyClassName() - 选择特定类名的所有元素

* querySelector() - 通过CSS选择器选择元素。

Javascript 还提供了其他操作元素的方法,而不仅仅是获取元素,比如appendChild()innerHTML()

Javascript 的 Promise 是什么

Promise 是异步编程的一种解决方案,可以替代传统的解决方案--回调函数和事件。ES6统一了用法,并原生提供了Promise对象。作为对象,Promise 有一下两个特点: (1)对象的状态不受外界影响。 (2)一旦状态改变了就不会在变,也就是说任何时候 Promise 都只有一种状态。

Promise 有三种状态,分别是:Pending (进行中), Resolved (已完成), Rejected (已失败)。Promise 从 Pending 状态开始,如果成功就转到成功态,并执行resolve回调函数;如果失败就转到失败状态并执行reject回调函数。

如果 Promise 被解析(resolved),我们可以调用then()方法并使用返回值执行操作。如果被拒绝(rejected),我们可以使用catch()方法来处理错误。

处理异步编程的其他方法还有async/awaitcallbacks

Javascript 中的原型是什么?

原型通常指的是prototype__proto__这两个原型对象,其中前者叫做显式原型对象,后者叫做隐式原型对象。

Javascript对象从原型继承方法和属性,而Object.prototype在继承链的顶部。Javascript prototype关键字还可以用于向构造函数添加新值和方法。

来看看事例:

function Animal(name, kind, age) {
  this.name = name;
  this.kind = kind;
  this.age = age;
}

Animal.prototype.ownerName('Mark');

可以看到,通过使用原型,我们能够将ownerName属性添加到Animal()构造函数中。

Javascript 的 变量提升 是什么

提升是一种机制,它将所有声明的变量和函数提升到它们局部作用域的顶部,如果变量和函数被放置在全局作用域,则会被提升到全局作用域的顶部。

Javascript中,可以在变量被使用后在声明它。

提升用于避免在变量或函数有在没有定义之前就执行导致的 undefined 错误。

name = 'Ted';
console.log(name);
var name;
// 'Ted'


var name;
name = 'Ted';
console.log(name);
// 'Ted';

使用 var 声明的变量,如果没有赋值,则默认会被初始化为 undefined, letconst 则不会。另外,需要注意的是,在声明const时,必须同时初始化它,因为后面不可在更改它。

Javascript中的对象是什么

对象只是一种特殊的数据。对象拥有属性和方法。JavaScript 中的所有事物都是对象,如:字符串、数值、数组、函数等。

对象的属性:反映该对象某些特定的性质的,如:字符串的长度、图像的长宽等;

对象的方法:能够在对象上执行的动作。例如,表单的“提交”(Submit),时间的“获取”(getYear)等;

属性只是简单的值,而方法是可以在对象上执行的操作。

var student = {
  firstName: 'Alice',
  lastName: 'Jones',
  age: 21,
  sayHi: () => {
    return 'Hi, I am ' + this.firstName;
  }
}

在上面的代码中,你可以看到Student对象,其中包含三个属性和一个方法。

Javascript 中的函数是什么

在javascript中函数是一段可以被执行或调用任意次数的JavasScript代码,在数据类型中属于"function"。函数也拥有属性和方法,因此函数也是对象。

在Javascript中函数定义函数声明或函数表达式由关键字function开始。在定义函数时,可以在函数名后面的括号中添加一些参数。当我们调用函数时,括号中传递的值称为参数。

function calculate(x, y) {
  return x * y;
}

calculate(2, 5);

Javascript中的纯函数是什么

如果函数的调用参数相同,则永远返回相同的结果。它不依赖于程序执行期间函数外部任何状态或数据的变化,必须只依赖于其输入参数。

顾名思义,纯函数跟我们初中数学的基本函数一样,遵循一定的映射关系,输入决定输出,一个输入只能对应一个输出。不同的输入可以有相同的输出,但是相同的输入不能有不同的输出

一个函数,如果符合以下两个特点,那么它就可以称之为 纯函数:

  • 对于相同的输入,永远得到相同的输出
  • 没有任何可观察到的副作用

Javascript中的构造函数是什么

构造函数是一种特殊的方法,用于初始化和创建 Javascript 中的对象。

JavaScript 中的构造函数和其它语言中的构造函数是不同的。 通过 new 关键字方式调用的函数都被认为是构造函数。

在构造函数内部,this 指向新创建的对象 Object。 这个新创建的对象的 prototype 被指向到构造函数的 prototype

如果被调用的函数没有显式的 return 表达式,则隐式的会返回 this 对象,也就是新创建的对象。

const Person = (name, age) => {
  this.name = name;
  this.age = age;
}

var man = new Person('Mark', 23);
console.log(man);
// { name: 'Mark', age: 23 }

在上面的代码中,我创建了一个Person构造函数,在下面的代码中,创建了一个名为man的新变量,并基于Person构造函数创建了一个新对象。

Javascript类是什么?

自从 ES6 引入以来,我们可以在Javascript中使用类。 类是一种函数,我们使用关键字class代替function 关键字来初始化它。

除此之外,我们还必须在类内部添加constructor()方法,该方法在每次初始化类时都会调用。

constructor()方法内部,我们添加了类的属性。 要基于现有的类创建另一个类,我们可以使用extends关键字。

在JavaScript中使用类的一个很好的例子是 React 框架,它是类的组件。

总结

在本文中,我收集了开发者经常问的 10 个Javascript问题,并给出答案,答案不是唯一,这里只是自己的一些见解,希望本文能给初始化者带来一些帮助。

人才们的 【三连】 就是小智不断分享的最大动力,如果本篇博客有任何错误和建议,欢迎人才们留言,最后,谢谢大家的观看。


原文:https://dev.to/duomly/10-most...

代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

交流

文章每周持续更新,可以微信搜索「 大迁世界 」第一时间阅读和催更(比博客早一到两篇哟),本文 GitHub https://github.com/qq449245884/xiaozhi 已经收录,整理了很多我的文档,欢迎Star和完善,大家面试可以参照考点复习,另外关注公众号,后台回复福利,即可看到福利,你懂的。

查看原文

赞 11 收藏 6 评论 0

前端小智 发布了文章 · 3月26日

ES10 中 Object.fromEntries() 是个啥?

作者:Shadeed
译者:前端小智
来源:dmitripavlutin
点赞再看,微信搜索大迁世界,B站关注前端小智这个没有大厂背景,但有着一股向上积极心态人。本文 GitHubhttps://github.com/qq44924588... 上已经收录,文章的已分类,也整理了很多我的文档,和教程资料。

最近开源了一个 Vue 组件,还不够完善,欢迎大家来一起完善它,也希望大家能给个 star 支持一下,谢谢各位了。

github 地址:https://github.com/qq44924588...

我们知道 Object.entries() 是将对象转成一个自身可枚举属性的键值对数组。同样,我们也可以把键值对数组转成了对象。

const keyValuePair = [
  ['cow', '?'],
  ['pig', '?'],
]

Object.fromEntries(keyValuePair);
// { cow: '?', pig: '?' }

 Object.fromEntries

我们知道,对象结构是一个是有键和值组合体,如下所示:

const object = {
  key: 'value',
};

基于这个逻辑,如果我们想将某个东西转成对象,就必须要传递键和值

有两种类型的参数可以满足这些要求:

  1. 具有嵌套键值对的数组
  2. Map 对象

使用 Object.fromEntries 将数组转成对象

下面是个键-值对嵌套数组

const nestedArray = [
  ['key 1', 'value 1'],
  ['key 2', 'value 2']
]

使用Object.fromEntries可以将该数组转成对象:

Object.fromEntries(nestedArray);
// { key 1: "value 1", key 2: "value 2"}

使用 Object.fromEntries 将 Map 转成对象

ES6 为我们带来了一个名为 map 的新对象,它与对象非常相似。

TC39:映射对象是键/值对的集合,其中键和值都可以是任意 ECMAScript 语言值。

我们来创建一个 Map 对象:

// 使用构造函数
const map = new Map([
  ['key 1', 'value 1'],
  ['key 2', 'value 2']
])

// 或者我们可以使用实例方法“set”
const map = new Map()
map.set('key 1', 'value 1')
map.set('key 2', 'value 2')

// 结果
Map(2) {"key 1" => "value 1", "key 2" => "value 2"}

让我们使用Object.fromentriesmap 转换为一个对象

Object.fromEntries(map);
// { key 1: "value 1", key 2: "value 2"}

对象的类型错误:试图使用 Object.fromEntries 将 其它类型 转成对象

将下面的类型传入 Object.fromEntries 都会导致报错 caught TypeError

clipboard.png

确保传递值要有键-值对

大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】

Object.fromEntries vs Object.entries

Object.fromEntriesObject.entries 反向。 Object.entries()方法返回一个给定对象自身可枚举属性的键值对数组,而Object.fromEntries() 方法把键值对列表转换为一个对象。

const object = { key1: 'value1', key2: 'value2' }

const array = Object.entries(object)
// [ ["key1", "value1"], ["key2", "value2"] ]


Object.fromEntries(array)
// { key1: 'value1', key2: 'value2' }

对象到对象的转换

如果你阅读了 TC39 提案,这就是引入此新方法的原因。 随着Object.entries的引入之前,要将一些非对象结构转成对象是比较麻烦的。

通常,当我们选择使用Object.entries,是因为它使我们能够访问许多漂亮的数组方法,例如filter。 但是在完成转换之后,我们有点被该数组所困扰。

const food = { meat: '?', broccoli: '?', carrot: '?' }

// ? 卡在这结果上...
const vegetarian = Object.entries(food).filter(
  ([key, value]) => key !== 'meat',
)
// [ ["broccoli", "?"], ["carrot", "?"] ]

好吧! 我们可以利用所有这些有用的数组方法,但仍然可以返回对象。 最后,从对象到对象的转换?

const food = { meat: '?', broccoli: '?', carrot: '?' }

const vegetarian = Object.fromEntries(
  Object.entries(food).filter(([key, value]) => key !== 'meat'),
)

// { broccoli: '?', carrot: '?' }

数组转成对象的替代方案

Object.fromEntries是 ES10 推出来,很新,可能浏览器支持度还够友好。 因此,让我们看一下如果将具有键值对结构的数组转成对象。

使用 reduce 方法将数组转成对象

将数组转换为对象的一种流行方法是使用reduce

const array = [
  ['key1', 'value1'],
  ['key2', 'value2']
]

const map = new Map([
  ['key1', 'value1'],
  ['key2', 'value2']
])

function toObject(pairs) {
  return Array.from(pairs).reduce(
    (acc, [key, value]) => Object.assign(acc, { [key]: value}),
    {}
  )
}


// 结果
// { key1: 'value1', key2: 'value2' }

使用 库 将数组转成对象

Lodash 也提供了将键值对转换为对象的方法。

_.object
将数组转换为对象。 传递[key, value]对的单个列表,或键的列表和值的列表。
const array = [
  ['key1', 'value1'],
  ['key2', 'value2']
]

_.object(array)
// { key1: 'value1', key2: 'value2' }
_.fromPairs

_.fromPairs_.toPairs 的反向,它的方法返回一个由键值对组成的对象。

const array = [
  ['key1', 'value1'],
  ['key2', 'value2'],
]

_.fromPairs(array)
// { key1: 'value1', key2: 'value2' }

浏览器支持情况

clipboard.png


原文:https://medium.com/@samantham...

代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

交流

文章每周持续更新,可以微信搜索「 大迁世界 」第一时间阅读和催更(比博客早一到两篇哟),本文 GitHub https://github.com/qq449245884/xiaozhi 已经收录,整理了很多我的文档,欢迎Star和完善,大家面试可以参照考点复习,另外关注公众号,后台回复福利,即可看到福利,你懂的。

查看原文

赞 2 收藏 2 评论 1

前端小智 发布了文章 · 3月25日

【Vue3教程】创建你的第一个Vue 3项目

作者:Shadeed
译者:前端小智
来源:dmitripavlutin
点赞再看,微信搜索大迁世界,B站关注前端小智这个没有大厂背景,但有着一股向上积极心态人。本文 GitHubhttps://github.com/qq44924588... 上已经收录,文章的已分类,也整理了很多我的文档,和教程资料。

最近开源了一个 Vue 组件,还不够完善,欢迎大家来一起完善它,也希望大家能给个 star 支持一下,谢谢各位了。

github 地址:https://github.com/qq44924588...

2021年2月15日Vue 3正式发布!在尤雨溪的声明中,他宣布了新框架中最大的变化,并谈论了整个Vue团队所做的出色工作。

长期以来,开发者一直在等待Vue 3宣布的真正酷的特性,比如Typescript支持、对大型项目更好的组织、及使Vue应用程序更好的渲染优化。

本文中我们要做以下的内容,使用组合API构建了两个组件。

image

开始

有几种不同的选项可用于将Vue 3添加到现有项目或创建自己的Vue 3项目。

这里,我用自己最喜欢的两个选项:

  1. Vue CLI
  2. Vite

Vue CLI

如果你用过Vue开发,那么很可能使用了Vue CLI来设置项目。

首先,我们必须确保拥有Vue CLI的最新版本,可以通过在终端上运行 npm update -g @vue/cli 来做到这一点。

接下来,创建项目,运行 vue create <项目名>,如果 CLI是最新的,我们就可以选择Vue 3。

image.png

选择了Vue 3选项,我们的应用程序便会构建。 完成后,我们只需要进入我们的项目,然后运行我们的Vue应用, 该命令是:

cd <项目我>
npm run serve

现在,在浏览器中输入http://localhost:8080/,就会看到我们的应用程序!

image.png

Vite

Vite (法语意为 "快速的",发音 /vit/) 是一种新型前端构建工具,能够显著提升前端开发体验,它主要由两部分组成:

为什么使用 Vite

你现在可能会有疑问?,那么 Vite 与现有的 vue-cli到底有什么不同呢?

由于@ vue-cli / service是在webpack之上构建的,因此它是一个模块捆绑程序,它将在启动,热重载和编译时捆绑整个Vue项目。

由于@vue-cli/service是在webpack之上构建的,因此它是一个模块捆绑程序,它将在启动,热重载和编译时捆绑整个Vue项目。

Webpack 的工作方式是,它通过解析应用程序中的每一个 importrequire ,将整个应用程序构建成一个基于 JavaScript 的捆绑包,并在运行时转换文件(例如 Sass、TypeScript、SFC)。

这都是在服务器端完成的,依赖的数量和改变后构建/重新构建的时间之间有一个大致的线性关系。

相反,Vite 不捆绑应用服务器端。相反,它依赖于浏览器对 JavaScript 模块的原生支持(也就是 ES 模块,是一个比较新的功能)。

浏览器将在需要时通过 HTTP 请求任何 JS 模块,并在运行时进行处理。Vite 开发服务器将按需转换任何文件(如 Sass、TypeScript、SFC)。

这种架构避免了服务器端对整个应用的捆绑,并利用浏览器高效的模块处理,提供了一个明显更快的开发服务器。

提示:当你对应用程序进行 code-split 和 tree-shake 动时,Vite 的速度会更快,因为它只加载它需要的模块,即使是在开发阶段。这与 Webpack 不同,在 Webpack 中,代码拆分只对生产包有利。

创建第一个Vite项目

运行下面命令即可:

npm init vite-app <项目名>

然后,我们只需进入我们的项目文件夹,安装依赖项,然后使用以下命令运行我们的应用程序:

cd <项目名>
npm install
npm run dev

现在,如果我们导航到http://localhost:3000 –我们应该看到以下应用程序:

image.png

一些你应该知道的Vue Vite特性

1.将项目打包到生产中

Vite的一个目标是使Vue的开发和生产尽可能容易。虽然在开发过程中没有捆绑,但是将你的项目捆绑到生产中是非常容易的。

你所要做的就是运行npm run build

如果查看package.json,实现是运行 vite build –与其他构建过程一样,打包后会放在dist文件中。

image.png

2.asset 路径

与其他Vue项目设置一样,Vite 提供了两种引用asset`的方法。

  1. 绝对路径 - 使用公用文件夹。 这些资源使用/file.extension引用,并且在构建项目时将复制到dist文件夹的根目录中。
  2. 相对路径 - 例如,根据文件夹的文件结构来相对访问src/assets文件夹中的文件。 构建项目时,整个文件夹都将作为_assets放置在dist文件夹中。

image.png

3.内置 Typescript 支持

Vue3 最大的变化之一是使用Typescript重写了核心库,允许根据IDE进行类型检查和更好的错误消息。

通过提供对.ts文件和SFC中的<script lang =“ ts”>的内置Typescript支持,Vite再次与Vue 3顺利集成。

理解 Vue3 组件

现在我们已经设置好了Vue 3应用程序,并且理解了Vue 3 Vite工具,让我们来看看这些组件是如何工作的。

Vue 3中最大的变化是引入了组合API。在这个新的结构中,我们能够根据特性来组织代码,而不是仅仅通过datacomputed等来分离代码。

这允许我们创建更多模块化、可读性和可伸缩性的代码,因为单个特性的代码都可以在代码的一个区域中编写。

image.png

如果打开src/components/HelloWorld.vue文件,我们将看到与我们在Vue2中编写的代码看起来相同的代码-这称为Options API

<script>
export default {
  name: 'HelloWorld',
  props: {
    msg: String
  },
  data() {
    return {
      count: 0
    }
  }
}
</script>

这很棒,因为在 Vue3 中,我们仍然可以在其中使用 vue2 的语法。

在本教程中,我们将介绍如何在新的Composition API中实现这一点,并从Options API中识别出这些变化。

组合API中的响应性数据

我们首先从vue核心库导入一些东西,以允许我们创建响应式变量。

import { ref } from 'vue'

然后,让我们用如下所示的setup函数替换data选项。

import { ref } from 'vue'
  export default {
    setup () {
      
      return {
       
      }
    }
  }

这个 setup 方法在组件创建时运行,在这里我们可以定义所有需要组件使用的响应数据、计算属性、方法等。

还有,该setup方法返回的任何内容都可以在模板中访问。

使用 ref 创建响应式数据

为了显示这一点,我们在模板中使用v-model创建一个文本输入。

<template>
   <div>
     <h2> Filter LearnVue Articles </h2>
     <input 
      type='text' 
      placeholder='Filter Search' 
      v-model='query'
    />
    {{ query }}
   </div>
</template>

我们使用ref创建响应式query变量,然后从setup方法返回它。

setup () {
      const query = ref('')

      return {
        query
      }
}

然后,如返回到应用程序,会看到我们使用Composition API获得了响应式数据。

image

很好!接下来,我们在input中添加一个clear按钮,看看如何在Composition API中创建一个方法。

组合API中的方法

选项 API中,Vue对象中有一个完整的属性专门用于方法。对于较大的文件,这意味着数据可能在数百行之外的方法中声明,这使得组件更难读取和维护。

组合API中,一切都在 setup 方法中,这意味着我们可以根据特性组织代码,甚至将通用特性提取到它们自己的代码模块中。

我们创建一个reset方法,它获取我们的ref并将其设置为一个空字符串。

setup () {
      const query = ref('')

      const reset = (evt) => {
        query.value = '' // clears the query
      }
      
      return {
        reset,
        query
      }
}

需要注意的一件事是,我们需要调用query.value才能访问数据的值。

为什么?

如果我们使用console.log(query),我们看到它不仅仅是一个字符串值,而是一个 Proxy。使用 Proxy 允许我们轻松地获取数据变化,这也是为什么我们需要在引用上调用.value的原因。

然后,就像在选项API使用的一样,我们可以在模板中添加一个按钮,在单击时调用这个reset方法。

<button @click='reset'> Reset </button>

image

向 Vue3 项目添加第二个组件

现在我们已经有了输入和查询数据,接着,创建一个组件显示结果。

这个组件取名为SearchResults.vue

要将其添加到我们的HelloWorld.vue组件中,首先必须将其导入并在我们的导出默认值中声明它。

<script>
  import { ref } from 'vue'
  import SearchResults from './SearchResults.vue'
  export default {
    components: {
      SearchResults
    },
    // ...
  }
</script>

然后,我们可以像这样将它添加到模板中:

// HelloWorld.vue
<template>
   <div>
     <h2> Filter LearnVue Articles </h2>
     <input 
      type='text' 
      placeholder='Filter Search' 
      v-model='query'
    />
    <br>
    <button @click='reset'> Reset </button>
    <search-results/>
   </div>
</template>

传递参数

Vue props 允许父组件将数据传递给其子组件。对于我们的例子,我们希望从HelloWorld.vue传递query 字符串给SearchResults.vue

// HelloWorld.vue
<search-results :query='query'/>

访问参数

SearchResults.vue内部,从 JSON 文件导入所有的文章信息。

import titles from '../post-data.json'
export default {
  setup (props, context) {
 
  }
}

然后,我们需要几个步骤来访问 props

首先,我们必须在 props 选项中声明它们。这告诉我们的组件需要什么数据。

// SearchResults.vue
export default {
  props: {
    query: String
  },
  setup (props, context) {
  // ...

如果我们仔细观察setup方法,就会发现它接受两个参数。

  1. props – 包含传递给组件的所有 props
  2. context– 包含 attrsslotemit

我们将使用 propssetup 方法中访问我们的 props 的值。

我们所需要做的就是使用计算属性来过滤使文章列表。

计算属性

// SearchResults.vue
import { computed } from 'vue'

然后,我们这样设置它,其中我们的computed属性接受一个getter方法。每当其中一个依赖项发生更改时,此方法将更新我们的computed属性。

// SearchResults.vue
import { computed } from 'vue'
import titles from '../post-data.json'
export default {
  props: {
    query: String
  },
  setup (props, context) {
    
    const filteredTitles = computed(() => {
     
    })

    return {
      filteredTitles
    }
  }
}

对于这个方法,我们希望使用query过滤所有的标题。所有内容都转换为小写,所以我们不必担心大小写。

// SearchResults.vue
const filteredTitles = computed(() => {
      return titles.filter(s => s.Name.toLowerCase().includes(props.query.toLowerCase()))
    })

很好~

剩下要做的就是实际使用我们的模板来显示数据!这是使用v-for循环完成的。

// SearchResults.vue
<template>
  <div class='root'>
    <p> Showing {{ filteredTitles.length }} results for "{{ query }}" </p>
    <ul>
      <li v-for='title in filteredTitles' :key='title.Page'>
        {{ title.Name }}
      </li>
    </ul>
  </div>
</template>

就这~

![上传中...]()

image

Vue3 生命周期钩子

在开始使用 Vue3 之前,还需要知道的另一件事是如何使用Vue生命周期钩子

image.png

像Composition API的其他部分一样,我们必须导入我们想要使用的生命周期钩子,并在setup方法中声明它们。

// Lifecycle Example 
import { computed, onMounted } from 'vue'
export default {
  setup () {
    
    onMounted(() => {
      console.log('mounted')
    })
  }
}

总结

Vue 3中有很多很棒的功能,这些功能对于创建可扩展的Vue应用程序非常有用。

希望本文本对你在使用 vue3 时提供一些帮助。

完~,我是刷碗智,我要去刷碗了,我们下期见~


代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

原文:https://learnue.co/2020/12/se...

交流

文章每周持续更新,可以微信搜索「 大迁世界 」第一时间阅读和催更(比博客早一到两篇哟),本文 GitHub https://github.com/qq449245884/xiaozhi 已经收录,整理了很多我的文档,欢迎Star和完善,大家面试可以参照考点复习,另外关注公众号,后台回复福利,即可看到福利,你懂的。

查看原文

赞 3 收藏 2 评论 1

前端小智 发布了文章 · 3月24日

组件的可重用性,6个级别的见解!

作者:Michael Thiessen
译者:前端小智
来源:news
点赞再看,微信搜索大迁世界,B站关注前端小智这个没有大厂背景,但有着一股向上积极心态人。本文 GitHubhttps://github.com/qq44924588... 上已经收录,文章的已分类,也整理了很多我的文档,和教程资料。

最近开源了一个 Vue 组件,还不够完善,欢迎大家来一起完善它,也希望大家能给个 star 支持一下,谢谢各位了。

github 地址:https://github.com/qq44924588...

我们所有人都希望编写更少的代码,同时也要做更多的事情。为了实现这一点,我们构建了组件,以便可以多次重用它们。

有些组件只需要基本的可重用性,而另一些则需要更复杂的重构技术,我们才能充分复用它。

这里有6个不同级别的可重用性概念,大家先来体会体会,后续更新会一个一个的讲。

1.模板化

通过模板化,我们将一些重复性高的代码包装在其自己的组件中,而不是在周围到处复制和粘贴代码。

当我们重用该组件(而不是直接使用代码)时,它为我们带来了两个好处:

  1. 将来进行更改就会容易得多,因为我们只需要在一个地方更改
  2. 我们不必记住每个重复代码复被复制到了哪些地方

这是最基本的,也是最经常谈论的可重用性形式。

2. 可配置

对于某些组件,我们需要根据需求对它们的工作方式进行修改,如:

Button组件默认有一个主版本,也有一个带有图标版本。但我们没有为每个版本创建全新的组件,而是指定 props 做到不同类型之间切换。

添加这些props通常不会给组件增加很大的复杂度,同时,又能给我们在使用组件方面带来更多在的灵活性。

注意:这不同于使用prop来保存状态或数据,比如loading prop 或disabled prop。

3.适应性

可配置的最大问题是缺乏远见。 我们需要预见将来的需求,并通过放置对应的 prop 将它们构建到组件中。

但是,如果你的组件具有足够适应性,则无需更改组件即应对未来的需求。

为了让我们的组件具有足够的适应性,我们可以使用 插槽 来实现。

例如,我们可以使用默认的插槽来代替在传入Button组件的 text

<!-- Button.vue -->
<template>
  <button
    class="btn btn--default"
    @click="$emit('click')"
  >
    <slot />
  </button>
</template>

现在我们不局限于传入的类型是 string 还是 number

如果我们想在不修改 Button 组件的情况下添加loading ,我们可以这样做:

<template>
  <Button>
    <img
      v-if="loading"
      data-original="spinner.svg"
    />
    Click Me
  </Button>
</template>

4.反转性

除了通过插槽传递完整的标记块给我们的子组件,我们还可以传递一组有关如何渲染的指令。

这就像我们根据食谱来做菜,而不是叫外卖。 当我们遵循食谱时,需要做更多的工作,但是我们完全可以按自己的节奏来制制作, 我们可以随时进行调整,也可以完全放弃不按食谱的流程来。

我们使用作用域插槽来为我们的组件增加更大的灵活性。

5. 扩展

通过适应性反转性,我们拥有必要的一些技术基础,这些技能可以最大限度地提高组件的可重用性。

下一步是将这些技术应用于整个组件,以便我们更轻松地扩展其行为。

我们使用命名插槽在组件中添加一个或多个扩展点。 仅适应性反转性本身给我们提供了扩展行为的一种选择,而拥有多个扩展点则为我们提供了许多不同的选择。

这里,我们有一个Modal组件,其中包含headerdefaultfooter

<template>
  <div class="modal">
    <slot name="header">
      <h2>{{ title }}</h2>
    </slot>

    <!-- Default slot for main content -->
    <slot />

    <slot name="footer">
      <Button @click="closeModal">
        Close
      </Button>
    </slot>
  </div>
</template>

这是一个相当简单的扩展示例,其中我们已经有几个扩展该组件的选项:

  1. 只需覆盖default slot即可添加我们的内容
  2. 可以通过插槽名来覆盖 header 的内容
  3. 可以通过插槽名来覆盖 footer 的内容,其内容还是以不同风格按钮为主
  4. headerfooter的插槽更多是用于自定义

你不必扩展此组件的行为,但也可以扩展其一部分。 无论哪种方式,我们都能获得很大的灵活性和大量的代码重用性。

6. 嵌套

扩展之上更高级重用性就是嵌套, 我们可以多个基本组件为基础, 一层嵌套一层,一开始可能听起来很疯狂,但它非常有用,特别是在大中型应用程序中。

我们从一个通过基础组件开始,该组件的功能相当普遍。 下一个组件就更加具体,以几种方式扩展了基础组件。 然后不断以前面基础组件为底往上扩展,直到我们拥有完成实际工作的最终组件。

这类似我们从非常普通的动物(Animal )到更特定的哺乳动物(Mammal ),然后是狗(Dog ),最后止于贵宾犬(Poodle)的方式。 如果我们需要的只是贵宾犬(Poodle)组件,看上去,我们整这么基础组件就是浪费时间。但是在大型应用程序中就不一样了,我们需要在相同的基本概念上进行多次更改,来满足不同的个性化需求,这时这种以基础组件嵌套的思想就很重要。

我们可以扩展犬类(Dog)组件来获得柯基犬(Corgi )比格犬(Beagle)组件。或者扩展哺乳动物(Mammal )组件以获得猫(Cat )组件,这样就可以添加老虎(Tiger)狮子(Lion)组件。

总结

以上是6个可重用性级别一些概述,当然很有可能会错过一些内容,但基本是可以为我们封装组件提供了一个大致思路,也是很不错的方式。

人才们的 【三连】 就是小智不断分享的最大动力,如果本篇博客有任何错误和建议,欢迎人才们留言,最后,谢谢大家的观看。


代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

原文:https://news.knowledia.com/US...

交流

文章每周持续更新,可以微信搜索「 大迁世界 」第一时间阅读和催更(比博客早一到两篇哟),本文 GitHub https://github.com/qq449245884/xiaozhi 已经收录,整理了很多我的文档,欢迎Star和完善,大家面试可以参照考点复习,另外关注公众号,后台回复福利,即可看到福利,你懂的。

查看原文

赞 6 收藏 4 评论 1

前端小智 发布了文章 · 3月22日

React 受控组件,Hooks 方式!

作者:Shadeed
译者:前端小智
来源:dmitripavlutin
点赞再看,微信搜索大迁世界,B站关注前端小智这个没有大厂背景,但有着一股向上积极心态人。本文 GitHubhttps://github.com/qq44924588... 上已经收录,文章的已分类,也整理了很多我的文档,和教程资料。

最近开源了一个 Vue 组件,还不够完善,欢迎大家来一起完善它,也希望大家能给个 star 支持一下,谢谢各位了。

github 地址:https://github.com/qq44924588...

React 提供了两种方法来访问input字段的值:使用受控或非受控组件。我更喜欢受控组件,因为我们可以通过组件的状态读取和设置input的值。

在这篇文章中,我们来看看如何使用React Hook 实现受控组件。

1.受控组件

假设我们有一个简单的文本字段,并且想访问其值:

import { useState } from 'react';

function MyControlledInput({ }) {
  const [ value, setValue ] = useState('');
  const onChange = (event) => {
    setValue(event.target.value);
  }
  
  return (
    <>
        <div>Input value: {value}</div>
        <input value={value} onChange={onChange} />
    </>
  )
}

image

打开示例(https://codesandbox.io/s/cont...。可以看到 value 变量包含input字段中的值,并且在每次输入新值时,它也会更新。

input字段受到控制,因为 React 从状态<input value = {value} ... />设置其值。 当用户在input 中输入内容时,onChange处理程序会使用从事件对象event.target.value访问的输入值来更新状态。

value变量表示用户真实输入的值。每次需要访问用户在input字段中输入的值时,只需读取value状态变量。

受控组件方法可以帮助我们访问任何输入类型的值:常规文本输入、textareaselect 等。

2. 受控组件中的3个步骤中

设置受控组件需要3个步骤:

  1. 定义保存input值的状态:const [value, setValue] = useState(")
  2. 创建事件处理程序,该事件处理程序在值更改时更新状态:
const onChange = event => setValue(event.target.value);

3.为input字段分配状态值,并添加事件处理程序:<input type="text" value={value} onChange={onChange} />

3. state 作为真实的数组源

我们看一个更复杂的例子。 页面中有一组员工姓名列表。 我们需要添加一个 input字段,当用户在此字段中键入内容时,员工列表将按姓名进行过滤。

function FilteredEmployeesList({ employees }) {
 const [query, setQuery] = useState('');  
 const onChange = event => setQuery(event.target.value);
  const filteredEmployees = employees.filter(name => {
    return name.toLowerCase().includes(query.toLowerCase());
  });

  return (
    <div>
 <h2>Employees List</h2>
 <input 
        type="text" 
 value={query}        onChange={onChange}      />
 <div className="list">
 {filteredEmployees.map(name => <div>{name}</div>)}
 </div>
 </div>
  );
}

打开演示(https://codesandbox.io/s/grac...,可以自行试试。

image

对输入进行防抖

在前面的实现中,只要在input中输入一个字符,就会立即过滤列表。这并不总是很方便,因为在输入查询时它会分散用户的注意力。

我们通过debounce来改善用户体验:在最后一次更改后,以400毫秒的延迟过滤列表。

import { useDebouncedValue } from './useDebouncedValue';
function FilteredEmployeesList({ employees }) {
  const [query, setQuery] = useState('');
 const debouncedQuery = useDebouncedValue(query, 400);  
  const onChange = event => setQuery(event.target.value);

  const filteredEmployees = employees.filter(name => {
 return name.toLowerCase().includes(debouncedQuery.toLowerCase());  });

  return (
    <div>
 <h2>Employees List</h2>
 <input 
        type="text" 
        value={query} 
        onChange={onChange}
      />
 <div className="list">
 {filteredEmployees.map(name => <div>{name}</div>)}
 </div>
 </div>
  );
}

image

打开演示(https://codesandbox.io/s/affe...,然后在input中输放值进行查询。员工列表不会在你打字时进行过滤,而是在最近一次按下键400毫秒后进行过滤。

下面是useDebouncedValue()的实现

export function useDebouncedValue(value, wait) {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const id = setTimeout(() => setDebouncedValue(value), wait);
    return () => clearTimeout(id);
  }, [value]);

  return debouncedValue;
}

受控组件是访问React中input字段的值的一种方便的技术。它不使用引用,而是作为访问input值的单一真实源。

~ 完,我们小智,我要去刷碗了,下期再见~


代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

原文:https://dmitripavlutin.com/co...

交流

文章每周持续更新,可以微信搜索「 大迁世界 」第一时间阅读和催更(比博客早一到两篇哟),本文 GitHub https://github.com/qq449245884/xiaozhi 已经收录,整理了很多我的文档,欢迎Star和完善,大家面试可以参照考点复习,另外关注公众号,后台回复福利,即可看到福利,你懂的。

查看原文

赞 4 收藏 1 评论 0

前端小智 发布了文章 · 3月18日

如何编写更好的 JS 代码!

作者:Taimoor Sattar
译者:前端小智
来源:valentinog
点赞再看,微信搜索大迁世界,B站关注前端小智这个没有大厂背景,但有着一股向上积极心态人。本文 GitHubhttps://github.com/qq44924588... 上已经收录,文章的已分类,也整理了很多我的文档,和教程资料。

Javascript 是浏览器可以理解的语言,它用于加载动态内容而无需刷新页面。今天列举一些用用更少的代码又更具可读性方式来编写 JS,肝货开始。

使用模板字符串

模板字符串是可以嵌入表达式中的字符串(变量),它可以让代码更加简单和易读。

var code = "javascript";
var str = ` I love ${code} I love ${code} `;

如果没有模板字符串,我们需要这么写:

var code = "javascript";
var str1 = "n I love " +  code + "n I love " +  code + "n";

使用三元运算符

在编程中,会遇到逻辑操作。如果要在两条语句之间执行逻辑,三元操作符的可读性要高得多。

let price= isMember ? '$2.00' : '$10.00'

使用Include语句

JS 中的 include 语句是一种在数组和句子中搜索字符串的更简单的方法。

var str = "I love JavaScript.";
var word = str.includes("javaScript"); // result: true

数组也可以使用 include 方法:

var str = ["taimoor", "ali", "umer"];
var n = str.includes("taimoor"); // result: true

空合并运算符

如果我们使用的是第三方API,可能会遇到相同的key-value不会出现在每个查询中。这样我们必须检查JSON中的空键,以免出现错误。

要检查空键,可以使用以下方法:

  • 条件语句
  • 空合并运算符(??)-(推荐)

例如,我们有如下JSON:

var person = {
  name: "Taimoor Sattar",
  age: 21,
  metadata: {
    hobby: "football, blog"
  }
}

使用条件语句,我们可以访问JSON的 matadata 中的 hobby 属性,如下所示

let hobby = "";
if (person.metadata){
  hobby = person.metadata.hobby ? person.metadata.hobby : "";
}

使用空合并操作符,我们只需要这样做:

let hobby = person.metadata?.hobby ?? "";

上面的代码检查JSON元数据中的 hobby 键,如果可用,则返回值,否则返回空字符串。

函数默认参数

JS 中的一些函数允许我们发送选项参数。根据可选参数,函数的返回值可以更改。

function outputName(name="taimoor"){
  return name;
}

let string1 = outputName(); // result: taimoor
let string2 = outputName("ali"); // result: ali

参数的类型检查

在某些情况下,函数参数要有类型的限制,我们可以这样检查函数的类型:

function sum(a, b){
  let result = (typeof a == "number" && typeof b == "number") ? a + b :  null;
  return result
}

sum("s", 6) // result: null
sum(4, 6) // result: 10

使用 Try/Catch 包装代码

Try/Catch 语句用于检查代码中的错误。 如果出错,将运行catch语句。

try{
  functionnotexist();
}catch(e){
  console.log("error");
}

解构

通过解构,我们可以将复杂的结构提取我们需要的部分。

function outputName({name = "taimoor"}){ // De-structuring
  return name;
}

var person = {
  name: "Taimoor Sattar",
  age: 21,
  metadata: {
    hobby: "football, blog"
  }
}

let str = outputName(person); // Taimoor Sattar

编写DRY代码

DRY(不要重复自己),避免在代码中重复以免造成混淆。 为避免代码混乱,可以遵循以下规则。

  • 编写可重用函数
  • 为变量和函数定义明确的名称

完~

我是小智,我要去刷碗了,我们下期见!


代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

原文:https://taimoorsattar.dev/blo...

交流

文章每周持续更新,可以微信搜索 【大迁世界 】 第一时间阅读,回复 【福利】 有多份前端视频等着你,本文 GitHub https://github.com/qq449245884/xiaozhi 已经收录,欢迎Star。

查看原文

赞 11 收藏 7 评论 10

前端小智 发布了文章 · 3月17日

用户体验细化,增强型的 <input type=number>

作者:Samantha Ming
译者:前端小智
来源:kilianvalkhof
点赞再看,微信搜索大迁世界,B站关注前端小智这个没有大厂背景,但有着一股向上积极心态人。本文 GitHubhttps://github.com/qq44924588... 上已经收录,文章的已分类,也整理了很多我的文档,和教程资料。

最近开源了一个 Vue 组件,还不够完善,欢迎大家来一起完善它,也希望大家能给个 star 支持一下,谢谢各位了。

github 地址:https://github.com/qq44924588...

input 标签的 number 类型提供了一种处理数字的好方法。 我们可以使用minmax属性设置界限,并且可以通过向上和向下键来添加或减少1,如果设置step属性,则向上或向下键来添加或减少对应的 step 值。 但是,如果我们想让用户以不同的step上下移动,该怎么办?

step不仅决定要添加或删除的数量,还决定了该数字的限制位置。 如果输入的值为5step10,然后按向上键,不会得到15(5 + 10),而是10(最接近的 step倍数)。

那么,我们希望用户可以输入任何数字又想增加10,要怎么做?

如何增强 input type=number 体验

先来定义一些按键操作。当用户在 input 标签中使用方向键时,有一些对应的快捷操作:

  • 如果按的是向上或向下键盘,我们要对应的加减 1
  • 如果按的是shift并按向上或向下键,我们要对应的加减 10
  • 如果按的是alt并按向上或向下键,我们要对应的加减 0.1
  • 如果按的是ctrl并按向上或向下键,我们要对应的加减 100, Mac 对应的 cmd
  • 如果输入内容为空,则根据 min 值来计算

实现

这是完整的代码,它相对简洁,仅约20行代码。

const isMac = navigator.platform === 'MacIntel';

const KEY = {
  UP: 38,
  DOWN: 40,
};

document.querySelector("input").addEventListener("keydown", e => {
  if ([KEY.UP, KEY.DOWN].includes(e.keyCode)) {
    e.preventDefault();
    
    const currentValue = isNaN(parseFloat(e.target.value))
      ? parseFloat(e.target.getAttribute("min")) || 0
      : parseFloat(e.target.value);
      
    const direction = e.keyCode === KEY.UP ? 1 : -1;
    
    const modifier = (isMac ? e.metaKey : e.ctrlKey) ? 100 : e.shiftKey ? 10 : e.altKey ? 0.1 : 1;
    
    const decimals = Math.max(
      (currentValue.toString().split(".")[1] || "").length,
      e.altKey ? 1 : 0
    );
    
    const newValue = currentValue + direction * modifier;
    
    e.target.value = newValue.toFixed(decimals);
  }
});

这段代码有些部分可能不是很好看懂,我们来逐行看看,表示的含义。

const isMac = navigator.platform === 'MacIntel';

const KEY = {
  UP: 38,
  DOWN: 40,
};

在 Windows 和 Linux 中,ctrl是我们想要使用的键,但在 Mac 上更常用的是cmdisMac是一个布尔值,表示是 Mac 还是 Window 系统。

你在键盘上按下的每个键都有一个唯一的键码。向上箭头键是38向下箭头键是40。因为我不喜欢代码中的魔法数字,所以我们将它们存储在一个对象中以便以后使用。

document.querySelector('input').addEventListener('keydown', e => {
  ...
}

然后是监听 inputkeydown 事件。keydown 可以告诉我们按下哪个键以及按下哪个修饰键的事件。 我们感兴趣的修饰键是shiftaltctrlcmdmetaKey 对应是 Mac 上是cmd键,在Windows中是 windows 键。

if ([KEY.UP, KEY.DOWN].includes(e.keyCode)) {
  e.preventDefault();
  ...
}

如果用户是向左或向右键,我们将不执行任何操作。 我们在代码周围添加了一个if子句,以便仅在用户按向上或向下键盘才执行。 当用户按向上或向下键时,我们调用e.preventDefault()。 这样可以防止输入内容被更新,因为我们会自己做。

const currentValue = isNaN(parseFloat(e.target.value))
  ? parseFloat(e.target.getAttribute("min")) || 0
  : parseFloat(e.target.value);

w你可能会认为获取值与获取e.target.value一样容易,但是,我们必须做更多的工作。 e.target.value始终是一个字符串,即使对于npmber类型的 input 元素也是如此,因此,要进行任何数学运算,我们都需要将其转换为数字。 因为我们需要能够加/减0.1,所以我们需要使用浮点数而不是整数。

是,如果输入为空,我们调用parseFloat,它返回的是一个NaN值。 由于我们无法添加或减去NaN,因此我们需要对些时行判断。 如果输入为空,那么我们将获得最小值(如果存在),或者默认为0。最小值也是一个字符串,因此我们也需要对其进行转换。

如果min属性未定义,它就变成NaN,而NaN || 0解析为0,所以得到结果是可以计算的。

const direction = e.keyCode === KEY.UP ? 1 : -1;

if子句中我们已经知道用户按下的向上或向下的键,所以需要检查用户是按向上还是向下键盘,以便确定是否需要加或减。 我们用变量 “direction” 来保存,如果是向上,值为 1,向下则为 -1,之后可以将其与以后的值相乘。

const modifier = (isMac ? e.metaKey : e.ctrlKey) ? 
  100 : 
  e.shiftKey ? 
    10 : 
    e.altKey ? 
      0.1 : 
      1;

我们找出按下了哪个修饰键。 事件属性可以告诉我们。 如果在我们按下的是向上或向下键的同时还按下 shiftalt 键,则e.shiftKeye.altKey的值为 true

我们首先使用(isMac ? e.metaKey : e.ctrlKey)来检查meta键或 ctrl键,具体取决于我们是否在 Mac上。 如果是这样,我们将相加或相减 100。 如果改为按Shift键,则我们用10加或减,如果按Alt键,则加0.1。 如果没有按下这些键,则按“默认”行为加1或减1

const decimals = Math.max(
(currentValue.toString().split(".")[1] || "").length,
e.altKey ? 1 : 0
);

这里有点棘手,因为我们使用的是浮动。由于四舍五入的关系,JavaScript 中的浮点数可能会产生意想不到的结果。具体来说,如果你加上例如0.1`和0.2,你得到的值是0.30000000000000004,大约是0.3`。

在进行基本计算时,0 的数量太多,但并不重要,在 input 元素中,0.30000000000000004看起来不是很好。我们只要 0.3。为了达到这个目的,我们需要知道在计算前的小数的最大数量是多少,就是当前输入的小数的数量,或者是按下alt键时的1,两者中哪个更大。我们存储这个值以便以后使用。

const newValue = currentValue + direction * modifier;

这是最终的结果值。 我们知道当前值,要增加或减少的数量以及是否需要增加或减少。

我们将modifier (要添加的数量)与direction (即+1或-1)相乘,以便在将其添加到当前值时可以相加或相减。

现在我们已经计算了新值,但是由于前面提到的可能很奇怪的四舍五入,我们不能直接将它设置为新值作为输入值,因为它可能有很多小数。相反,我们使用toFixed与我们之前找到的小数的数目

e.target.value = newValue.toFixed(decimals);

这,就是所有的代码.

这个input可以让用户快速增加或减少数值,或者精确地锁定一个数字,这取决于用户按的是哪个修改键。


编辑中可能存在的bug没法实时知道,事后为了解决这些bug,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

原文:https://kilianvalkhof.com/202...

交流

文章每周持续更新,可以微信搜索 【大迁世界 】 第一时间阅读,回复 【福利】 有多份前端视频等着你,本文 GitHub https://github.com/qq449245884/xiaozhi 已经收录,欢迎Star。

查看原文

赞 6 收藏 1 评论 12

前端小智 发布了文章 · 3月15日

前端必要懂的,完整的 HTTP cookie 指南

作者:valentinog
译者:前端小智
来源:valentinog
点赞再看,微信搜索大迁世界,B站关注前端小智这个没有大厂背景,但有着一股向上积极心态人。本文 GitHubhttps://github.com/qq44924588... 上已经收录,文章的已分类,也整理了很多我的文档,和教程资料。

最近开源了一个 Vue 组件,还不够完善,欢迎大家来一起完善它,也希望大家能给个 star 支持一下,谢谢各位了。

github 地址:https://github.com/qq44924588...

Web 开发中的 cookie 是什么?

cookie 是后端可以存储在用户浏览器中的小块数据。 Cookie 最常见用例包括用户跟踪,个性化以及身份验证。

Cookies 具有很多隐私问题,多年来一直受到严格的监管。

在本文中,主要侧重于技术方面:学习如何在前端和后端创建,使用 HTTP cookie。

后端配置

后端示例是Flask编写的。如果你想跟着学习,可以创建一个新的Python虚拟环境,移动到其中并安装Flask

mkdir cookies && cd $_

python3 -m venv venv
source venv/bin/activate

pip install Flask

在项目文件夹中创建一个名为flask app.py的新文件,并使用本文的示例在本地进行实验。

谁创建 cookies ?

首先,cookies 从何而来? 谁创建 cookies ?

虽然可以使用document.cookie在浏览器中创建 cookie,但大多数情况下,后端的责任是在将响应客户端请求之前在请求中设置 cookie。

后端是指可以通过以下方式创建 Cookie:

  • 后端实际应用程序的代码(Python、JavaScript、PHP、Java)
  • 响应请求的Web服务器(Nginx,Apache)

后端可以在 HTTP 请求求中 Set-Cookie 属性来设置 cookie,它是由键/值对以及可选属性组成的相应字符串:

Set-Cookie: myfirstcookie=somecookievalue

什么时候需要创建 cookie? 这取决于需求。

cookie 是简单的字符串。在项目文件夹中创建一个名为flask_app.py的Python文件,并输入以下内容:

from flask import Flask, make_response

app = Flask(__name__)


@app.route("/index/", methods=["GET"])
def index():
    response = make_response("Here, take some cookie!")
    response.headers["Set-Cookie"] = "myfirstcookie=somecookievalue"
    return response

然后运行应用程序:

FLASK_ENV=development FLASK_APP=flask_app.py flask run

当该应用程序运行时,用户访问http://127.0.0.1:5000/index/,后端将设置一个具有键/值对的名为Set-Cookie的响应标头。

127.0.0.1:5000是开发中的 Flask 应用程序的默认侦听地址/端口)。

Set-Cookie标头是了解如何创建cookie的关键:

response.headers["Set-Cookie"] = "myfirstcookie=somecookievalue"

大多数框架都有自己设置 cookie 的方法,比如Flask的set_cookie()

如何查看 cookies ?

访问http://127.0.0.1:5000/index/后,后端将在浏览器中设置cookie。 要查看此cookie,可以从浏览器的控制台调用document.cookie

clipboard.png

或者可以在开发人员工具中选中Storage选项卡。单击cookie,会看到 cookie 具体的内容:

clipboard.png

在命令行上,还可以使用curl查看后端设置了哪些 cookie

curl -I http://127.0.0.1:5000/index/

可以将 Cookie 保存到文件中以供以后使用:

curl -I http://127.0.0.1:5000/index/ --cookie-jar mycookies

在 stdout 上显示 cookie:

curl -I http://127.0.0.1:5000/index/ --cookie-jar -

请注意,没有HttpOnly属性的cookie,在浏览器中可以使用document.cookie上访问,如果设置了 HttpOnly 属性,document.cookie就读取不到。

Set-Cookie: myfirstcookie=somecookievalue; HttpOnly

现在,该cookie 仍将出现在 Storage 选项卡中,但是 document.cookie返回的是一个空字符串。

从现在开始,为方便起见,使用Flask的 response.set_cookie() 在后端上创建 cookie。

我有一个 cookie,现在怎么办?

你的浏览器得到一个 cookie。现在怎么办呢?一旦有了 cookie,浏览器就可以将cookie发送回后端。

这有许多用途发如:用户跟踪、个性化,以及最重要的身份验证。

例如,一旦你登录网站,后端就会给你一个cookie:

Set-Cookie: userid=sup3r4n0m-us3r-1d3nt1f13r

为了在每个后续请求中正确识别 我们的身份,后端会检查来自请求中浏览器的 cookie

要发送Cookie,浏览器会在请求中附加一个Cookie标头:

Cookie: userid=sup3r4n0m-us3r-1d3nt1f13r

cookie 可以设置过期时间: Max-Age 和 expires

默认情况下,cookie 在用户关闭会话时即关闭浏览器时过期。要持久化cookie,我们可以通过expiresMax-Age属性

Set-Cookie: myfirstcookie=somecookievalue; expires=Tue, 09 Jun 2020 15:46:52 GMT; Max-Age=1209600

注意:Max-Age优先于expires

cookie的作用域是网站路径: path 属性

考虑该后端,该后端在访问http://127.0.0.1:5000/时为其前端设置了一个新的 cookie。 相反,在其他两条路径上,我们打印请求的cookie

from flask import Flask, make_response, request

app = Flask(__name__)


@app.route("/", methods=["GET"])
def index():
    response = make_response("Here, take some cookie!")
    response.set_cookie(key="id", value="3db4adj3d", path="/about/")
    return response


@app.route("/about/", methods=["GET"])
def about():
    print(request.cookies)
    return "Hello world!"


@app.route("/contact/", methods=["GET"])
def contact():
    print(request.cookies)
    return "Hello world!"

运行该应用程序:

FLASK_ENV=development FLASK_APP=flask_app.py flask run

在另一个终端中,如果我们与根路由建立连接,则可以在Set-Cookie中看到cookie:

curl -I http://127.0.0.1:5000/ --cookie-jar cookies

HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 23
Set-Cookie: id=3db4adj3d; Path=/about/
Server: Werkzeug/1.0.1 Python/3.8.3
Date: Wed, 27 May 2020 09:21:37 GMT

请注意,此时 cookie 具有Path属性:

Set-Cookie: id=3db4adj3d; Path=/about/

/about/ 路由并保存 cookit

curl -I http://127.0.0.1:5000/about/ --cookie cookies

在 Flask 应用程序的终端中运行如下命令,可以看到:

ImmutableMultiDict([('id', '3db4adj3d')])
127.0.0.1 - - [27/May/2020 11:27:55] "HEAD /about/ HTTP/1.1" 200 -

正如预期的那样,cookie 返回到后端。 现在尝试访问 /contact/ 路由:

url -I http://127.0.0.1:5000/contact/ --cookie cookies

在 Flask 应用程序的终端中运行如下命令,可以看到:

ImmutableMultiDict([])
127.0.0.1 - - [27/May/2020 11:29:00] "HEAD /contact/ HTTP/1.1" 200 -

这说明啥?cookie 的作用域是Path 。具有给定路径属性的cookie不能被发送到另一个不相关的路径,即使这两个路径位于同一域中。

这是cookie权限的第一层。

在cookie创建过程中省略Path时,浏览器默认为/

cookie 的作用域是域名: domain 属性

cookie 的 Domain 属性的值控制浏览器是否应该接受cookie以及cookie返回的位置。

让我们看一些例子。

大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】

主机不匹配(错误的主机)

查看 https://serene-bastion-01422.herokuapp.com/get-wrong-domain-cookie/设置的cookie:

Set-Cookie: coookiename=wr0ng-d0m41n-c00k13; Domain=api.valentinog.com

这里的 cookie 来自serene-bastion-01422.herokuapp.com,但是Domain属性具有api.valentinog.com

浏览器没有其他选择来拒绝这个 cookie。比如 Chrome 会给出一个警告(Firefox没有)

clipboard.png

主机不匹配(子域名)

查看 https://serene-bastion-01422.herokuapp.com/get-wrong-subdomain-cookie/设置的cookie:

Set-Cookie: coookiename=wr0ng-subd0m41n-c00k13; Domain=secure-brushlands-44802.herokuapp.com

这里的 Cookie 来自serene-bastion-01422.herokuapp.com,但“Domain”属性是secure-brushlands-44802.herokuapp.com

它们在相同的域上,但是子域名不同。 同样,浏览器也拒绝此cookie:

clipboard.png

主机匹配(整个域)

查看 https://www.valentinog.com/get-domain-cookie.html设置的cookie:

set-cookie: cookiename=d0m41n-c00k13; Domain=valentinog.com

此cookie是使用 Nginx add_header在Web服务器上设置的:

add_header Set-Cookie "cookiename=d0m41n-c00k13; Domain=valentinog.com";

这里使用 Nginx 中设置cookie的多种方法。 Cookie 是由 Web 服务器或应用程序的代码设置的,对于浏览器来说无关紧要。

重要的是 cookie 来自哪个域。

在此浏览器将愉快地接受cookie,因为Domain中的主机包括cookie所来自的主机。

换句话说,valentinog.com包括子域名www.valentinog.com

同时,对valentinog.com的新请求,cookie 都会携带着,以及任何对valentinog.com子域名的请求。

这是一个附加了Cookie的 www 子域请求:

clipboard.png

下面是对另一个自动附加cookie的子域的请求

clipboard.png

Cookies 和公共后缀列表

查看 https://serene-bastion-01422.herokuapp.com/get-domain-cookie/:设置的 cookie:

Set-Cookie: coookiename=d0m41n-c00k13; Domain=herokuapp.com

这里的 cookie 来自serene-bas-01422.herokuapp.comDomain 属性是herokuapp.com。浏览器在这里应该做什么

你可能认为serene-base-01422.herokuapp.com包含在herokuapp.com域中,因此浏览器应该接受cookie。

相反,它拒绝 cookie,因为它来自公共后缀列表中包含的域。

Public Suffix List(公共后缀列表)。此列表列举了顶级域名和开放注册的域名。浏览器禁止此列表上的域名被子域名写入Cookie。

主机匹配(子域)

查看 https://serene-bastion-01422.herokuapp.com/get-subdomain-cookie/:设置的 cookie:

Set-Cookie: coookiename=subd0m41n-c00k13

当域在cookie创建期间被省略时,浏览器会默认在地址栏中显示原始主机,在这种情况下,我的代码会这样做:

response.set_cookie(key="coookiename", value="subd0m41n-c00k13")

当 Cookie 进入浏览器的 Cookie 存储区时,我们看到已应用Domain

clipboard.png

现在,我们有来自serene-bastion-01422.herokuapp.com 的 cookie, 那 cookie 现在应该送到哪里?

如果你访问https://serene-bastion-01422.herokuapp.com/,则 cookie 随请求一起出现:

clipboard.png

但是,如果访问herokuapp.com,则 cookie 不会随请求一起出现:

clipboard.png

概括地说,浏览器使用以下启发式规则来决定如何处理cookies(这里的发送者主机指的是你访问的实际网址):

  • 如果“Domain”中的域或子域与访问的主机不匹配,则完全拒绝 Cookie
  • 如果 Domain 的值包含在公共后缀列表中,则拒绝 cookie
  • 如果Domain 中的域或子域与访问在主机匹配,则接受 Cookie

一旦浏览器接受了cookie,并且即将发出请求,它就会说:

  • 如果请求主机与我在Domain中看到的值完全匹配,刚会回传 cookie
  • 如果请求主机是与我在“Domain”中看到的值完全匹配的子域,则将回传 cookie
  • 如果请求主机是sub.example.dev之类的子域,包含在example.dev之类的 Domain 中,则将回传 cookie
  • 如果请求主机是例如example.dev之类的主域,而 Domain 是sub.example.dev之类,则不会回传cookie。

Domain 和 Path 属性一直是 cookie 权限的第二层。

Cookies可以通过AJAX请求传递

Cookies 可以通过AJAX请求传播。 AJAX 请求是使用 JS (XMLHttpRequest或Fetch)进行的异步HTTP请求,用于获取数据并将其发送回后端。

考虑 Flask的另一个示例,其中有一个模板,该模板又会加载 JS 文件:

from flask import Flask, make_response, render_template

app = Flask(__name__)


@app.route("/", methods=["GET"])
def index():
    return render_template("index.html")


@app.route("/get-cookie/", methods=["GET"])
def get_cookie():
    response = make_response("Here, take some cookie!")
    response.set_cookie(key="id", value="3db4adj3d")
    return response

以下是 templates/index.html 模板:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<button>FETCH</button>
</body>
<script data-original="{{ url_for('static', filename='index.js') }}"></script>
</html>

下面是 static/index.js 的内容:

const button = document.getElementsByTagName("button")[0];

button.addEventListener("click", function() {
  getACookie();
});

function getACookie() {
  fetch("/get-cookie/")
    .then(response => {
      // make sure to check response.ok in the real world!
      return response.text();
    })
    .then(text => console.log(text));
}

当访问http://127.0.0.1:5000/时,我们会看到一个按钮。 通过单击按钮,我们向/get-cookie/发出获取请求并获取Cookie。 正如预期的那样,cookie 落在浏览器的 Cookie storage中。

对 Flask 应用程序进行一些更改,多加一个路由:

from flask import Flask, make_response, request, render_template, jsonify

app = Flask(__name__)


@app.route("/", methods=["GET"])
def index():
    return render_template("index.html")


@app.route("/get-cookie/", methods=["GET"])
def get_cookie():
    response = make_response("Here, take some cookie!")
    response.set_cookie(key="id", value="3db4adj3d")
    return response


@app.route("/api/cities/", methods=["GET"])
def cities():
    if request.cookies["id"] == "3db4adj3d":
        cities = [{"name": "Rome", "id": 1}, {"name": "Siena", "id": 2}]
        return jsonify(cities)
    return jsonify(msg="Ops!")

另外,调整一下 JS 代码,用于下请求刚新增的路由:

const button = document.getElementsByTagName("button")[0];

button.addEventListener("click", function() {
  getACookie().then(() => getData());
});

function getACookie() {
  return fetch("/get-cookie/").then(response => {
    // make sure to check response.ok in the real world!
    return Promise.resolve("All good, fetch the data");
  });
}

function getData() {
  fetch("/api/cities/")
    .then(response => {
      // make sure to check response.ok in the real world!
      return response.json();
    })
    .then(json => console.log(json));

当访问http://127.0.0.1:5000/时,我们会看到一个按钮。 通过单击按钮,我们向/get-cookie/发出获取请求以获取Cookie。 Cookie出现后,我们就会对/api/cities/再次发出Fetch请求。

在浏览器的控制台中,可以看到请求回来 的数据。另外,在开发者工具的Network选项卡中,可以看到一个名为Cookie的头,这是通过AJAX请求传给后端。

只要前端与后端在同一上下文中,在前端和后端之间来回交换cookie就可以正常工作:我们说它们来自同一源。

这是因为默认情况下,Fetch 仅在请求到达触发请求的来源时才发送凭据,即 Cookie

cookie 不能总是通过AJAX请求传递

考虑另一种情况,在后端独立运行,可以这样启动应用程序:

FLASK_ENV=development FLASK_APP=flask_app.py flask run

现在,在 Flask 应用程序之外的其他文件夹中,创建index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<button>FETCH</button>
</body>
<script data-original="index.js"></script>
</html>

使用以下代码在同一文件夹中创建一个名为index.js的 JS 文件:


button.addEventListener("click", function() {
  getACookie().then(() => getData());
});

function getACookie() {
  return fetch("http://localhost:5000/get-cookie/").then(response => {
    // make sure to check response.ok in the real world!
    return Promise.resolve("All good, fetch the data");
  });
}

function getData() {
  fetch("http://localhost:5000/api/cities/")
    .then(response => {
      // make sure to check response.ok in the real world!
      return response.json();
    })
    .then(json => console.log(json));
}

在同一文件夹中,从终端运行:

npx serve

此命令为您提供了要连接的本地地址/端口,例如http://localhost:42091/。 访问页面并尝试在浏览器控制台打开的情况下单击按钮。 在控制台中,可以看到:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:5000/get-cookie/. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing)

因为 http://localhost:5000/http://localhost:42091/.不同。 它们是不同的域,因此会 CORS 的限制。

大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】

处理 CORS

CORS 是一个 W3C 标准,全称是“跨域资源共享”(Cross-origin resource sharing)。它允许浏览器向跨域的服务器,发出XMLHttpRequest请求,从而克服了 AJAX 只能同源使用的限制。

整个 CORS 通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS 通信与普通的 AJAX 通信没有差别,代码完全一样。浏览器一旦发现 AJAX 请求跨域,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感知。因此,实现 CORS 通信的关键是服务器。只要服务器实现了 CORS 接口,就可以跨域通信。

默认情况下,除非服务器设置了Access-Control-Allow-Origin的特定HTTP标头,否则浏览器将阻止AJAX对非相同来源的远程资源的请求。

要解决此第一个错误,我们需要为Flask配置CORS:

pip install flask-cors

然后将 CORS 应用于 Flask:

from flask import Flask, make_response, request, render_template, jsonify
from flask_cors import CORS

app = Flask(__name__)
CORS(app=app)


@app.route("/", methods=["GET"])
def index():
    return render_template("index.html")


@app.route("/get-cookie/", methods=["GET"])
def get_cookie():
    response = make_response("Here, take some cookie!")
    response.set_cookie(key="id", value="3db4adj3d")
    return response


@app.route("/api/cities/", methods=["GET"])
def cities():
    if request.cookies["id"] == "3db4adj3d":
        cities = [{"name": "Rome", "id": 1}, {"name": "Siena", "id": 2}]
        return jsonify(cities)
    return jsonify(msg="Ops!")

现在尝试在浏览器控制台打开的情况下再次单击按钮。在控制台中你应该看到

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:5000/api/cities/. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing)

尽管我们犯了同样的错误,但这次的罪魁祸首是第二个路由。

你可以通过查看 “Network” 标签中的请求来确认,没有发送此类Cookie:

clipboard.png

为了在不同来源的Fetch请求中包含cookie,我们必须提credentials 标志(默认情况下,它是相同来源)。

如果没有这个标志,Fetch 就会忽略 cookie,可以这样修复:

const button = document.getElementsByTagName("button")[0];

button.addEventListener("click", function() {
  getACookie().then(() => getData());
});

function getACookie() {
  return fetch("http://localhost:5000/get-cookie/", {
    credentials: "include"
  }).then(response => {
    // make sure to check response.ok in the real world!
    return Promise.resolve("All good, fetch the data");
  });
}

function getData() {
  fetch("http://localhost:5000/api/cities/", {
    credentials: "include"
  })
    .then(response => {
      // make sure to check response.ok in the real world!
      return response.json();
    })
    .then(json => console.log(json));
}

credentials: "include" 必须在第一个 Fetch 请求中出现,才能将Cookie保存在浏览器的Cookie storage 中:

fetch("http://localhost:5000/get-cookie/", {
    credentials: "include"
  })

它还必须在第二个请求时出现,以允许将cookie传输回后端

  fetch("http://localhost:5000/api/cities/", {
    credentials: "include"
  })

再试一次,我们还需要在后端修复另一个错误:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:5000/get-cookie/. (Reason: expected ‘true’ in CORS header ‘Access-Control-Allow-Credentials’).

为了允许在CORS请求中传输cookie,后端还需要设置 Access-Control-Allow-Credentials标头。

CORS(app=app, supports_credentials=True)

要点:为了使Cookie在不同来源之间通过AJAX请求传递,可以这样做:

  • credentials: "include" 用于前端的 fetch 请求中
  • Access-Control-Allow-CredentialsAccess-Control-Allow-Origin 用于后端

cookie可以通过AJAX请求传递,但是它们必须遵守我们前面描述的域规则。

Cookie 的 Secure 属性

Secure 属性是说如果一个 cookie 被设置了Secure=true,那么这个cookie只能用https协议发送给服务器,用 http 协议是不发送的。换句话说,cookie 是在https的情况下创建的,而且他的Secure=true,那么之后你一直用https访问其他的页面(比如登录之后点击其他子页面),cookie会被发送到服务器,你无需重新登录就可以跳转到其他页面。但是如果这是你把url改成http协议访问其他页面,你就需要重新登录了,因为这个cookie不能在http协议中发送。

可以这样设置 Secure 属性

response.set_cookie(key="id", value="3db4adj3d", secure=True)

如果要在真实环境中尝试,请可以运行以下命令,并注意curl在此处是不通过HTTP保存cookie:

curl -I http://serene-bastion-01422.herokuapp.com/get-secure-cookie/ --cookie-jar -

相反,通过HTTPS,cookie 出现在cookie jar中:

curl -I https://serene-bastion-01422.herokuapp.com/get-secure-cookie/ --cookie-jar -

cookie jar 文件:

serene-bastion-01422.herokuapp.com      FALSE   /       TRUE    0

不要被Secure欺骗:浏览器通过HTTPS接受cookie,但是一旦cookie进入浏览器,就没有任何保护。

因为带有 Secure 的 Cookie 一般也不用于传输敏感数据.

Cookie 的 HttpOnly 属性

如果cookie中设置了HttpOnly属性,那么通过js脚本将无法读取到cookie信息,这样能有效的防止XSS攻击,窃取cookie内容,这样就增加了cookie的安全性,即便是这样,也不要将重要信息存入cookie。

XSS 全称Cross SiteScript,跨站脚本攻击,是Web程序中常见的漏洞,XSS属于被动式且用于客户端的攻击方式,所以容易被忽略其危害性。其原理是攻击者向有XSS漏洞的网站中输入(传入)恶意的HTML代码,当其它用户浏览该网站时,这段HTML代码会自动执行,从而达到攻击的目的。如,盗取用户Cookie、破坏页面结构、重定向到其它网站等。

如果有设置 HttpOnly 看起来是这样的:

Set-Cookie: "id=3db4adj3d; HttpOnly"

在 Flask 中

response.set_cookie(key="id", value="3db4adj3d", httponly=True)

这样,cookie 设置了HttpOnly属性,那么通过js脚本将无法读取到cookie信息。如果在控制台中进行检查,则document.cookie将返回一个空字符串。

何时使用HttpOnly? cookie 应该始终是HttpOnly的,除非有特定的要求将它们暴露给运行时 JS。

可怕的 SameSite 属性

first-party cookie 和 third-party cookie

查看https://serene-bastion-01422.herokuapp.com/get-cookie/ 中所携带的 Cookie

Set-Cookie: simplecookiename=c00l-c00k13; Path=/

first-party是指你登录或使用的网站所发行的 cookie,而third-party cookie 常为一些广告网站,有侵犯隐私以及安全隐患。

我们将这类 Cookie 称为 first-party。 也就是说,我在浏览器中访问该URL,并且如果我访问相同的URL或该站点的另一个路径(假设Path为/),则浏览器会将cookie发送回该网站。

现在考虑在https://serene-bastion-01422.herokuapp.com/get-frog/上的另一个网页。 该页面设置了一个cookie,此外,它还从https://www.valentinog.com/cookie-frog.jpg托管的远程资源中加载图像。

该远程资源又会自行设置一个cookie:

clipboard.png

我们将这种 cookie 称为third-party(第三方) Cookie。

第三方 Cookie 除了用于 CSRF 攻击,还可以用于用户追踪。比如,Facebook 在第三方网站插入一张看不见的图片。

![](facebook.com)

浏览器加载上面代码时,就会向 Facebook 发出带有 Cookie 的请求,从而 Facebook 就会知道你是谁,访问了什么网站。

使用 SameSite 属性

Cookie 的SameSite 属性用来限制third-party Cookie,从而减少安全风险。它可以设置三个值。

  • Strict
  • Lax
  • None

Strict最为严格,完全禁止第三方 Cookie,跨站点时,任何情况下都不会发送 Cookie。换言之,只有当前网页的 URL 与请求目标一致,才会带上 Cookie。

Set-Cookie: CookieName=CookieValue; SameSite=Strict;

这个规则过于严格,可能造成非常不好的用户体验。比如,当前网页有一个 GitHub 链接,用户点击跳转就不会带有 GitHub 的 Cookie,跳转过去总是未登陆状态。

Lax规则稍稍放宽,大多数情况也是不发送第三方 Cookie,但是导航到目标网址的 Get 请求除外。


Set-Cookie: CookieName=CookieValue; SameSite=Lax;

导航到目标网址的 GET 请求,只包括三种情况:链接,预加载请求,GET 表单。详见下表。

clipboard.png

设置了StrictLax以后,基本就杜绝了 CSRF 攻击。当然,前提是用户浏览器支持 SameSite 属性。

Chrome 计划将Lax变为默认设置。这时,网站可以选择显式关闭SameSite属性,将其设为None。不过,前提是必须同时设置Secure属性(Cookie 只能通过 HTTPS 协议发送),否则无效。

下面的设置无效。

Set-Cookie: widget_session=abc123; SameSite=None

下面的设置有效。

Set-Cookie: widget_session=abc123; SameSite=None; Secure

Cookies 和 认证

身份验证是 web 开发中最具挑战性的任务之一。关于这个主题似乎有很多困惑,因为JWT中的基于令牌的身份验证似乎要取代“旧的”、可靠的模式,如基于会话的身份验证。

来看看 cookie 在这里扮演什么角色。

基于会话的身份验证

身份验证是 cookie 最常见的用例之一。

当你访问一个请求身份验证的网站时,后端将通过凭据提交(例如通过表单)在后台发送一个Set-Cookie标头到前端。

型的会话 cookie 如下所示:

Set-Cookie: sessionid=sty1z3kz11mpqxjv648mqwlx4ginpt6c; expires=Tue, 09 Jun 2020 15:46:52 GMT; HttpOnly; Max-Age=1209600; Path=/; SameSite=Lax

这个Set-Cookie头中,服务器可以包括一个名为sessionsession id或类似的cookie

这是浏览器可以清楚看到的唯一标识符。 每当通过身份验证的用户向后端请求新页面时,浏览器就会发回会话cookie

基于会话的身份验证是有状态的,因为后端必须跟踪每个用户的会话。这些会话的存储可能是:

  • 数据库
  • 像 Redis 这样的键/值存储
  • 文件系统

在这三个会话存储中,Redis 之类应优先于数据库或文件系统。

请注意,基于会话的身份验证与浏览器的会话存储无关。

之所以称为基于会话的会话,是因为用于用户识别的相关数据存在于后端的会话存储中,这与浏览器的会话存储不同。

何时使用基于会话的身份验证

只要能使用就使用它。基于会话的身份验证是一种最简单、安全、直接的网站身份验证形式。默认情况下,它可以在Django等所有流行的web框架上使用。

但是,它的状态特性也是它的主要缺点,特别是当网站是由负载均衡器提供服务时。在这种情况下,像粘贴会话,或者在集中的Redis存储上存储会话这样的技术会有所帮助。

大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】

关于 JWT 的说明

JWTJSON Web Tokens的缩写,是一种身份验证机制,近年来越来越流行。

JWT 非常适合单页和移动应用程序,但它带来了一系列新挑战。 想要针对API进行身份验证的前端应用程序的典型流程如下:

  • 前端将凭证发送到后端
  • 后端检查凭证并发回令牌
  • 前端在每个后续请求上带上该令牌

这种方法带来的主要问题是:为了使用户保持登录状态,我将该令牌存储在前端的哪个地方?

对于前端开发来说,最自然的事情是将令牌保存在localStorage中。 由于许多原因,这很糟糕。

localStorage很容易从 JS 代码访问,而且它很容易成为XSS攻击的目标。

为了解决此问题,大多数开发人员都将JWT令牌保存在cookie中,以为HttpOnly和Secure可以保护cookie,至少可以免受XSS攻击。

SameSite 设置为 strict 就可以完全保护 JWT免受CSRF攻击

设置为SameSite = Strict的新SameSite属性还将保护您的“熟化” JWT免受CSRF攻击。 但是,由于SameSite = Strict不会在跨域请求上发送cookie,因此,这也完全使JWT的用例无效。

SameSite=Lax呢? 此模式允许使用安全的HTTP方法(即GET,HEAD,OPTIONS和TRACE)将 cookie发送回去。 POST 请求不会以任何一种方式传输 cookie。

实际上,将JWT标记存储在cookielocalStorage中都不是好主意。

如果你确实要使用JWT而不是坚持使用基于会话的身份验证并扩展会话存储,则可能要使用带有刷新令牌的JWT来保持用户登录。

总结

自1994年以来,HTTP cookie一直存在,它们无处不在。

Cookies是简单的文本字符串,但可以通过DomainPath对其权限进行控制,具有Secure的Cookie,只能通过 HTTP S进行传输,而可以使用 HttpOnly从 JS隐藏。

但是,对于所有预期的用途,cookie都可能使用户暴露于攻击和漏洞之中。

浏览器的供应商和Internet工程任务组(Internet Engineering Task Force)年复一年地致力于提高cookie的安全性,最近的一步是SameSite

那么,什么才算是比较安全cookie? ,如下几点:

  • 仅使用 HTTPS
  • 尽可能带有 HttpOnly 属性
  • 正确的SameSite配置
  • 不携带敏感数据

人才们的 【三连】 就是小智不断分享的最大动力,如果本篇博客有任何错误和建议,欢迎人才们留言,最后,谢谢大家的观看。


代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

原文:https://gizmodo.com/the-compl...

交流

文章每周持续更新,可以微信搜索 【大迁世界 】 第一时间阅读,回复 【福利】 有多份前端视频等着你,本文 GitHub https://github.com/qq449245884/xiaozhi 已经收录,欢迎Star。

查看原文

赞 12 收藏 9 评论 0

前端小智 发布了文章 · 3月12日

金三银四了,掌握 JS 这 36 个概念,助你一臂之力

作者:Mahdhi Rezvi
译者:前端小智
来源:dmitripavlutin
点赞再看,微信搜索大迁世界,B站关注前端小智这个没有大厂背景,但有着一股向上积极心态人。本文 GitHubhttps://github.com/qq44924588... 上已经收录,文章的已分类,也整理了很多我的文档,和教程资料。

最近开源了一个 Vue 组件,还不够完善,欢迎大家来一起完善它,也希望大家能给个 star 支持一下,谢谢各位了。

github 地址:https://github.com/qq44924588...

你可能会经常听到一些人在抱怨 JS 很奇怪,有时甚至是一文不值。 之所以有这种想法,是因为他们不太了解 JS 背后的运作方式。 我也觉得 JS 在某些情况处理方式与其它语言不太一样,但这并不能怪它,它也只是以自己的方式展现给大家而已。

如果,你热爱一门编程语言,那么应该就会想深入了解并逐个掌握它的概念。

这里列出了36个JavaScript概念,你需要掌握这些概念才能成为一个更懂 JS 的前端开发者。

1.调用堆栈执行

我们都知道堆栈溢出,但是你知道堆栈溢出是由什么原因导致的吗? 堆栈溢出是与调用堆栈一些操作错误相关联的。

理解了调用堆栈,你就会清楚解像是JS 这们的编程语言是如何执行的。

2. 原始数据类型

const foo = "bar";
foo.length; // 3
foo === "bar"; // true

这里,我们将值bar分配给常量foo时,它属于原始类型string。 这个每个人都知道。 但是各位少侠想没想过一个问题,string是基本数据类型,怎么能调用方法了?

奇怪吗? 不。

这个特性称为自动装箱。每当读取一个基本类型的时候,JS 后台就会创建一个对应的基本包装类型对象,从而让我们能够调用一些方法来操作这些数据。

还是拿上面的例子开始:

const foo = "bar";
foo.length; // 3
foo === "bar"; // true

变量 foo 是一个基本类型值,它不是对象,它不应该有方法。但是 JS 内部为我们完成了一系列处理(即装箱),使得它能够调用方法,实现的机制如下:

  • 创建String类型的一个实例
  • 在实例上调用指定的方法
  • 销毁这个实例
const foo  = new String("bar");
foo.length
foo === 'bar'
foo = null

通过对原始数据类型有深入的了解,我们就应该知道这些“怪异”情况是如何发生的,以及它们背后的逻辑原因。

3.值类型和引用类型

最近,我对“引用传递" 在 JS 是怎么工作的感到困惑。 尽管我知道 C 和 Java 等语言中有“按引用传递”“按值传递”的概念,但是我不确定它在 JS 中如何工作。

你是否知道分配给 JS 中非原始值的变量对该值的引用? 引用指向存储值的内存位置。。

var arr1 = [1,2,3];
var arr2 = arr1;
arr2.push(10);
console.log(arr2);
//[1, 2, 3, 10]
console.log(arr1);
//[1, 2, 3, 10]

上面的示例中可以看到,对arr2所做的任何修改也将体现在arr1上。 这是因为它们仅保存值对应的内存地址的引用,而非值本身。

通过了解值类型和引用类型的概念,你就会更好地了解如何为变量分配值和内存引用。

4. 强制类型转换

这个概念主要解释了隐式和显式类型强制之间的区别。 这是前端开发中对 JS 迷惑少数几个领域之一。 对于隐式强制转换的概念尤其如此,因为它对不同的数据类型以不同的方式表现。

这是 JS 面试中最常被考的。

Number('789')   // 显式
+'789'          // 隐式
789 != '456'    // 隐式
9 > '5'         // 隐式
10/null          // 隐式
true | 0        // 隐式

掌握了类型显隐转换,恭喜你对 JS 了解就进一步了。

5. 比较运算符号 和 typeof 运算符

双等与三等,它们在大多数情况下在表面上看起来相同并且给出相同的结果,但是,它们有时候可能会给带来意想不到的错误。

为了了解这两亲兄弟的区别,我们可以借助 typeof 来查看被比较的值的类型。

typeof 3 // "number"
typeof "abc" // "string"
typeof {} // "object"
typeof true // "boolean"
typeof undefined // "undefined"
typeof function(){} // "function"
typeof [] // "object"
typeof null // "object"

6. JavaScript 作用域

作用域是 JS 中一个很重要的尴尬,JS 也一直在不断完善自己的作用域。 根据Wissam的说法,作用域的简单定义是,编译器在需要时查找变量和函数。

了解作用域有助于我们有效地使用JavaScript。 我们还需要了解全局作用域以及块和函数作用域,也称为词法作用域。 JS 作用域一开始接触会感到很困惑,但是一旦你了解了事情的幕后原理,使用它就会非常令人兴奋。

7. 语句和声明

JavaScript 程序是一系列可执行语句的集合。所谓语句,就是一个可执行的单元,通过该语句的执行,从而实现某种功能。通常一条语句占一行,并以分号结束。默认情况下,JavaScript 解释器按照语句的编写流程依次执行。如果要改变这种默认执行顺序,需要使用判断、循环等流程控制语句。

我们应该要知道 语句和声明 的区别,这对我们全面了解 JS 是很有帮助的。

8. 立即调用的函数表达式和模块

IIFE: Immediately Invoked Function Expression,意为立即调用的函数表达式,也就是说,声明函数的同时立即调用这个函数。它主要用于避免污染全局作用域。 后来,引入了ES6模块,为避免全局作用域的污染提供了一种标准方法,尽管有人认为它不是IIFE的直接替代。

通过理解IIFE和模块,你可以构建较少由于全局空间处理不当而导致的错误的应用程序。当然,使用模块,我们还可以做很多事情。

大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】

9.消息队列和事件循环

正如MDN文档所说,JavaScript 有一个基于事件循环的并发模型,事件循环负责执行代码、收集和处理事件以及执行队列中的子任务。这个模型与其它语言中的模型截然不同,比如 C 和 Java。

在并发模型中,消息队列用于处理最早的消息。 只要有事件发生,便会加入消息队列中。通过理解这些概念,你可以更好地理解JS在底层是如何工作的,以及知道你的代码是如果运行的。

10.时间间隔

想在 JS 有计划的调用的函数,可以使用下面两个函数:

  • setTimeout 允许我们在特定时间间隔后运行一次函数。
  • setInterval允许我们重复运行一个函数,从一个特定的时间间隔开始,然后以该间隔连续重复。

这些与前面的消息队列和事件处理程序的概念有些关联。因此,通过理解时间间隔方法,我们可以理解它们是如何工作的,并在我们的用例中有效地使用它们。

11.JS 引擎

JavaScript引擎是执行 JS 代码的计算机程序或解释器。JS 引擎可以用多种语言编写。例如,驱动Chrome浏览器的V8引擎是用 c++ 编写的,而驱动Firefox浏览器的SpiderMonkey引擎是用 C 和 c++编 写的。

要想编写高效的代码,你必须了解所使用的 JS 引擎。使用webview的移动开发人员要特别注意这一点。

12.按位运算

按位运算操作将值视为(0和1),而不是十进制,十六进制或八进制数字。 按位运算符对此类二进制表示形式执行其操作,但是它们返回标准JavaScript数值。

通常,很少会在代码中使用这些操作,但是它们确实有一些用例。 比如,可以使用它们来查找偶数和奇数值,颜色转换,颜色提取等等。

通过全面了解这些按位操作,您可以很好地使用 WebGL 之类的技术,因为它包含许多像素操作。

13. DOM 和布局树

我们大多数人都听说过文档对象模型(DOM),但只有少数人对此有深入的了解。 你知道在浏览器中看到的不是DOM吗? 而是渲染树,它实际上是DOM和CSSOM的组合。

通过理解DOM的工作方式、结构以及页面的渲染方式,我们就能够在 JS 的帮助下动态地操作web页面。这对于确保我们的应用程序具有高标准的性能尤为必要。

14.类和工厂

JavaScript 不是一种面向对象的语言。 但是,为了模仿OOP属性,使用了构造函数。 根据Tania的说法,“ JavaScript中的类实际上并没有提供其他功能,只是在原型和继承上提供语法糖,因为它们提供了更简洁,更优雅的语法。 由于其他编程语言都使用类,因此 JS 中的类语法使开发人员在各种语言之间移动变得更加简单。”

工厂函数是不是返回对象的类或构造函数的函数。 根据JS专家Eric Elliot的说法,“在JavaScript中,任何函数都可以返回一个新对象。 如果它不是构造函数或类,则称为工厂函数。”

当开始开发规模更大的应用程序时,理解这两个概念是很有必要的。

15.this 关键字和 apply,callbind方法

就我个人而言,我认为对于一个JS开发人员来说,理解this 关键字是至关重要的。如果你不能正确地理解它,将来你开发的项目也会经常遇到this相关的问题。

如果你对this关键字很清楚,则可以看看applycallbind方法,这些都可以解决 this 指向引发的问题。

16.构造函数和 “instanceOf” 运算符

构造函数就像常规函数一样。 但是它们有很多差异,函数名称以大写字母开头,并且只能由new运算符执行。 具有OOP开发经验的程序员会熟悉new关键字。

为了正确识别对象的类型,我们使用instanceOf运算符。 简单来说,它检查一个对象是否是另一个对象的实例。

这才助于你理解对象如何相互继承,继承是通过原型实现的。

17.原型

这是 JS 中最令人困惑的概念之一,即使对于有十年经验的人来说也是如此。

JavaScript中的原型是在对象之间共享通用功能的机制。 JavaScript中几乎所有对象都是Object的实例。 对象会从Object.prototype继承所有属性和方法。

简单来说,原型是 JS 对象从中继承方法和属性的对象。

理解了原型,你就可以构建高效,快速的应用程序。

18. 使用 newObject.createObject.assign 创建对象

创建对象有很多方法。 但是,大都会选择Object.create方法而不是new关键字。这是有原因的,因为 使用Object.create方法时,可以将现有对象用作新创建的对象的原型。 这样就可以重用现有对象的属性和功能,有点像OOP中的继承概念。

使用Object.assign方法时,可以将可枚举的自身属性从一个或多个源对象复制到目标对象。 在这种情况下,目标对象的原型不包含源对象的属性。 这是这两种方法之间的主要区别。

通过了解对象创建的这三种方式,可以根据实际情况适当地使用它们,以创建效率更高的程序。

19.map,filter, reduce 方法

当涉及到数组操作时,这三种方法非常有用。 它们可以在Array原型中找到。

如果你有一个数组,并且想对每个元素做一些事情,那么您可以使用map方法。

如果你有一个数组,并且想通过某些条件来过滤一些值时,则可以使用filter方法。

reduce() 方法对数组中的每个元素执行一个由你提供的reducer函数(升序执行),将其结果汇总为单个返回值。

典型的例子就是对数组的所有元素进行求和:

let numbers = [1,2,3,4,5,6]
const reduced = numbers.reduce( (accumulator, currentValue) => accumulator + currentValue )
console.log(reduced)
// 21

请注意,上述三种方法不会更改原始数组的值。

20.纯函数,副作用和状态变更

这三个概念对于 JS 开发人员而言非常重要,状态变更对于使用 React 的开发人员尤其重要。

纯函数指的是一个函数的返回结果只依赖于它的参数,并且在执行过程里面没有副作用。

函数副作用是指当调用函数时,除了返回函数值之外,还对主调用函数产生附加的影响。副作用的函数不仅仅只是返回了一个值,而且还做了其他的事情,比如:

  • 修改了一个变量
  • 直接修改数据结构
  • 设置一个对象的成员
  • 抛出一个异常或以一个错误终止
  • 打印到终端或读取用户输入
  • 读取或写入一个文件
  • 在屏幕上画图

状态变更是指你更改变量值的地方。 如果你对变量进行更改,则可能会影响到其他函数,具体取决于变量被更改之前的值。 在React环境中,建议我不要改变状态。

21. 闭包

闭包很难理解。 但是一旦理解,你会觉得 JS 其实也挺好的。 在线上有足够的资源。 你花足够的时间学习闭包,掌握理解它并不难。

使用闭包可以访问内部作用域中外部作用域的作用域。 每次创建函数时都会在函数创建时创建JavaScript闭包。

22. 高阶函数

高阶函数是将其他函数作为参数或返回结果的函数。 你可以创建仅负责一项任务的较小函数,然后在这些较小函数的帮助下构造复杂函数。 这也会提交代码的可重用性。

23.递归

递归是所有编程语言中的一个常见概念。简单地说,递归就是把大问题分解成小问题,然后解决小问题一种思路。

尽管递归可能是一个让你头疼的令人困惑的概念,但是通过大量的练习,从一些小问题开始,你可以更好地理解它。

24.集合与生成器

ES6 中新引入了集合和生成器。 新引入的集合有MapSetWeakSetWeakMap。 这些集合为我们提供一些很方便的操作。 了解它们的方式至关重要,尤其是对于现代JavaScript。

生成器有时很难理解,特别是对于初学者。 生成器允许我们编写代码函数,从而能够暂停和重新启动函数,而不会阻止其他代码的执行,这在JavaScript中是很不常见的。

25. Promise

JecelynPromises 的解释如下:“想象一下你是个孩子。 你妈妈向你保证,她下周会买一部新手机给你。”

你要到下周才能知道你是否能屋那部手机。你的妈妈要么真的给你买了一个全新的手机,要么因为不开心就不给你买。

这算是一个承诺。 一个 Promise 有三个状态,分别是:

  1. Pending:你不知道你是否会能得到那个电话
  2. Fulfilled:妈妈高兴了,给你买了一部新手机
  3. Rejected:老妈不开心了,就是不给买,爱咋滴就咋滳

26.异步编程

要了解什么是异步编程,首先要先积善成德什么是同步编程。 同步编程是线程阻塞的,由于 JS 是单线程的,因此代码将逐行执行。

但是使用异步代码,你可以执行一些比较耗时的任务。 当你必须执行花费很长时间才能完成的多个任务时,此功能特别有用。 但是在某些情况下,即使是需要执行很长时间的代码,也可能需要用同步的方式,这时就可以使用async/await

27. ES6 箭头函数

箭头函数是 ES6 的新增功能,是常规函数的语法替代。 区别在于箭头函数不绑定到thisargumentssupernew.target关键字。 这使得箭头函数在某些情况下是一个不错的选择,而在另一些情况下则是一个非常糟糕的选择。

因此,不要一上来就使用箭头函数。 需要根据你实际情况还使用它们。

大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】

28. 数据结构

无论使用哪种编程语言,数据结构都是开发人员应具备的基本知识之一。

糟糕的程序员担心代码,好的程序员担心数据结构和它们之间的关系。

数据结构方面,你应该了解链表,队列,堆栈,树,图和哈希表。

29.时间复杂度

不管编程语言如何,时间复杂度分析也是计算机编程的另一个基础。 为了构建更好的应用程序,你应该编写更好的解决方案。 为此,你需要了解时间复杂度的概念。 有时也称为BigO

30.算法

这也是在计算机基础课程中首先要教的内容之一。 简而言之,算法是逐步实现目标的过程。 程序员应该能够从算法的角度看任何问题。

尽管有成千上万个用例的大量算法,但是下面两个很常见:

  • 查找
  • 排序

这两个用例对程序员来说是非常常见的,至少应该了解实现它们的已知算法。没有固定的规则规定你应该使用这些算法之一,但是这些算法在性能方面是众所周知的,并且有很好的文档证明。

你甚至可以创建自己的算法,并将其介绍给世界。如果它比目前已知的算法更好,你可能会成为下一个编程明星

31.继承,多态和代码重用

JS 中的继承可用于原型来实现。 这是因为 JS 是非OOP语言。 但是 JS 通过提供原型继承来提供OOP的某些功能。

多态是对象、变量或函数可以采用多种形式的概念。在 JS 中,要看到多态的效果有点困难,因为在静态类型的系统中,多态的经典类型更明显。

以上两个概念都可以帮助我们在 JS 中实现更好代码重用。

32.设计模式

设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

33. 函数式编程

函数式编程是一种编程范式,是一种构建计算机程序结构和元素的风格,它把计算看作是对数学函数的评估,避免了状态的变化和数据的可变。

你需要掌握函数式编程的几个概念:

  • 纯函数
  • 不可变
  • 引用透明性
  • 高阶函数

34. 简洁代码的原则

无论使用哪种编程语言,这都是每个开发人员都应该掌握的一项基本技能。 每种编程语言都有一套单独的良好实践。 尽管这些“良好”做法是主观的,并且在工作场所之间存在差异,但有些惯例被认为是“良好”。

通过遵循这些代码原则,可以确保每个人都可以阅读和维护你的代码。 这也会帮助你和你的团队在应用程序开发过程中顺利合作。

35. 解构赋值

在ES6中引入了解构赋值操作符,它非常有用。对于相同的用例,它们比以前的实现更简单、更有效。

36. ES2020新特性

编程的优点之一是,如果你不去不断学习,你永远不会成为该领域专家。 编程语言会随着时间不断发展,因为每个主要版本中都引入了其他新的功能。

这也说明了你对某个概念的专业知识很可能在将来的10年后会过期,因为会有更好的替代版本与版本更新一起发布。 对于任何编程语言,这都是非常常见的情况。

ES202 0发布了几个新特性,包括可选链接、空值合并、动态导入等等。你必须学习这些新概念,以跟上快速变化的It世界。

掌握一门语言需要多年的经验和时间,但是知道要掌握什么会让事情变得更容易,希望这 36 个概念能对你有所帮助。

人才们的 【三连】 就是小智不断分享的最大动力,如果本篇博客有任何错误和建议,欢迎人才们留言,最后,谢谢大家的观看。


代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

交流

文章每周持续更新,可以微信搜索 【大迁世界 】 第一时间阅读,回复 【福利】 有多份前端视频等着你,本文 GitHub https://github.com/qq449245884/xiaozhi 已经收录,欢迎Star。

查看原文

赞 19 收藏 14 评论 1

前端小智 发布了文章 · 3月11日

7个关于"this"面试题,你能回答上来吗?

作者:Shadeed
译者:前端小智
来源:dmitripavlutin
点赞再看,微信搜索大迁世界,B站关注前端小智这个没有大厂背景,但有着一股向上积极心态人。本文 GitHubhttps://github.com/qq44924588... 上已经收录,文章的已分类,也整理了很多我的文档,和教程资料。

最近开源了一个 Vue 组件,还不够完善,欢迎大家来一起完善它,也希望大家能给个 star 支持一下,谢谢各位了。

github 地址:https://github.com/qq44924588...

在JavaScript中,this 表示函数调用上下文。this难点在于它有一个复杂的行为,这也是面试中经常被考的点。

本文列举7个关于this有趣的面试问题:

  • 问题1:变量 vs 属性
  • 问题2:Cat 的名字
  • 问题3:延迟打招呼
  • 问题4:人工方法
  • 问题5:问候和告别
  • 问题6:棘手的 length
  • 问题7:调用参数

问题1:变量 vs 属性

下面的打印结果是啥:

const object = {
  message: 'Hello, World!',

  getMessage() {
    const message = 'Hello, Earth!';
    return this.message;
  }
};

console.log(object.getMessage()); // ??

答案:'Hello, World!'

object.getmessage()是一个方法调用,此时的 this 表示 object

方法还有一个变量声明const message = 'Hello, Earth!'。这个变量都不会影响this.message的值。

问题2:Cat 的名字

下面代码打印什么:

function Pet(name) {
  this.name = name;

  this.getName = () => this.name;
}

const cat = new Pet('Fluffy');

console.log(cat.getName()); // What is logged?

const { getName } = cat;
console.log(getName());     // What is logged?

答案:'Fluffy''Fluffy'

当函数作为构造函数new Pet('Fluffy')调用时,构造函数内部的this等于构造的对象

Pet构造函数中的this.name = name表达式在构造的对象上创建name属性。

this.getName = () => this.name在构造的对象上创建方法getName。 而且由于使用了箭头函数,箭头函数内部的this值等于外部作用域的this值, 即 Pet

调用cat.getName()以及getName()会返回表达式this.name,其计算结果为'Fluffy'

问题3:延迟打招呼

下面代码打印什么:

const object = {
  message: 'Hello, World!',

  logMessage() {
    console.log(this.message); // What is logged?
  }
};

setTimeout(object.logMessage, 1000);

答案:1秒后,打印 undefined

尽管setTimeout()函数使用object.logMessage作为回调,但仍将object.logMessage用作常规函数,而不是方法。

在常规函数调用期间,this等于全局对象,即浏览器环境中的 window。

这就是为什么logMessage方法中的 this.message等于 window.message,即undefined

问题4:人工方法

如何调用logMessage函数,让它打印 "Hello, World!" ?

 message: 'Hello, World!'
};

function logMessage() {
  console.log(this.message); // "Hello, World!"
}

答案:

至少有 3 种方式,可以做到:

 message: 'Hello, World!'
};

function logMessage() {
  console.log(this.message); // logs 'Hello, World!'
}

// Using func.call() method
logMessage.call(object);

// Using func.apply() method
logMessage.apply(object);

// Creating a bound function
const boundLogMessage = logMessage.bind(object);
boundLogMessage();

问题5:问候和告别

下面代码打印什么:

const object = {
  who: 'World',

  greet() {
    return `Hello, ${this.who}!`;
  },

  farewell: () => {
    return `Goodbye, ${this.who}!`;
  }
};

console.log(object.greet());    // What is logged?
console.log(object.farewell()); // What is logged?

答案: 'Hello, World!''Goodbye, undefined!'

当调用object.greet()时,在greet()方法内部,this值等于 object,因为greet是一个常规函数。因此object.greet()返回'Hello, World!'

但是farewell()是一个箭头函数,箭头函数中的this值总是等于外部作用域中的this值。

farewell()的外部作用域是全局作用域,它是全局对象。因此object.farewell()实际上返回'Goodbye, ${window.who}!',它的结果为'Goodbye, undefined!'

问题6:棘手的 length

下面代码打印什么:

var length = 4;
function callback() {
  console.log(this.length); // What is logged?
}

const object = {
  length: 5,
  method(callback) {
    callback();
  }
};

object.method(callback, 1, 2);

答案: 4

callback()是在method()内部使用常规函数调用来调用的。由于在常规函数调用期间的this值等于全局对象,所以this.length结果为window.length。。

第一个语句var length = 4,处于最外层的作用域,在全局对象 window 上创建一个属性length

问题7:调用参数

下面代码打印什么:

var length = 4;
function callback() {
  console.log(this.length); // What is logged?
}

const object = {
  length: 5,
  method() {
    arguments[0]();
  }
};

object.method(callback, 1, 2);

答案: 3

obj.method(callback, 1, 2)被调用时有3个参数:callback, 12。结果,method()内部的参数特殊变量是如下结构的数组类对象:

{
  0: callback,
  1: 1, 
  2: 2, 
  length: 3 
}

因为arguments[0]()是arguments对象上的回调的方法调用,所以回调内部的参数等于arguments。 所以 callback()中的this.lengtharguments.length相同,即3

~ 完,我是小智,我要去刷碗了,我们下期见!


代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

原文:https://dmitripavlutin.com/ja...

交流

文章每周持续更新,可以微信搜索「 大迁世界 」第一时间阅读和催更(比博客早一到两篇哟),本文 GitHub https://github.com/qq449245884/xiaozhi 已经收录,整理了很多我的文档,欢迎Star和完善,大家面试可以参照考点复习,另外关注公众号,后台回复福利,即可看到福利,你懂的。

查看原文

赞 20 收藏 14 评论 1

前端小智 发布了文章 · 3月9日

使用这些 CSS 属性,布局效率又提高了一个层次!

作者:Ahmad shaded
译者:前端小智
来源:sitepoint
点赞再看,养成习惯

本文 GitHubhttps://github.com/qq44924588... 上已经收录,更多往期高赞文章的分类,也整理了很多我的文档,和教程资料。欢迎Star和完善,大家面试可以参照考点复习,希望我们一起有点东西。

最近开源了一个 Vue 组件,还不够完善,欢迎大家来一起完善它,也希望大家能给个 star 支持一下,谢谢各位了。

github 地址:https://github.com/qq44924588...

有很多CSS属性,有些人不了解,或者他们了解它们,但是忘记在需要时使用它们。其实,有时候我们用 JavaScript 来实某些交互,CSS 一个属性就能搞定了,这可以大大节约我们编码的时间。

作为前端开发人员,我们经常会遇到这样的事情。所以我问自己,为什么不搞篇文章列出所有那些较少使用但既有用又有趣的 CSS 属性?

在本文中,我将介绍一些不一样的CSS属性,希望能给你带来点新鲜感,废话不多说,让我们开始吧。

在CSS网格中使用Place-Items

clipboard.png

我们只需使用两行 CSS 代码就可以将元素水平和垂直居中。

HTML

<div class="hero">
    <div class="hero-wrapper">
        <h2>CSS is awesome</h2>
        <p>Yes, this is a hero section made for fun.</p>
        <a href="#">See more</a>
    </div>
</div>

CSS

.hero {
    display: grid;
    place-items: center;
}

place-items是将justify-itemsalign-items结合在一起的简写属性。上面的代码等同于下面代码:

.hero {
    display: grid;
    justify-items: center;
    align-items: center;
}

你可能想知道,这是怎么回事? 我们来解释一下。当使用place-items时,它将应用于网格中的每个单元格,也就是说单元格的内容都会居中。如果我们多增加几个单元格就会很清晰明了:

.hero {
    display: grid;
    grid-template-columns: 1fr 1fr;
    place-items: center;
}

clipboard.png

Flexbox 与 margin 的配合

clipboard.png

与flexbox 结合使用,margin: auto 可以非常轻松地将 flex 项目水平和垂直居中。

html

<div class="parent">
    <div class="child"></div>
</div>

css

.parent {
    width: 300px;
    height: 200px;
    background: #ccc;
    display: flex;
}

.child {
    width: 50px;
    height: 50px;
    background: #000;
    margin: auto;
}

看起来有点酷 😎

大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】

列表的 marker 属性

clipboard.png

这前,我还不知道每个li项旁边的默认小圆圈称为marker。 在我知道::marker伪元素之前,如果要重置小圆圈列表样式,我们一般使用伪类::before::after伪元素:

ul {
    list-style: none;
    padding: 0;
}

li {
    color: #222;
}

li::before {
    content: "•";
    color: #ccc;
    margin-right: 0.5em;
}

如上所示<li>颜色是#222,而伪元素::before#ccc。如果<li>::before具有相同的颜色,那么小圆圈默认颜色就是 li 的颜色,因此根本不需要伪元素。

我们来看一种更好的方法:

li {
    color: #222;
}

li::marker {
    color: #ccc;
}

比起上面伪类的方式,这简直不要太爽!

text-align 属性

随着CSS flexbox和 grid 的日益普及,初学者一般也是使用它们来居中文本,而不是使用text-align属性,但是,旧方法仍然有效。

使用text-align:center 也可以快速解决问题,考虑以下示例。

clipboard.png

如果仅内容需要居中,不一定非要使用flexboxgrid,使用text-align反而会更简单。

display: inline-Flex 属性

clipboard.png

当我们需要以为内联的方式显示徽章列表,并且每个徽章都应该是一个flexbox元素,这时就需要 inline-flex 出场了。

HTML

<span class="badge"><svg></svg></span>
<span class="badge"><svg></svg></span>
<span class="badge"><svg></svg></span>
<span class="badge"><svg></svg></span>
.badge {
    display: inline-flex; /* where the magic happens */
    justify-content: center;
    align-items: center;
}

column-rule 属性

clipboard.png

CSS 的columns 属性是一种布局方法,可以将元素划分为列。 一个常见的用例是将段落文本内容分为两行。 但是,最不常见的是我们可以在列之间添加边框。 我从是 Manuel Matuzovic的文章中学到了这一技巧。 😍

p {
    columns: 3;
    column-rule: solid 2px #222;
}

column-rule属性名称可能不能反映其用途,但可以将其想象为类似border-right作用。

background-repeat: round

clipboard.png

我最近从Addy Osmani的一条推文中了解了这种价值。 background-repeat有一个值,可以防止背景裁剪。

.element {
    background-size: contain;
    background-repeat: round;
}

太神奇了,是不是 😲

object-fit 属性

clipboard.png

object-fit属性是相当神奇且有用的。 当我第一次了解它时,它改变了很多事情,使我作为前端开发人员的生活更加轻松。 最近,我正在研究显示徽标网格的部分。 由于徽标大小不一致,因此有时很难做到这一点。 其中一些具有水平形状,一些具有垂直形状。😒😵

通过使用object-fit:contain,我可以控制徽标的widthheight,并强制将图像包含在定义的宽度和高度中。😱😱

HTML

<ul class="brands">
    <li class="brands__item">
        <a href="#">
            ![](img/logo.png)
        </a>
    </li>
    <li> <!-- other logos --> </li>
</ul>

css

img {
    width: 130px;
    height: 75px;
    object-fit: contain;
}

通过定义widthheight,强制限定图像的大小,这是一个巨大的好处。 更好的是,我们可以将以上内容包装在@supports中,以避免在不支持对象适配的浏览器中拉伸徽标图像。

@supports (object-fit: contain) {
  img {
    object-fit: contain;
    height: 75px;
  }
}

代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

原文:https://ishadeed.com/article/...

交流

文章每周持续更新,可以微信搜索「 大迁世界 」第一时间阅读和催更(比博客早一到两篇哟),本文 GitHub https://github.com/qq449245884/xiaozhi 已经收录,整理了很多我的文档,欢迎Star和完善,大家面试可以参照考点复习,另外关注公众号,后台回复福利,即可看到福利,你懂的。

查看原文

赞 53 收藏 37 评论 1

前端小智 发布了文章 · 3月5日

通过编写简易虚拟DOM,来学习虚拟DOM 的知识!

作者:deathmood
译者:前端小智
来源:medium
点赞再看,养成习惯

本文 GitHubhttps://github.com/qq44924588... 上已经收录,更多往期高赞文章的分类,也整理了很多我的文档,和教程资料。欢迎Star和完善,大家面试可以参照考点复习,希望我们一起有点东西。

最近开源了一个 Vue 组件,还不够完善,欢迎大家来一起完善它,也希望大家能给个 star 支持一下,谢谢各位了。

github 地址:https://github.com/qq44924588...

要构建自己的虚拟DOM,需要知道两件事。你甚至不需要深入 React 的源代码或者深入任何其他虚拟DOM实现的源代码,因为它们是如此庞大和复杂——但实际上,虚拟DOM的主要部分只需不到50行代码。

有两个概念:

  • Virtual DOM 是真实DOM的映射
  • 当虚拟 DOM 树中的某些节点改变时,会得到一个新的虚拟树。算法对这两棵树(新树和旧树)进行比较,找出差异,然后只需要在真实的 DOM 上做出相应的改变。

用JS对象模拟DOM树

首先,我们需要以某种方式将 DOM 树存储在内存中。可以使用普通的 JS 对象来做。假设我们有这样一棵树:

<ul class=”list”>
  <li>item 1</li>
  <li>item 2</li>
</ul>

看起来很简单,对吧? 如何用JS对象来表示呢?

{ type: ‘ul’, props: { ‘class’: ‘list’ }, children: [
  { type: ‘li’, props: {}, children: [‘item 1’] },
  { type: ‘li’, props: {}, children: [‘item 2’] }
] }

这里有两件事需要注意:

  • 用如下对象表示DOM元素
{ type: ‘…’, props: { … }, children: [ … ] }

  • 用普通 JS 字符串表示 DOM 文本节点

但是用这种方式表示内容很多的 Dom 树是相当困难的。这里来写一个辅助函数,这样更容易理解:

function h(type, props, …children) {
  return { type, props, children };
}

用这个方法重新整理一开始代码:

h(‘ul’, { ‘class’: ‘list’ },
  h(‘li’, {}, ‘item 1’),
  h(‘li’, {}, ‘item 2’),
);

这样看起来简洁多了,还可以更进一步。这里使用 JSX,如下:

<ul className=”list”>
  <li>item 1</li>
  <li>item 2</li>
</ul>

编译成:

React.createElement(‘ul’, { className: ‘list’ },
  React.createElement(‘li’, {}, ‘item 1’),
  React.createElement(‘li’, {}, ‘item 2’),
);

是不是看起来有点熟悉?如果能够用我们刚定义的 h(...) 函数代替 React.createElement(…),那么我们也能使用JSX 语法。其实,只需要在源文件头部加上这么一句注释:

/** @jsx h */
<ul className=”list”>
  <li>item 1</li>
  <li>item 2</li>
</ul>

它实际上告诉 Babel ' 嘿,小老弟帮我编译 JSX 语法,用 h(...) 函数代替 React.createElement(…),然后 Babel 就开始编译。'

综上所述,我们将DOM写成这样:

/** @jsx h */
const a = (
  <ul className=”list”>
    <li>item 1</li>
    <li>item 2</li>
  </ul>
);

Babel 会帮我们编译成这样的代码:

const a = (
  h(‘ul’, { className: ‘list’ },
    h(‘li’, {}, ‘item 1’),
    h(‘li’, {}, ‘item 2’),
  );
);

当函数 “h” 执行时,它将返回普通JS对象-即我们的虚拟DOM:

const a = (
  { type: ‘ul’, props: { className: ‘list’ }, children: [
    { type: ‘li’, props: {}, children: [‘item 1’] },
    { type: ‘li’, props: {}, children: [‘item 2’] }
  ] }
);

从Virtual DOM 映射到真实 DOM

好了,现在我们有了 DOM 树,用普通的 JS 对象表示,还有我们自己的结构。这很酷,但我们需要从它创建一个真正的DOM。

首先让我们做一些假设并声明一些术语:

  • 使用以' $ '开头的变量表示真正的DOM节点(元素,文本节点),因此 $parent 将会是一个真实的DOM元素
  • 虚拟 DOM 使用名为 node 的变量表示

* 就像在 React 中一样,只能有一个根节点——所有其他节点都在其中

那么,来编写一个函数 createElement(…),它将获取一个虚拟 DOM 节点并返回一个真实的 DOM 节点。这里先不考虑 propschildren 属性:

function createElement(node) {
  if (typeof node === ‘string’) {
    return document.createTextNode(node);
  }
  return document.createElement(node.type);
}

上述方法我也可以创建有两种节点分别是文本节点和 Dom 元素节点,它们是类型为的 JS 对象:

{ type: ‘…’, props: { … }, children: [ … ] }

因此,可以在函数 createElement 传入虚拟文本节点和虚拟元素节点——这是可行的。

现在让我们考虑子节点——它们中的每一个都是文本节点或元素。所以它们也可以用 createElement(…) 函数创建。是的,这就像递归一样,所以我们可以为每个元素的子元素调用 createElement(…),然后使用 appendChild() 添加到我们的元素中:

function createElement(node) {
  if (typeof node === ‘string’) {
    return document.createTextNode(node);
  }
  const $el = document.createElement(node.type);
  node.children
    .map(createElement)
    .forEach($el.appendChild.bind($el));
  return $el;
}

哇,看起来不错。先把节点 props 属性放到一边。待会再谈。我们不需要它们来理解虚拟DOM的基本概念,因为它们会增加复杂性。

完整代码如下:

/** @jsx h */

function h(type, props, ...children) {
  return { type, props, children };
}

function createElement(node) {
  if (typeof node === 'string') {
    return document.createTextNode(node);
  }
  const $el = document.createElement(node.type);
  node.children
    .map(createElement)
    .forEach($el.appendChild.bind($el));
  return $el;
}

const a = (
  <ul class="list">
    <li>item 1</li>
    <li>item 2</li>
  </ul>
);

const $root = document.getElementById('root');
$root.appendChild(createElement(a));

比较两棵虚拟DOM树的差异

现在我们可以将虚拟 DOM 转换为真实的 DOM,这就需要考虑比较两棵 DOM 树的差异。基本的,我们需要一个算法来比较新的树和旧的树,它能够让我们知道什么地方改变了,然后相应的去改变真实的 DOM。

怎么比较 DOM 树?需要处理下面的情况:

  • 添加新节点,使用 appendChild(…) 方法添加节点

图片描述

  • 移除老节点,使用 removeChild(…) 方法移除老的节点

图片描述

  • 节点的替换,使用 replaceChild(…) 方法

图片描述

如果节点相同的——就需要需要深度比较子节点

图片描述

编写一个名为 updateElement(…) 的函数,它接受三个参数—— $parentnewNodeoldNode,其中 $parent 是虚拟节点的一个实际 DOM 元素的父元素。现在来看看如何处理上面描述的所有情况。

添加新节点

function updateElement($parent, newNode, oldNode) {
  if (!oldNode) {
    $parent.appendChild(
      createElement(newNode)
    );
  }
}

移除老节点

这里遇到了一个问题——如果在新虚拟树的当前位置没有节点——我们应该从实际的 DOM 中删除它—— 这要如何做呢?

如果我们已知父元素(通过参数传递),我们就能调用 $parent.removeChild(…) 方法把变化映射到真实的 DOM 上。但前提是我们得知道我们的节点在父元素上的索引,我们才能通过 $parent.childNodes[index] 得到该节点的引用。

好的,让我们假设这个索引将被传递给 updateElement 函数(它确实会被传递——稍后将看到)。代码如下:

function updateElement($parent, newNode, oldNode, index = 0) {
  if (!oldNode) {
    $parent.appendChild(
      createElement(newNode)
    );
  } else if (!newNode) {
    $parent.removeChild(
      $parent.childNodes[index]
    );
  }
}

节点的替换

首先,需要编写一个函数来比较两个节点(旧节点和新节点),并告诉节点是否真的发生了变化。还有需要考虑这个节点可以是元素或是文本节点:

function changed(node1, node2) {
  return typeof node1 !== typeof node2 ||
         typeof node1 === ‘string’ && node1 !== node2 ||
         node1.type !== node2.type
}

现在,当前的节点有了 index 属性,就可以很简单的用新节点替换它:

function updateElement($parent, newNode, oldNode, index = 0) {
  if (!oldNode) {
    $parent.appendChild(
      createElement(newNode)
    );
  } else if (!newNode) {
    $parent.removeChild(
      $parent.childNodes[index]
    );
  } else if (changed(newNode, oldNode)) {
    $parent.replaceChild(
      createElement(newNode),
      $parent.childNodes[index]
    );
  }
}

比较子节点

最后,但并非最不重要的是——我们应该遍历这两个节点的每一个子节点并比较它们——实际上为每个节点调用updateElement(…)方法,同样需要用到递归。

  • 当节点是 DOM 元素时我们才需要比较( 文本节点没有子节点 )
  • 我们需要传递当前的节点的引用作为父节点
  • 我们应该一个一个的比较所有的子节点,即使它是 undefined 也没有关系,我们的函数也会正确处理它。
  • 最后是 index,它是子数组中子节点的 index
function updateElement($parent, newNode, oldNode, index = 0) {
  if (!oldNode) {
    $parent.appendChild(
      createElement(newNode)
    );
  } else if (!newNode) {
    $parent.removeChild(
      $parent.childNodes[index]
    );
  } else if (changed(newNode, oldNode)) {
    $parent.replaceChild(
      createElement(newNode),
      $parent.childNodes[index]
    );
  } else if (newNode.type) {
    const newLength = newNode.children.length;
    const oldLength = oldNode.children.length;
    for (let i = 0; i < newLength || i < oldLength; i++) {
      updateElement(
        $parent.childNodes[index],
        newNode.children[i],
        oldNode.children[i],
        i
      );
    }
  }
}

完整的代码

Babel+JSX
/* @jsx h /

function h(type, props, ...children) {
  return { type, props, children };
}

function createElement(node) {
  if (typeof node === 'string') {
    return document.createTextNode(node);
  }
  const $el = document.createElement(node.type);
  node.children
    .map(createElement)
    .forEach($el.appendChild.bind($el));
  return $el;
}

function changed(node1, node2) {
  return typeof node1 !== typeof node2 ||
         typeof node1 === 'string' && node1 !== node2 ||
         node1.type !== node2.type
}

function updateElement($parent, newNode, oldNode, index = 0) {
  if (!oldNode) {
    $parent.appendChild(
      createElement(newNode)
    );
  } else if (!newNode) {
    $parent.removeChild(
      $parent.childNodes[index]
    );
  } else if (changed(newNode, oldNode)) {
    $parent.replaceChild(
      createElement(newNode),
      $parent.childNodes[index]
    );
  } else if (newNode.type) {
    const newLength = newNode.children.length;
    const oldLength = oldNode.children.length;
    for (let i = 0; i < newLength || i < oldLength; i++) {
      updateElement(
        $parent.childNodes[index],
        newNode.children[i],
        oldNode.children[i],
        i
      );
    }
  }
}

// ---------------------------------------------------------------------

const a = (
  <ul>
    <li>item 1</li>
    <li>item 2</li>
  </ul>
);

const b = (
  <ul>
    <li>item 1</li>
    <li>hello!</li>
  </ul>
);

const $root = document.getElementById('root');
const $reload = document.getElementById('reload');

updateElement($root, a);
$reload.addEventListener('click', () => {
  updateElement($root, b, a);
});

HTML

<button id="reload">RELOAD</button>
<div id="root"></div>

CSS

#root {
  border: 1px solid black;
  padding: 10px;
  margin: 30px 0 0 0;
}

打开开发者工具,并观察当按下“Reload”按钮时应用的更改。

图片描述

总结

现在我们已经编写了虚拟 DOM 实现及了解它的工作原理。作者希望,在阅读了本文之后,对理解虚拟 DOM 如何工作的基本概念以及在幕后如何进行响应有一定的了解。

然而,这里有一些东西没有突出显示(将在以后的文章中介绍它们):

  • 设置元素属性(props)并进行 diffing/updating
  • 处理事件——向元素中添加事件监听
  • 让虚拟 DOM 与组件一起工作,比如React
  • 获取对实际DOM节点的引用
  • 使用带有库的虚拟 DOM,这些库可以直接改变真实的 DOM,比如 jQuery 及其插件

原文:
https://medium.com/@deathmood...

代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug


交流

文章每周持续更新,可以微信搜索「 大迁世界 」第一时间阅读和催更(比博客早一到两篇哟),本文 GitHub https://github.com/qq449245884/xiaozhi 已经收录,整理了很多我的文档,欢迎Star和完善,大家面试可以参照考点复习,另外关注公众号,后台回复福利,即可看到福利,你懂的。

查看原文

赞 26 收藏 21 评论 0

前端小智 发布了文章 · 3月2日

10个HTML文件上传技巧

作者: Tapas Adhikary
译者:前端小智
来源:dev
点赞再看,养成习惯

本文 GitHubhttps://github.com/qq44924588... 上已经收录,更多往期高赞文章的分类,也整理了很多我的文档,和教程资料。欢迎Star和完善,大家面试可以参照考点复习,希望我们一起有点东西。

最近开源了一个 Vue 组件,还不够完善,欢迎大家来一起完善它,也希望大家能给个 star 支持一下,谢谢各位了。

github 地址:https://github.com/qq44924588...

简介

上传文件功能可以说是项目经常出现的需求。从在社交媒体上上传照片到在求职网站上发布简历,文件上传无处不在。在本文中,我们将讨论 HTML文件上传支持的10种用法,希望对你有用。

1. 单文件上传

我们可以将input 类型指定为file,以在Web应用程序中使用文件上传功能。

<input type="file" id="file-uploader">

input filte 提供按钮上传一个或多个文件。 默认情况下,它使用操作系统的本机文件浏览器上传单个文件。成功上传后,File API 使得可以使用简单的 JS 代码读取File对象。 要读取File对象,我们需要监听 change事件。

首先,通过id获取文件上传的实例:

const fileUploader = document.getElementById('file-uploader');

然后添加一个change 事件侦听器,以在上传完成后读取文件对象, 我们从event.target.files属性获取上传的文件信息:

fileUploader.addEventListener('change', (event) => {
  const files = event.target.files;
  console.log('files', files);
});

在控制台中观察输出结果,这里关注一下FileList数组和File对象,该对象具有有关上传文件的所有元数据信息。

clipboard.png

如果大家看到这里,有点激动,想手贱一下,可以 CodePen 玩玩,地址:https://codepen.io/atapas/pen...

2. 多文件上传

如果我们想上传多个文件,需要在标签上添加 multiple 属性:

<input type="file" id="file-uploader" multiple />

现在,我们可以上传多个文件了,以前面事例为基础,选择多个文件上传后,观察一下控制台的变化:

clipboard.png

如果大家看到这里,有点激动,想手贱一下,可以 CodePen 玩玩,地址:https://codepen.io/atapas/pen...

3.了解文件元数据

每当我们上传文件时,File对象都有元数据信息,例如file namesize,last update time,type 等等。这些信息对于进一步的验证和特殊处理很有用。

const fileUploader = document.getElementById('file-uploader');

// 听更 change 件并读取元数据
fileUploader.addEventListener('change', (event) => {
  // 获取文件列表数组
  const files = event.target.files;

  // 遍历并获取元数据
  for (const file of files) {
    const name = file.name;
    const type = file.type ? file.type: 'NA';
    const size = file.size;
    const lastModified = file.lastModified;
    console.log({ file, name, type, size, lastModified });
  }
});

下面是单个文件上传的输出结果:

clipboard.png

如果大家看到这里,有点激动,想手贱一下,可以 CodePen 玩玩,地址:https://codepen.io/atapas/pen...

4.了解 accept 属性

我们可以使用accept属性来限制要上载的文件的类型,如果只想上传的文件格式是 .jpg.png 时,可以这么做:

<input type="file" id="file-uploader" accept=".jpg, .png" multiple>

在上面的代码中,只能选择后缀是.jpg.png的文件。

如果大家看到这里,有点激动,想手贱一下,可以 CodePen 玩玩,地址:https://codepen.io/atapas/pen...

5. 管理文件内容

成功上传文件后显示文件内容,站在用户的角度上,如果上传之后,没有一个预览的,就很奇怪也不体贴。

我们可以使用FileReader对象将文件转换为二进制字符串。 然后添加load 事件侦听器,以在成功上传文件时获取二进制字符串。

// FileReader 实例
const reader = new FileReader();

fileUploader.addEventListener('change', (event) => {
  const files = event.target.files;
  const file = files[0];

  reader.readAsDataURL(file);

  reader.addEventListener('load', (event) => {
    const img = document.createElement('img');
    imageGrid.appendChild(img);
    img.src = event.target.result;
    img.alt = file.name;
  });
});

如果大家看到这里,有点激动,想手贱一下,可以 CodePen 玩玩,地址:https://codepen.io/atapas/pen...

6.验证文件大小

如果用户上传图片过大,为了不让服务器有压力,我们需要限制图片的大小,下面是允许用户上传小于 1M 的图片,如果大于 1M 将上传失败。

fileUploader.addEventListener('change', (event) => {
  // Read the file size
  const file = event.target.files[0];
  const size = file.size;

  let msg = '';

 // 检查文件大小是否大于1MB
  if (size > 1024 * 1024) {
      msg = `<span style="color:red;">The allowed file size is 1MB. The file you are trying to upload is of ${returnFileSize(size)}</span>`;
  } else {
      msg = `<span style="color:green;"> A ${returnFileSize(size)} file has been uploaded successfully. </span>`;
  }
  feedback.innerHTML = msg;
});

如果大家看到这里,有点激动,想手贱一下,可以 CodePen 玩玩,地址:https://codepen.io/atapas/pen...

7. 显示文件上传进度

更好的用户体验是让用户知道文件上传进度,前面我们用过了FileReader以及读取和加载文件的事件。

const reader = new FileReader();

FileReader还有一个progress 事件,表示当前上传进度,配合HTML5的progress标签,我们来模拟一下文件的上传进度。

reader.addEventListener('progress', (event) => {
  if (event.loaded && event.total) {
    // 计算完成百分比
    const percent = (event.loaded / event.total) * 100;
    // 将值绑定到 `progress`标签
    progress.value = percent;
  }
});

clipboard.png

如果大家看到这里,有点激动,想手贱一下,可以 CodePen 玩玩,地址:https://codepen.io/atapas/pen...

8. 怎么上传目录上传?

我们可以上传整个目录吗?嗯,这是可能的,但有一些限制。有一个叫做webkitdirectory的非标准属性(目前只有谷歌浏览器还有Microsoft Edge支持按照文件夹进行上传),它允许我们上传整个目录。

目前只有谷歌浏览器还有Microsoft Edge支持按照文件夹进行上传,具体可以看下百度云盘的网页版的上传按钮,在火狐下就支持按照文件进行上传,而在谷歌和Edge下,就会给用户提供一个下拉,让用户选择是根据文件进行上传还是根据文件夹进行上传。
<input type="file" id="file-uploader" webkitdirectory />

用户必须需要确认才能上传目录

clipboard.png

用户单击“上传”按钮后,就会进行上传。 这里要注意的重要一点。 FileList数组将以平面结构的形式包含有关上载目录中所有文件的信息。 对于每个File对象,webkitRelativePath属性表示目录路径。

例如,上传一个主目录及其下的其他文件夹和文件:

clipboard.png

现在,File 对象将将webkitRelativePath填充为:

clipboard.png

如果大家看到这里,有点激动,想手贱一下,可以 CodePen 玩玩,地址:https://codepen.io/atapas/pen...

9. 拖拽上传

不支持文件上传的拖拽就有点 low 了,不是吗?我们来看看如何通过几个简单的步骤实现这一点。

首先,创建一个拖放区域和一个可选的区域来显示上传的文件内容。

<div id="container">
  <h1>Drag & Drop an Image</h1>
  <div id="drop-zone">
    DROP HERE
  </div>

  <div id="content">
    Your image to appear here..
  </div>

</div>

通过它们各自的ID获取dropzonecontent 区域。

 const dropZone = document.getElementById('drop-zone');
 const content = document.getElementById('content');

添加一个dragover 事件处理程序,以显示将要复制的内容的效果:

dropZone.addEventListener('dragover', event => {
  event.stopPropagation();
  event.preventDefault();
  event.dataTransfer.dropEffect = 'copy';
});

clipboard.png

接下来,我们需要一个drop事件监听器来处理。

dropZone.addEventListener('drop', event => {
  // Get the files
  const files = event.dataTransfer.files;


});

如果大家看到这里,有点激动,想手贱一下,可以 CodePen 玩玩,地址:https://codepen.io/atapas/pen...

10. 使用objectURL处理文件

有一个特殊的方法叫做URL.createobjecturl(),用于从文件中创建唯一的URL。还可以使用URL.revokeObjectURL()方法来释放它。

URL.revokeObjectURL() 静态方法用来释放一个之前已经存在的、通过调用 URL.createObjectURL() 创建的 URL 对象。当你结束使用某个 URL 对象之后,应该通过调用这个方法来让浏览器知道不用在内存中继续保留对这个文件的引用了。

fileUploader.addEventListener('change', (event) => {
  const files = event.target.files;
  const file = files[0];
  
  const img = document.createElement('img');
  imageGrid.appendChild(img);
  img.src = URL.createObjectURL(file);
  img.alt = file.name;
});

如果大家看到这里,有点激动,想手贱一下,可以 CodePen 玩玩,地址:https://codepen.io/atapas/pen...

总结

无论何时,如果你还想学习本文涉及的一些知识,你可以在这里尝试。

https://html-file-upload.netl...


代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

原文:https://dev.to/atapas/10-usef...

交流

文章每周持续更新,可以微信搜索「 大迁世界 」第一时间阅读和催更(比博客早一到两篇哟),本文 GitHub https://github.com/qq449245884/xiaozhi 已经收录,整理了很多我的文档,欢迎Star和完善,大家面试可以参照考点复习,另外关注公众号,后台回复福利,即可看到福利,你懂的。

查看原文

赞 40 收藏 36 评论 1

前端小智 发布了文章 · 2月26日

30道CSS 面试知识点总结

译者:前端小智
来源:codersera.
点赞再看,微信搜索大迁世界,B站关注前端小智这个没有大厂背景,但有着一股向上积极心态人。本文 GitHubhttps://github.com/qq44924588... 上已经收录,文章的已分类,也整理了很多我的文档,和教程资料。

CSS是层叠样式表( Cascading Style Sheets )的缩写,是一种样式表语言,用于描述以 HTML 之类的标记语言编写的文档的布局。 它是用于设计Web页面的三剑客之一,另外两位浩客是HTMLJavascript

CSS 的设计目的是使样式和内容分离,包括布局、颜色和字体。这种分离可以提高内容的可访问性,在样式特征的规范中提供更多的灵活性和控制,通过在一个单独的. .css 文件中指定相关的 CSS,使多个 web 页面能够共享格式,并减少结构内容中的复杂性和重复。

它具有简单的语法,并使用大量的英文关键字来指定各种样式属性的名称。

既然我们已经讨论了CSS的基础知识,让我们来观察一下基于CSS的重要面试问题。

clipboard.png

问题1:什么是 CSS?

CSS(层叠样式表)是一种样式语言,对于 HTML 元素来说足够简单。 它在网页设计中非常流行,其应用在XHTML中也很常见。

问题2:为什么要开发CSS?

CSS是在1997年开发的,作为一种web开发人员设计他们正在创建的web页面布局的方法。它的目的是让开发者将网站代码的内容和结构从视觉设计中分离出来。

这种结构和设计的分离允许HTML执行比原来更多的功能。

问题3:CSS的主要版本有哪些?

CSS的不同版本:

  1. CSS1
  2. CSS2
  3. CSS2.1
  4. CSS3

问题4:CSS样式的组成部分是什么?

一个样式规则由三部分组成:

  1. 选择器–选择器是 HTML 标记,用于选择要设置样式的内容。 它根据其ID,类和名称选择 HTML元素。
  2. 属性–属性是 HTML 标签的一种属性。 简而言之,所有 HTML 属性都转换为 CSS 属性。
  3. – CSS中的值定义CSS属性的一组有效值。

问题 5:有多少种方法可以将 CSS 集成为 web 页面

CSS 可以集成为三种方式

  1. 内联:直接在HTML元素上使用
<p style=”colour:skyblue;”>hello world</p>
  1. 外部:在工作空间中创建单独的CSS文件,然后在创建的每个web页面中链接它们
<head>

<link rel=”text/css”href=”your_CSS_file_location”/>

</head>
  • 内部: web 页面的 head 元素在其中实现了内部 CSS。
head>
     <style> 
             P{
                   color : lime;
               background-color:black;
                }
     </style>
</head>

问题 6:谁在维护 CSS 规范?

万维网协会维护 CSS规范。

问题 7:伪元素是什么意思?

伪元素是添加到选择器的关键字,它允许一种样式,即所选元素的特定部分。CSS用于在HTML标记中应用样式,它允许在不影响实际文档的情况下对文档进行额外标记。它可以用来:

  1. 为第一个字母、行或元素设置样式。
  2. 插入内容

语法:

Selector: :pseudo-element
{Property1 :value;
Property2 :value;}

问题 8:CSS有什么优势?

CSS的优点是:

  1. 一致性 – CSS有助于构建一致的框架,设计人员可以使用该框架来构建其他站点。 因此,网页设计师的效率也提高了。
  2. 易于使用 – CSS 是非常容易学习和简化网站开发。所有代码都放在一个页面上,这意味着对代码行进行改进或编辑不需要重复修改多个页面.
  3. *网站速度 *– 通常,一个网站使用的代码最多可以达到 2 页或更多。但是对于CSS,这不是问题。它只需要2-3行代码,因此,网站数据库保持整洁,消除任何网站加载问题。
  4. 设备兼容性 – 由于人们使用不同类型的智能设备访问互联网,因此需要响应式web设计。CSS 在这里的作用是使 web 页面的响应性更好,这样它们就可以在所有设备中以相同的方式显示。
  5. 多浏览器支持 – CSS享有多浏览器的支持,它与所有主要的互联网浏览器兼容。
  6. 重新定位 – CSS允许您定义页面上 web 元素位置的变化。通过它的实现,开发人员可以将 HTML 元素放置在他们喜欢的位置,以便与页面的美学吸引力或其他考虑因素保持一致。

问题9:CSS 渐变是什么?

渐变是指我们在两幅图像之间创建中间帧,以获得第一幅图像的外观,然后发展成第二幅图像的过程,它主要用于创建动画。

问题10:什么是 CSS 特异性?

CSS 特定性是一个分数或等级,它决定了元素必须使用哪种样式声明。 CSS 中有四类可以授权选择器的特异性级别:

  1. 内联样式
  2. ID
  3. 类,属性和伪类
  4. 元素和伪元素

问题12:CSS有什么缺点

CSS的缺点有:

  1. 版本太多 – 与HTML或Javascript等其他参数相比,CSS有很多版本-CSS1,CSS2,CSS2.1,CSS3。 因此,CSS变得非常混乱,尤其是对于初学者。
  2. 缺乏安全性 - 由于CSS是基于开放文本的系统,因此它没有内置的安全系统来防止其被覆盖。 通过对其读/写操作的访问,任何人都可以更改 CSS 文件并更改链接。
  3. Fragmentation - 使用 CSS,可能无法在一个浏览器上使用另一浏览器。 因此,在网站上线之前,Web 开发人员必须通过在多个浏览器上运行程序来测试兼容性。
  4. 复杂性–使用 Microsoft FrontPage 等第三方软件会使CSS变得复杂。

问题13:什么是 RWD (Responsive Web Design)?

RWD(响应式Web设计)技术用于在每种屏幕尺寸以及移动,平板电脑,台式机和笔记本电脑等设备上完美显示设计页面,让我们无需为每个设备创建不同的页面。

问题14:CSS 精灵有什么好处?

CSS精灵的好处有:

  1. 通过将各种小图像组合成一个图像,减少了web页面的加载时间。
  2. 减少HTTP请求,从而减少加载时间。

问题 15:什么是 CSS 上下文选择器?

上下文选择器,严格来讲,叫后代组合式选择器,就是一组以空格分隔的标签名。用于选择作为指定祖先元素后代的标签。只要有标签在它的层次结构“上游”存在这么一个祖先,那么就会选中该标签。无论从该标签到作为祖先的上下文之间隔着多少层次都没有关系。


大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】

我和阿里云合作服务器,折扣价比较便宜:89/年,223/3年,比学生9.9每月还便宜,买了搭建个项目,熟悉技术栈比较香(老用户用家人账号买就好了,我用我妈的)推荐买三年的划算点,点击本条就可以查看。


问题 16:什么是渐进增强和平稳退化?

渐进增强的概念是指从最基本的可用性出发,在保证站点页面在低级浏览器中 的可用性和可访问性的基础上,逐步增加功能及提高用户体验。本质上讲,我们日常的一些开发习惯,例如首先使用标记语言编写页面,然后通过样式表来控制页面 样式等,都属于渐进增强的概念;其他更为明显的行为包括使用HTML5、CSS3等新技术,针对高级浏览器为页面提高用户体验的丰富程度。

平稳退化的概念是指首先使用最新的技术面向高级浏览器构建最强的功能及用户体验,然后针对低级浏览器的限制,逐步衰减那些无法被支持的功能及体验;在我们日常的开 发中,一个典型的平稳退化的例子就是首先针对Chrome编写页面代码,然后修复IE中的异常或针对IE去除那些无法被实现的功能特色.

所以, 这两个概念方法其实早已并存在我们的日常开发工作中了,只是“渐进增强”与“平稳退化”这样的措辞是近些年才开始被普及。在我们眼下的HTML5与 CSS3实战用,这两个概念就尤其重要了,怎样保证使用不断变化的新技术来构建在主流浏览器下都具有基本可用性的站点,并针对高级浏览器进行体验提升,这 些是我们在开发过程中需要明确的思路。

问题 17:我们如何在网页上添加图标?

我们可以使用诸如font-awesome或者阿里的 iconfont 之类的图标库将图标添加到HTML网页。 我们必须将给定图标类的名称添加到任何内联HTML元素中。 (<i><span>)。 图标库中的图标是可缩放的矢量,可以使用CSS进行自定义。

问题 18:哪个属性指定边框的宽度?

border-width指定边框的宽度。

问题 19:如何区分物理标签和逻辑标签?

物理标签被称为表示标记,而逻辑标签对于外观是无用的。物理标签是较新的版本,而逻辑标签是旧的并且专注于内容。

如题,我们的标签元素写上后,浏览器就会渲染出结果,但不仅仅是这么简单

//物理元素
<b>我想用b标签加粗</b>
 
//逻辑元素
<strong>我想用strong标签加粗</strong>
 
//两段文字都加粗了,而且视觉效果完全一样

确实,文字加粗了,两者都达到了我们想要的目的,但是我们忽略了一个问题,既然b标签可以加粗,那么strong这个标签同样是加粗,存在的 意义又是什么呢?既然W3C定义了两个,它们之间的不同点是什么呢?它们之间的相同点又是什么呢?

物理元素

物理元素,又叫实体标签,它所做的是一种物理行为,比如上面我把一段文字用b标签加粗了,它所传达的给浏览器,告诉浏览器 我要加粗这段文字,从单词Bold中也可以看出来,英文中仅仅是加粗的意思,并没有其他作用。总结来说就是一句话: 物理元素就是告诉浏览器该怎么显示出来。

逻辑元素

逻辑元素,从英文字面上Strong就可以看出它是强调的意思,所以我们用这个逻辑元素(如上strong)来向浏览器传达 一个强调某段文字重要性的消息,说明此文字较为重要,也有利于搜索引擎收录。

Web标准主张XHTML不涉及具体的表现形式,“强调”可以用加粗来强调,也可以用别的方式强调,也可以通过css来改变strong的具体表现 ,还有就是并不是有了strong逻辑标签,就不用b标签来表示字体加粗了,b标签和strong标签默认情况下强调的效果一致,strong完全可以定义成别的样式,用来强调 效果,但是最好符合W3C标准,它更提倡内容与样式分离,所以单纯为了达到加粗而使用b标签不建议这样做, 从XHTML文档有意义性及用户体验角度来说,strong逻辑标签更加合适,而SEO方面,则针对优化情况而定。

问题 20:如何在CSS中定义一个伪类?它们是用来干什么的

CSS伪类是用来添加一些选择器的特殊效果。伪类的语法

selector:pseudo-class{property:value;}

问题 21:CSS和SCSS有什么区别?

CSSSCSS 之间的区别如下:

  1. CSS是一种用于设计web页面的样式语言,而SCSS用于为浏览器组合CSS样式表。
  2. SCSS 提供了一些变量,可以使用这些变量来缩短代码,这是与 CSS 相比的一大优势。

问题 22:嵌入式样式表的优缺点是什么?

嵌入式样式表的优点:

  1. 可以在一个文档中创建多种标签类型。
  2. 在复杂情况下,可以使用选择器和分组方法来应用样式。
  3. 无需额外下载。

嵌入式样式表的缺点:

无法控制多个文档。

问题 23:列出使用的各种媒体类型。

不同的介质不区分大小写,因此它们具有不同的属性。 他们是:

  1. aural - 用于语音和音频合成器
  2. print - 用于打印机
  3. projection - 用于方案展示,比如幻灯片
  4. handheld - 用于小的手持的设备
  5. screen - 用于电脑显示器

问题 24:font 的属性有哪些?

  1. Font-style
  2. Font-variant
  3. Font-weight
  4. Font-size/line-weight
  5. Font-family

问题 25:“规则集”是什么意思?

该指令告诉浏览器如何在HTML页面上渲染特定元素。 它由一个选择器和一个遵循规则集的声明块组成。 选择器可以附加到其他选择器,以通过规则集进行标识。

问题 26:什么是 CSS 框架?

CSS 框架是一个库,它允许使用CSS语言进行更轻松,更符合标准的Web设计。 这些框架中的大多数至少包含一个网格以及更多功能和其他基于Javascript的功能。 一些著名的CSS框架有:ACSS,Bulma,YAML,Foundation等。

问题 27:简单介绍使用图片 base64 编码的优点和缺点。

base64编码是一种图片处理格式,通过特定的算法将图片编码成一长串字符串,在页面上显示的时候,可以用该字符串来代替图片的
url属性。
使用base64的优点是:
(1)减少一个图片的HTTP请求
使用base64的缺点是:
(1)根据base64的编码原理,编码后的大小会比原文件大小大1/3,如果把大图片编码到html/css中,不仅会造成文件体
积的增加,影响文件的加载速度,还会增加浏览器对html或css文件解析渲染的时间。
(2)使用base64无法直接缓存,要缓存只能缓存包含base64的文件,比如HTML或者CSS,这相比域直接缓存图片的效果要
差很多。
(3)兼容性的问题,ie8以前的浏览器不支持。
一般一些网站的小图标可以使用base64图片来引入。

问题 28: 对 BFC 规范(块级格式化上下文:block formatting context)的理解?

BFC指的是块级格式化上下文,一个元素形成了BFC之后,那么它内部元素产生的布局不会影响到外部元素,外部元素的布局也
不会影响到BFC中的内部元素。一个BFC就像是一个隔离区域,和其他区域互不影响。
一般来说根元素是一个BFC区域,浮动和绝对定位的元素也会形成BFC,display属性的值为inline-block、flex这些
属性时也会创建BFC。还有就是元素的overflow的值不为visible时都会创建BFC。

问题 29: IFC 是什么?

IFC指的是行级格式化上下文,它有这样的一些布局规则:
(1)行级上下文内部的盒子会在水平方向,一个接一个地放置。
(2)当一行不够的时候会自动切换到下一行。
(3)行级上下文的高度由内部最高的内联盒子的高度决定。

问题 30: CSS 优化、提高性能的方法有哪些?

加载性能:
(1)css压缩:将写好的css进行打包压缩,可以减少很多的体积。
(2)css单一样式:当需要下边距和左边距的时候,很多时候选择:margin:top 0 bottom 0;但margin-bottom:bot
tom;margin-left:left;执行的效率更高。
(3)减少使用@import,而建议使用link,因为后者在页面加载时一起加载,前者是等待页面加载完成之后再进行加载。
选择器性能:
(1)关键选择器(key selector)。选择器的最后面的部分为关键选择器(即用来匹配目标元素的部分)。CSS选择符是从右到
左进行匹配的。当使用后代选择器的时候,浏览器会遍历所有子元素来确定是否是指定的元素等等;
(2)如果规则拥有ID选择器作为其关键选择器,则不要为规则增加标签。过滤掉无关的规则(这样样式系统就不会浪费时间去匹
配它们了)。
(3)避免使用通配规则,如*{}计算次数惊人!只对需要用到的元素进行选择。
(4)尽量少的去对标签进行选择,而是用class。
(5)尽量少的去使用后代选择器,降低选择器的权重值。后代选择器的开销是最高的,尽量将选择器的深度降到最低,最高不要超过
三层,更多的使用类来关联每一个标签元素。
(6)了解哪些属性是可以通过继承而来的,然后避免对这些属性重复指定规则。
渲染性能:
(1)慎重使用高性能属性:浮动、定位。
(2)尽量减少页面重排、重绘。
(3)去除空规则:{}。空规则的产生原因一般来说是为了预留样式。去除这些空规则无疑能减少css文档体积。
(4)属性值为0时,不加单位。
(5)属性值为浮动小数0.**,可以省略小数点之前的0。
(6)标准化各种浏览器前缀:带浏览器前缀的在前。标准属性在后。
(7)不使用@import前缀,它会影响css的加载速度。
(8)选择器优化嵌套,尽量避免层级过深。
(9)css雪碧图,同一页面相近部分的小图标,方便使用,减少页面的请求次数,但是同时图片本身会变大,使用时,优劣考虑清
楚,再使用。
(10)正确使用display的属性,由于display的作用,某些样式组合会无效,徒增样式体积的同时也影响解析性能。
(11)不滥用web字体。对于中文网站来说WebFonts可能很陌生,国外却很流行。web fonts通常体积庞大,而且一些浏
览器在下载web fonts时会阻塞页面渲染损伤性能。
可维护性、健壮性:
(1)将具有相同属性的样式抽离出来,整合并通过class在页面中进行使用,提高css的可维护性。
(2)样式与内容分离:将css代码定义到外部css中。

代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

原文:https://codersera.com/blog/to...


交流

文章每周持续更新,可以微信搜索【大迁世界 】第一时间阅读,回复【福利】有多份前端视频等着你,本文 GitHub https://github.com/qq449245884/xiaozhi 已经收录,欢迎Star。

查看原文

赞 21 收藏 17 评论 2

前端小智 发布了文章 · 2月19日

8 种用于前端性能分析工具

作者:Mahdhi Rezvi
译者:前端小智
来源:blog.bitsrc
点赞再看,微信搜索大迁世界,B站关注前端小智这个没有大厂背景,但有着一股向上积极心态人。本文 GitHubhttps://github.com/qq44924588... 上已经收录,文章的已分类,也整理了很多我的文档,和教程资料。

最近开源了一个 Vue 组件,还不够完善,欢迎大家来一起完善它,也希望大家能给个 star 支持一下,谢谢各位了。

github 地址:https://github.com/qq44924588...

我们可以编写一些漂亮,且吸引人的网站,但如果该网站无法快速加载到浏览器中,人们往往会跳过它。 尽管有许多性能规则,但归根结底,这全都取决于加载时间。

根据 Jakob Nielson说法,在网建的网站时,需要注意以下几点:

  • 小于100毫秒加载速度才是爽的
  • 100ms到300ms 感觉良好
  • 一秒钟大概是用户思路不被打断的极限。用户会感觉到延迟,但还可以接受
  • 47%的用户希望网页能在两秒或更短的时间内加载
  • 40% 的用户如果网页加载超过 3 秒,表示会放弃该网站
  • 10秒左右是用户注意力的极限。 大多数用户会在10秒后离开你的网站

如上所述,即使在最差的网络带宽上,也需要确保页面尽快加载。 但这个说起来容易做起来难。

为了能帮助更好的实现这一目标,这里推荐了几个性能分析工具。

1. PageSpeed Insights

这是一个免费的服务,分析网页的内容,然后提出建议,使该网页更快。它为您提供了关键指标,如第一个内容绘制,总阻塞时间和更多。度量标准被分类为Field DataOrigin Summary,Lab Data,OpportunitiesDiagnosticsPassed Audits。它还为我们提供了进一步改进的建议。

clipboard.png

2. Lighthouse

Lighthouse 是一个开源的自动化工具,用于改进网络应用的质量。 你可以将其作为一个 Chrome 扩展程序运行,或从命令行运行。 你为 Lighthouse 提供一个您要审查的网址,它将针对此页面运行一连串的测试,然后生成一个有关页面性能的报告。

从此处,您可以失败的测试为指示器,看看可以采取哪些措施来改进您的应用。

注意: Lighthouse 目前非常关注 Progressive Web App (PWA) 功能,如“添加到主屏幕”和离线支持。不过,此项目的首要目标是针对网络应用质量的各个方面提供端到端审查。

运行 Lighthouse 的方式有两种:作为 Chrome 扩展程序运行,或作为命令行工具运行。 Chrome 扩展程序提供了一个对用户更友好的界面,方便读取报告。 命令行工具允许您将 Lighthouse 集成到持续集成系统。

clipboard.png

3. WebPageTest

WebPageTest是一个在线的免费性能评测网站,支持IE,Chrome,使用真正的浏览器(IE和Chrome)和真实的消费者连接速度,从全球多个地点运行免费网站速度测试。可以运行简单的测试或执行高级测试,包括多步骤事务、视频捕获、内容阻塞等等。还将依据测试结果提供丰富的诊断信息,包括资源加载瀑布图,页面速度优化检查和改进建议,会给每一项内容一个最终的评级。

WebpageTest 主要提供了Advanced Testing、simple Testing、Visual Comparison、Traceroute四个功能,我们主要关注Advanced Testing。

如何使用WebPageTest

  • 填写需要测试的URL
  • 填写Test Location,下拉选择即可,可选移动端设备(包括Android、IOS),可选PC端(分地区,每个地区可支持的浏览器不同)
  • 可选支持的浏览器
  • 点击右侧START TEST

clipboard.png

4. Pingdom

我最喜欢的功能是分析后的的摘要,其中会为我们提供有关网站内容和请求的摘要。 我发现这对于了解网页上提供的内容有很大帮助。

clipboard.png

5. Sitespeed.io

Sitespeed.io 是一款开源的Web性能测试工具,用来衡量Web网站的综合性能,帮助开发和测试人员分析网页的加载速度和渲染性能。

Sitespeed.io通过驱动浏览器(如:Chrome、Firefox)进行测试,然后从开发者的站点收集多个页面的数据,并根据最佳实践等规则来分析这些网页,然后将结果以HTML报告的形式输出。

Sitespeed.io 满足了一个完整的Web性能测试工具所需的3个关键功能:

  1. 使用真实的浏览器测试Web站点,模拟真实的用户进行请求连接,收集以用户为中心的重要指标,如:响应速度指标、第一视觉呈现
  2. 可分析页面的组成,并给出相应性能反馈,增加终端用户的使用体验友好性
  3. 通过收集和保存页面组成的数据,便于跟踪定位

clipboard.png

6. Calibre

Caliber 是一款多功能的性能监控套件,可帮助你监控和审核网站的性能。 它还允许你通过指定测试服务器的位置,管理模拟的广告首选项甚至模拟移动设备来模拟现实条件。 它还允许你设置预算,并通过为你提供性能下降来帮助你将预算保持在预算之内。

clipboard.png

7. SpeedCurve

SpeedCurve 既可以让你追踪竞争对手的性能表现,也可以追踪自己的性能表现。

使用 SpeedCurve 时,你可以查看某个因素在不同站点的速度表现。对于移动用户来说,他们希望网站在手机上加载起来要快于电脑,如果感到加载迟缓,往往会迅速关上网页,所以,网站的响应速度对他们很重要。

SpeedCurve 还提供了综合监控。综合监控是在受控环境中模拟你的网站。你可以自定义选项,比如网络速度、设备、操作系统等等。

clipboard.png

大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】

8. SpeedTracker

SpeedTracker是一个运行在WebPageTest之上的工具,可在你的网站上进行定期的性能测试,并直观显示各种性能指标随时间变化的方式。 这使你可以不断评估网站,并查看新功能如何影响网站的性能。 你还可以定义预算并通过电子邮件和Slack获取警报。

clipboard.png


代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

原文:https://blog.bitsrc.io/perfor...

交流

文章每周持续更新,可以微信搜索「 大迁世界 」第一时间阅读和催更(比博客早一到两篇哟),本文 GitHub https://github.com/qq449245884/xiaozhi 已经收录,整理了很多我的文档,欢迎Star和完善,大家面试可以参照考点复习,另外关注公众号,后台回复福利,即可看到福利,你懂的。

查看原文

赞 26 收藏 20 评论 0

前端小智 发布了文章 · 2月18日

12 个炫酷背景特效库

作者:lindelof
译者:前端小智
来源:github
点赞再看,微信搜索大迁世界,B站关注前端小智这个没有大厂背景,但有着一股向上积极心态人。本文 GitHubhttps://github.com/qq44924588... 上已经收录,文章的已分类,也整理了很多我的文档,和教程资料。

最近开源了一个 Vue 组件,还不够完善,欢迎大家来一起完善它,也希望大家能给个 star 支持一下,谢谢各位了。

github 地址:https://github.com/qq44924588...

1.particles-bg

地址:https://github.com/lindelof/particles-bg

效果:

clipboard.png

clipboard.png

clipboard.png

2.particles-bg-vue

地址:https://github.com/lindelof/awesome-web-effect

这是一个基于VUE的粒子动画组件。

clipboard.png

clipboard.png

3.jquery.ripples

地址:https://github.com/sirxemic/jquery.ripples

jQuery Ripples 插件向HTML添加一层水元素将波纹光标与WebGL的互动。您可以使用这种效果,让你的静态CSS背景图像更多的互动。

clipboard.png

4.MorphingBackgroundShapes

地址:https://github.com/codrops/MorphingBackgroundShapes

这是一个很具装饰性的网站背景效果,当用户在滚动到某一页面后此背景的SVG图形将随着变形和移动。

5. SegmentEffect

地址:https://github.com/codrops/SegmentEffect

背景分割装饰特效。

6.jQuery.BgSwitcher

地址:https://github.com/rewish/jquery-bgswitcher

jQuery.BgSwitcher实现背景图像切换效果。

7.BackgroundScaleHoverEffect

地址:https://github.com/codrops/BackgroundScaleHoverEffect

使用 CSSclip paths 重现背景缩放悬停特效。

https://klxxcdn.oss-cn-hangzhou.aliyuncs.com/histudy/hrm/media/66/7116.gif

8.ImageGridMotionEffect

地址:https://github.com/codrops/ImageGridMotionEffect

为背景网格的图像提供运动悬停特效。

9.jquery.adaptive-backgrounds.js

地址:https://github.com/briangonzalez/jquery.adaptive-backgrounds.js

adaptive-background.js是一款jQuery插件,可以根据div,img标签里图片的边框颜色来动态调整父标签的背景颜色,有点类似iTunes的专辑详情的效果.

clipboard.png

10.fixed-background-effect

地址:https://codyhouse.co/demo/fixed-background-effect/index.html#0

整屏滚动背景悬浮效果。

11.jquery-warpdrive-plugin

地址:https://github.com/NiklasKnaack/jquery-warpdrive-plugin

query-warpdrive-plugin是一款可以制作基于HTML5 canvas的炫酷星空背景特效的jquery插件。这个星空背景特效可通过配置参数进行灵活的配置,可用鼠标进行互动。

大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】

12.RainEffect

使用WebGL在不同场景下的一些实验性降雨和水滴效应。

人才们的 【三连】 就是小智不断分享的最大动力,如果本篇博客有任何错误和建议,欢迎人才们留言,最后,谢谢大家的观看。


代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

原文:https://github.com/lindelof/a...


交流

文章每周持续更新,可以微信搜索「 大迁世界 」第一时间阅读和催更(比博客早一到两篇哟),本文 GitHub https://github.com/qq449245884/xiaozhi 已经收录,整理了很多我的文档,欢迎Star和完善,大家面试可以参照考点复习,另外关注公众号,后台回复福利,即可看到福利,你懂的。

查看原文

赞 37 收藏 31 评论 0

前端小智 发布了文章 · 2月7日

使用 CSS Viewport 完成响应式布局

作者:Ahmad shaded
译者:前端小智
来源:sitepoint
点赞再看,微信搜索大迁世界,B站关注前端小智这个没有大厂背景,但有着一股向上积极心态人。本文 GitHubhttps://github.com/qq44924588... 上已经收录,文章的已分类,也整理了很多我的文档,和教程资料。

最近开源了一个 Vue 组件,还不够完善,欢迎大家来一起完善它,也希望大家能给个 star 支持一下,谢谢各位了。

github 地址:https://github.com/qq44924588...

CSS Viewport units(视口单位)在过去几年已经出现了,随着时间的推移,越来越多的开发人员开始使用它们。它们的好处在于为我们提供了一种不需要使用J avaScript 就能以动态的方式调整大小的方法。而且,如果它失效,也有很多备用方案。

在本文中,我们将学习 CSS Viewport units(视口单位)以及如何使用它们,并用列举一些常见问题及其解决方案和用例,让我们开始吧。

简介

根据CSS规范,视口百分比单位相对于初始包含块的大小,它是web页面的根元素。

视口单位为:vwvhvminvmax

vw单位表示根元素宽度的百分比。1vw等于视口宽度的1%

视口宽度

vw单位表示根元素宽度的百分比,1vw等于视口宽度的1%

clipboard.png

假设我们有一个元素与以下CSS:

.element {
    width: 50vw;
}

当视口宽度为500px时,50vw计算如下

width = 500*50% = 250px

视口高度

vh单位表示根元素高度的百分比,一个vh等于视口高度的1%

clipboard.png

我们有一个元素与以下CSS:

.element {
    height: 50vh;
}

当视口高度为290px时,70vh计算如下:

height = 290*70% = 202px

大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】

Vmin 单位

vmin表示视口的宽度和高度中的较小值,也就是vwvh 中的较小值。如果视口宽度大于其高度,则将根据高度计算该值。

我们以下面的例子为例。

clipboard.png

我们有一个横屏手机,其中有一个元素具有vmin单元。在这种情况下,值将根据视口高度计算,因为它小于宽度。

.element {
    padding-top: 10vmin;
    padding-bottom: 10vmin;
}

这是vmin的计算方式:

clipboard.png

正如你所猜测的,计算结果如下所示

padding-top = (10% of height) = 10% * 164 = 16.4px 

padding-bottom = (10% of height) = 10% * 164 = 16.4px

Vmax 单位

vmaxvmin相反,该值是vwvh 中的较大值。

clipboard.png

我们以下面的例子为例。

.element {
    padding-top: 10vmax;
    padding-bottom: 10vmax;
}

clipboard.png

计算结果如下:

padding-top = (10% of width) = 10% * 350 = 35px 
padding-bottom = (10% of width) = 10% * 350 = 35px

视口单位与百分比有何不同?

视口单位基于页面的根元素,而百分比则基于它们所在的容器。因此,它们彼此不同,但各自都有各自的用处。

视口单位的用例

字体大小

clipboard.png

CSS 视口单位非常适合响应式排版。 例如,我们可以将以下内容用作文章标题:

.title {
    font-size: 5vw;
}

标题的font-size将根据视口宽度增加或缩小。 就像提供的字体大小是视口宽度的5%一样。 但是,如果没有适当的测试就直接使用它可能会踩到坑。 让我们看下面的视频:

图片描述

体大小变得非常小,这不利于可访问性和用户体验。据我所知,移动设备上的最小字体大小不应该不于14px。在GIF中,不小于10px

要解决该问题,我们需要为标题提供最小字体大小,可以使用 calc()

.title {
    font-size: calc(14px + 2vw);
}

calc()CSS函数将具有一个最小值14px,并在些基础上添加2vw的值,有了这些,字体大小值就不会变得太小。

另一个需要考虑的重要问题是字体大小在大屏幕上的表现,例如 27” iMac。会发生什么呢?你猜对了,字体大小为95px左右,这是一个很大的值。为了防止这种情况,我们应该在某些断点上使用媒体查询并更改字体大小。

@media (min-width: 1800px) {
    .title {
        font-size: 40px;
    }
}

通过重置字体大小,我们可以确保大小不会太大。您可能还需要添加多个媒体查询,但这取决于你自己,也取决于项目的上下文。

事例地址:https://codepen.io/shadeed/pe...

全屏

有时,我们需要一个section来获取100%的视口高度,为此,我们可以使用viewport高度单位。

clipboard.png

.section {
    height: 100vh;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
}

通过添加 height: 100vh,我们可以确保section 高度将采取100%的视口。此外,我们添加了一些flexbox来处理水平和垂直居中的内容。

事例源码:https://codepen.io/shadeed/pe...

粘性布局(footer)

在大屏幕上,网站内容有时候很少,footer 没有粘在底部。这很正常,也不被认为是一种不好的做法。但是,还有改进的余地。考虑下面代表问题的示图:

clipboard.png

为了解决这个问题,我们需要给内容(content)一个高度,它等于视口高度- (header + footer)。动态地做到这一点是很困难的,但是使用CSS的视口,这是很容易的。

大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】

第一种解决方案:calc和视口单位

如果headerfooter 的高度是固定的,那么可以将calc()函数和视口单位结合起来,如下所示:

header,
footer {
    height: 70px;
}

main {
    height: calc(100vh - 70px - 70px);
}

不能保证此解决方案始终有效,尤其是对于footer而言。 在我的职业生涯中,我没有使用固定高度的页脚,因为在例如不同的屏幕尺寸下,此footer是不可行的。

2.第二种解决方案:Flexbox和视口单位(推荐

通过将100vh设置为body元素的高度,然后可以使用flexbox来使main元素占用剩余空间。

body {
    min-height: 100vh;
    display: flex
    flex-direction: column;
{

main {
    /* This will make the main element take the remaining space dynamically */
    flex-grow: 1;
}

这样,问题就解决了,无论内容长度如何,我们都有一个粘性footer

clipboard.png

事例源码:https://codepen.io/shadeed/pe...

响应式元素

假设我们有一个作品集来展示我们的响应式设计工作,并且我们有三种设备(移动设备、平板电脑和笔记本电脑)。每个设备包含一个图像。目标使用 CSS 来响应适配这些页面。

clipboard.png

通过使用CSS网格和视口单位,我们可以使其完全动态的响应式。

<div class="wrapper">
  <div class="device laptop"></div>
  <div class="device mobile"></div>
  <div class="device tablet"></div>
</div>

视口单位也可以用于grid- *属性,也用于borderborder-radius和其他属性。

.wrapper {
  display: grid;
  grid-template-columns: repeat(20, 5vw); 
  grid-auto-rows: 6vw;
}

.mobile { 
  position: relative;
  z-index: 1;
  grid-column: 2 / span 3;
  grid-row: 3 / span 5;
}
 
.tablet {
  position: relative;
  z-index: 1;
  grid-column: 13 / span 7; 
  grid-row: 4 / span 4;
  border-bottom: 1vw solid #a9B9dd;
  border-right: solid 3vw #a9B9dd;
}
 
.laptop {
  position: relative;
  grid-column: 3/span 13;
  grid-row: 2 / span 8;
}

/* Viewport units are used for the bottom, left, right, height, and border-radius. Isn't that cool? */
.laptop:after {
    content:"";
    position:absolute;
    bottom: -3vw;
    left: -5.5vw;
    right: -5.5vw;
    height: 3vw;
    background-color: #a9B9dd;
    border-radius: 0 0 1vw 1vw;
}

!
clipboard.png
例:https://codepen.io/shadeed/pe...

从容器中挣脱出来

我注意到一个用例最适合编辑版面。 一个子元素,即使父元素的宽度受到限制,它也会占据视口100%的宽度。 考虑下面:

clipboard.png

.break-out {
    width: 100vw;
    position: relative;
    left: 50%;
    right: 50%;
    margin-left: -50vw;
    margin-right: -50vw;
}

让我们把它分解一下,这样我们就能一点一点地理解所有这些属性是如何工作的。

1.添加 width: 100vw

最重要的一步,将图像的宽度设置为100%的视口。

clipboard.png

2.添加 margin-left: -50vw

为了使图像居中,我们需要给它一个负的边距,其宽度为视口宽度的一半。

clipboard.png

3.添加 left: 50%

最后,我们需要将图像向右推,其值为父宽度的50%

clipboard.png

事例地址:https://codepen.io/shadeed/pe...

垂直和水平间距

我想到的另一个有趣的用例是使用视口单位来表示元素之间的间距。这可以与margintopbottomgrid-gap等值一起使用。使用时,间距将基于视口宽度或高度,这对于使布局更具动态性可能很有用。

模态框

对于模态,我们需要将它们从视口顶部推入。 通常,使用top属性进行此操作,并使用百分比或像素值。 但是,对于视口单位,我们可以添加一个可以根据视口高度改变的间距。

clipboard.png

.modal-body {
    top: 20vh;
}

图片描述

事例地址:https://codepen.io/shadeed/pe...

大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】

页面头部

页面header 是充当页面介绍的部分。 它通常具有标题和描述,并且其中上下边缘的高度固定或填充

例如,有以下的CSS的样式:

.page-header {
    padding-top: 10vh;
    padding-bottom: 10vh;
}

.page-header h2 {
    margin-bottom: 1.5vh;
}

使用vh单位用于页面标题的 paddding,以及标题下方的边距。 注意间距如何变化!

图片描述

事例源码:https://codepen.io/shadeed/pe...

大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】

Vmin 和 Vmax 用例

该用例是关于页面标题元素的顶部和底部padding 。 当视口较小(移动)时,通常会减少padding 。 通过使用vmin,我们可以在视口较小尺寸(宽度或高度)的基础上获得合适的顶部和底部 padding

.page-header {
    padding: 10vmin 1rem;
}

图片描述

事例源码:https://codepen.io/shadeed/pe...

纵横比

我们可以使用vw单位创建响应元素,以保持其纵横比,而不管视口大小如何。

首先,需要先确定所需的纵横比,对于此示例,使用9/16

section {
    /* 9/16 * 100 */
    height: 56.25vw;
}

图片描述

事例源码:https://codepen.io/shadeed/pe...

流行的顶部边框

你知道大多数网站使用的顶部边框吗? 通常,它的颜色与品牌颜色相同,这会赋予一些个性。

clipboard.png

我们支持边框的初始值为3px。 如何将固定值转换为视口对象?下面是如何计算它的等效的vw

vw = (Pixel Value / Viewport width) * 100

视口宽度用于估计像素值与所需vw单位之间的比率。

对于我们的示例,我们为 header 添加以下样式:

.header {
    border-top: 4px solid #8f7ebc;  
}

在我的情况下,视口宽度为1440(这不是固定数字,请用你自己的数字替换)

vw = (4 / 1440) * 100 = 0.277
.header {
    border-top: 0.277vw solid #8f7ebc;  
}

更好的是,我们可以使用一个基本像素值,而视口单元可以是一个附加的。

.header {
    border-top: calc(2px + 0.138vw) solid $color-main;
}

移动端滚动问题

移动设备中存在一个常见问题,即使使用100vh,也会滚动,原因是地址栏的高度可见。Louis Hoebregts 写了一篇关于这个问题的文章,并给出了一个简单的解决方案。

.my-element {
  height: 100vh; /* 不支持自定义属性的浏览器的回退 */
  height: calc(var(--vh, 1vh) * 100);
}
// 首先,我们得到视口高度,我们乘以 1% 得到一个vh单位的值
let vh = window.innerHeight * 0.01;
// 然后,将`--vh`自定义属性中的值设置为文档的根目录一个属性
document.documentElement.style.setProperty('--vh', `${vh}px`);

clipboard.png

事例源码:https://codepen.io/shadeed/pe...


代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

原文:https://ishadeed.com/viewport...


交流

文章每周持续更新,可以微信搜索「 大迁世界 」第一时间阅读和催更(比博客早一到两篇哟),本文 GitHub https://github.com/qq449245884/xiaozhi 已经收录,整理了很多我的文档,欢迎Star和完善,大家面试可以参照考点复习,另外关注公众号,后台回复福利,即可看到福利,你懂的。

查看原文

赞 16 收藏 11 评论 0