js入门学习

js入门学习

总教程:JavaScript | MDN (mozilla.org)

简单记一些知识点就行了。

入门

1、JavaScript 开始运行之前,HTML和CSS已经构建好了

2、每个标签页都用来运行代码的独立容器,做到浏览器安全

3、运行次序是从上到下运行的

4、JavaScript 是轻量级解释型语言,js转换器采用了即时编译(just-in-time compiling)的技术,当代码执行的时候会转换成二进制文件,但即便如此,还是解释型的语言。

5、JavaScript 是区分大小写的,而且非常精确

6、脚本阻塞的解决的方法:(旧方法是把脚本元素放在文档体的底端(</body> 标签之前,与之相邻),但有大量的js脚本,会带来显著的性能消耗),现代的解决方法是async和defer

async加载方式如下,如果脚本无需等待页面解析,且无依赖独立运行,那么应使用 async

<script async src="js/vendor/jquery.js"></script>

如果脚本需要等待页面解析,且依赖于其它脚本,调用这些脚本时应使用 defer,将关联的脚本按所需顺序置于 HTML 中。

<script defer src="js/vendor/jquery.js"></script>

7、==只测试值,不测试数据类型,===测试值和数据类型

8、事实上, 许多你调用(运行或者执行的专业词语)浏览器内置函数时调用的代码并不是使用JavaScript来编写——大多数调用浏览器后台的函数的代码,是使用像C++这样更低级的系统语言编写的,而不是像JavaScript这样的web编程语言。

9、在现代浏览器中,默认所有事件处理程序都在冒泡阶段进行注册(冒泡就是从内向外,捕捉是从外向内),可以用stopPropagation()来阻止冒泡

10、事件委托,就是子节点的事情冒泡到父节点上,把事件监听器设置在父节点上进行监听。

11、函数始终有一个默认的protorype属性,所有有函数的原型属性的 __proto__ 就是 window.Object.prototype.

12、必须重申,原型链中的方法和属性没有被复制到其他对象——它们被访问需要通过前面所说的“原型

13、不在prototype里面的不能被继承,只能被本身的构造器自身使用,在prototype能被继承下来。

14、Object.create(),实际做的是从指定原型对象创建一个新的对象,。这里以 person1 为原型对象创建了 person2 对象,person2.__proto__返回的对象是person1

var person2 = Object.create(person1);

15、事实上,一种极其常见的对象定义模式是,在构造器(函数体)中定义属性、在 prototype 属性上定义方法。如此,构造器只包含属性定义,而方法则分装在不同的代码块,代码更具可读性。例如:

// 构造器及其属性定义
function Test(a,b,c,d) {
  // 属性定义
};
// 定义第一个方法
Test.prototype.x = function () { ... }
// 定义第二个方法
Test.prototype.y = function () { ... }
// 等等……

16、Promise对比callbacks的优势:可以用多个then()操作将多个异步操作链接在一起,防止回调地狱。总是严格按照它们放置在事件队列中的顺序调用。错误处理要好得多,都由末尾的catch()块处理。

17、递归setTimeout()和setInterval()的区别:

  • 递归 setTimeout() 保证执行之间的延迟相同,例如在上述情况下为100ms。 代码将运行,然后在它再次运行之前等待100ms,因此无论代码运行多长时间,间隔都是相同的。
  • 使用 setInterval() 的示例有些不同。 我们选择的间隔包括执行我们想要运行的代码所花费的时间。假设代码需要40毫秒才能运行 – 然后间隔最终只有60毫秒。
  • 当递归使用 setTimeout() 时,每次迭代都可以在运行下一次迭代之前计算不同的延迟。 换句话说,第二个参数的值可以指定在再次运行代码之前等待的不同时间(以毫秒为单位)。

相当于一个是才末尾结束了才开始计时,一个是开始运行就开始计时了。

18、async/await虽好,可别贪杯,因为它会阻塞await后面的句子,从而变慢。一个缓解的小技巧就是存储Promise对象,然后await Promise对象,这样子多个Promise都会同时进行。

参考这个🌰:https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Asynchronous/Async_await

异步不同的方法之间的比较:https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Asynchronous/Choosing_the_right_approach

19、Document.querySelector()是推荐的主流方法,会匹配第一个元素,如果想要所有的元素,使用这个Document.querySelectorAll(),其他的是比较old的方法,还是不推荐使用

20、cookies已经过时了,存在各种安全问题、无法存储更复杂的数据,应该使用web storage和indexedDB。为什么仍然有新创建的站点使用 cookies?这主要是因为开发人员的习惯,使用了仍然使用cookies的旧库,以及存在许多web站点,提供了过时的参考和培训材料来学习如何存储数据。(有趣)

21、一元运算符 + 也可以把数字字符串转换成数值,parseInt() 函数会尝试逐个解析字符串中的字符,直到遇上一个无法被解析成数字的字符,然后返回该字符所有数字字符组成的数字。但是运算符 “+”对字符串的转换方式与之不同, 只要字符串含有无法被解析成数字的字符,该字符串就将被转换成 NaN

中级

数据类型

1、八种数据类型,包括七种原始类型:undefined、Boolean、Number、String、BigIntSymbol、null,还有Object类型,typeof就是检查这八种类型,也只能检查这八种。检查Object种类是使用 instanceof

2、Number类型为双精度 64 位二进制格式的值(-(2^53 -1) 到 2^53 -1)。BigInt可以超过Number.MAX_SAFE_INTEGER,BigInt 是通过在整数末尾附加 n 或调用构造函数来创建的。BigInt不能和数字互换操作,否则会抛出TypeError。

3、符号类型Symbols,可以理解为C语言里面的枚举类型。自己看了关于Symbol的用处,说实话更多的是为了解决开发过程中所产生的一些问题。用Symbol创建的,是具有唯一性的,可以作为对象的键并且不会重复,并且不会通过遍历的方式或者Object.keys这种方式暴露出来。而且可读性比较好。

4、对象有两种属性,分别是数据属性和访问器属性。数据属性有四个,Value、Writeable、Enumerable和Configurable。访问器属性有四个:Get、Set、Enumerable和Configurable。

5、Map 对象可以枚举,赋值和搜索都需要O(n)的操作,由于一直被引用,垃圾收集器不能很好优化。

WeakMaps 的 key 只能是 Object 类型,键是不可以被枚举的,是弱引用的,允许垃圾收集器优化回收。

高级

原型

1、函数创建的实例对象,会有一个私有属性_proto__指向它构造函数的原型对象prototype。这句话的理解就是,实例对象能够通过__proto__这个属性找他构造函数的原型对象prototype,原型链会根据__proto__这个属性依次往上找。但是构造函数呢,是没有__proto__这个属性的,他只有prototype这个对象,所以这就解释了为什么下面那个doSomething.foo是找不到的,因为doSomething是构造函数,没有__proto__这个属性,这只有实例对象才有,所以他就没有,找不到,但是他在构造函数的prototype对象里面定义了,所以用doSomething.prototype.foo就能够找到。

简而言之, prototype 是用于类(函数?)的,而 Object.getPrototypeOf()/__proto__ 是用于实例的(instances),两者功能一致。

function doSomething(){}
doSomething.prototype.foo = "bar";
var doSomeInstancing = new doSomething();
doSomeInstancing.prop = "some value";
console.log("doSomeInstancing.prop:      " + doSomeInstancing.prop);
console.log("doSomeInstancing.foo:       " + doSomeInstancing.foo);
console.log("doSomething.prop:           " + doSomething.prop);
console.log("doSomething.foo:            " + doSomething.foo);
console.log("doSomething.prototype.prop: " + doSomething.prototype.prop);
console.log("doSomething.prototype.foo:  " + doSomething.prototype.foo);

doSomeInstancing.prop:      some value
doSomeInstancing.foo:       bar
doSomething.prop:           undefined
doSomething.foo:            undefined
doSomething.prototype.prop: undefined
doSomething.prototype.foo:  bar

2、函数的原型链:fun —> Function.prototype —> Object.prototype —> null。

普通的对象的原型链:obj —> Object.prototype —> null(注意,普通对象的原型直接抄底到Object的原型对象,无中间商赚差价)。

数组的原型链:arr —> Array.prototype —> Object.prototype —> null。

3、Object.create(),创建新的对象,然后会继承相关的属性和原型链,这里我试了一下,b.__proto__就是a,也就是a本身是个实例对象,拥有属性,但现在变成了b的原型对象了。

var a = {a: 1};
// a ---> Object.prototype ---> null
var b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (继承而来)
var c = Object.create(b);
// c ---> b ---> a ---> Object.prototype ---> null
var d = Object.create(null);
// d ---> null
console.log(d.hasOwnProperty); // undefined, 因为d没有继承Object.prototype

4、es6引入的class关键字,只是语法糖,本质上还是基于原型链的。

5、当然查找原型链有点区块链的味道了,性能比较拉,查找属性比较容易耗时,遍历对象的属性时,原型链上的每个可枚举的属性都会被枚举出来。查找是不是对象自己定义的属性,用从Object那继承的hasOwnProperty来判断。这个方法是处理属性并且不会遍历原型链的方法,另一个这样子特性的是Object.keys();

注意:检查属性是否为 undefined不能够检查其是否存在的。该属性可能已存在,但其值恰好被设置成了 undefined

6、不要去扩展内置的原生对象的原型,比如Object.prototype,这会引来新的问题。如果一定要去做,也只能是去扩展支持JS引擎的新特性,比如Array.forEach在某些内核版本不支持,那么你去扩展这个Array.prototype来获得未来的功能,这才是可以的,否则,千万别。

严格模式

1、脚本级别开启严格模式。只需要在源码的最上面加入"use strict"。缺点就是合并严格模式的脚本和非严格模式的脚本会带来问题。

2、函数级别开启严格模式。需要把"use strict"放在函数体内所有语句的前面。

类型化数组

最开始就是为了WebGL才弄出来的,架构分为两个,一个是缓冲(由ArrayBuffer对象实现),另一个是视图。

// 创建缓冲
var buffer = new ArrayBuffer(16);
// 大小是16个字节长度
console.log(buffer.byteLength) //16
// 创建Int32的视图,一个Int32占据4个字节,所以长度是16/4=4
var int32View = new Int32Array(buffer);
// 往里面填充数据,分别是0,2,4,8
for (var i = 0; i < int32View.length; i++) {
  int32View[i] = i * 2;
}
// 也可以对同一个缓冲使用多个视图,比如说用Int16对刚才的缓冲
var int16View = new Int16Array(buffer);
// 这次会打印0, 0, 2, 0, 4, 0, 6, 0
for (var i = 0; i < int16View.length; i++) {
  console.log("Entry " + i + ": " + int16View[i]);
}

可以使用Array.from转换成普通的数组

内存管理

自动回收的,但这不意味着不需要关心内存管理。

内存管理的生命周期都是一样的

  1. 分配你所需要的内存
  2. 使用分配到的内存(读、写)
  3. 不需要时将其释放\归还

垃圾回收,就是看有没有被对象引用,这个“对象”不仅只包括对象,还包括函数作用域(或者全局词法作用域)。

早期算法就是计算引用的次数,如果降为0,就自动回收。限制就是无法处理循环引用,容易造成内存泄露。

新的算是标记-清除法,这个算法把“对象是否不再需要”简化定义为“对象是否可以获得”。

这个算法假定设置一个叫做根(root)的对象(在Javascript里,根是全局对象)。垃圾回收器将定期从根开始,找所有从根开始引用的对象,然后找这些对象引用的对象……从根开始,垃圾回收器将找到所有可以获得的对象和收集所有不能获得的对象。

循环引用就不是问题了,一旦函数退出了,在全局对象中找不到了,无法获得,所以就会被自动回收。

限制就是那些无法从根对象查询到的对象都将被清除,但这个问题不大。

并发模型与事件循环

Stack, heap, queue

栈:函数调用形成了一个由若干帧组成的栈。

堆:对象被分配在堆中,堆是一个用来表示一大块(通常是非结构化的)内存区域的计算机术语。

队列:一个 JavaScript 运行时包含了一个待处理消息的消息队列。每一个消息都关联着一个用以处理这个消息的回调函数。

在事件循环中,按钮找队列处理消息,被处理的消息会被移除队列,并作为输入参数来调用与之关联的函数。函数的处理会一直进行到栈为空,然后事件循环处理队列中的下一个消息。

事件循环:伪代码如下,

waitForMessage()会同步地等待消息到达(如果当前没有任何消息等待被处理)。

while (queue.waitForMessage()) {
  queue.processNextMessage();
}

消息会被完整的执行,是同步的,不像C语言会被抢占,垃圾C。缺点就是消息执行过长,会未响应,所以尽量把大消息裁剪成小消息。

添加消息就是事件发生并且被事件监听器捕捉到,就会被添加到消息队列。 setTimeout的第二个参数表示添加到队列的最小延迟,如果队列为空并且栈为空,那么就会被立即执行,否则就慢慢等其他消息处理完。

一个web worker或者一个跨域的iframe都有自己的栈、堆和消息队列,两个只能通过postMessage方法进行通信

永不阻塞。

**所以我就有个疑问:**js不是单线程吗,怎么还能处理这种异步的东西,还有消息队列这种东西。查了一下,JS本身是单线程的,但是浏览器不是啊,他收到异步的请求,他去交给浏览器的其他线程去处理,处理完了再放回这个队列里面,然后执行回调函数。所以说他是永不阻塞的,因为异步的都被弄出去了,下面这张图就看出来了。估计是V8引擎干的,所以推测nodejs也是一样的。

img

一句话总结:浏览器中只有一个JS执行线程,但异步机制是浏览器两个或者以上的线程共同完成的。

其他

1、为便于记忆,你只需要记住,全局作用域中的 this 就是 globalThis

2、instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。

用法:object instanceof constructor

3、new 关键字会进行如下的操作:

  1. 创建一个空的简单JavaScript对象(即{});
  2. 为步骤1新创建的对象添加属性__proto__,将该属性链接至构造函数的原型对象 ;
  3. 将步骤1新创建的对象作为this的上下文 ;
  4. 如果该函数没有返回对象,则返回this

4、算术运算符基本上都是左结合的,只有幂运算是右结合的(++、–、一元+、逻辑非这些不是算术运算符,这些是逻辑运算符吧,从右到左的右结合)

5、空值合并??,这是是一个逻辑操作符,当左侧的操作数为 null 或者 undefined 时,返回其右侧操作数,否则返回左侧操作数,用处就是在假性false的情况下,比如(0''NaNnullundefined)这些情况,用??更合适。不过这算是比较新的特性了,到chrome80和nodejs14才支持。

类似功能的还有可选链操作符(?.),可选链操作符不能够赋值,这还真的有点可惜。

可以结合两个,如下面的例子所示。

let customer = {
  name: "Carl",
  details: { age: 82 }
};
let customerCity = customer?.city ?? "暗之城";
console.log(customerCity); // “暗之城”

6、apply和call都差不多,区别就是call()方法接受的是参数列表,而apply()方法接受的是一个参数数组

// apply的语法
func.apply(thisArg, [argsArray])

thisArg
必选的。在 func 函数运行时使用的 this 值。请注意,this可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动替换为指向全局对象,原始值会被包装。

示例:比如把一个数组的元素添加到现有的数组(不是concat,因为他是创建返回一个新的数组)

var array = ['a', 'b'];
var elements = [0, 1, 2];
array.push.apply(array, elements);
console.info(array); // ["a", "b", 0, 1, 2]

或者循环遍历数组的情况

/* 使用Math.min/Math.max以及apply 函数时的代码 */
var max = Math.max.apply(null, numbers); /* 基本等同于 Math.max(numbers[0], ...) 或 Math.max(5, 6, ..) */

7、剩余参数语法允许我们将一个不定数量的参数表示为一个数组。和arguments的区别就是:

  • 剩余参数只包括那些没有形参的实参,arguments是都有
  • arguments不是一个真正的数组,而剩余参数是一个真正的Array,数组方法都能用
function(a, b, ...theArgs) {
  // ...
}

8、展开语法,光从语法上来看,和剩余参数一样,都是用...,但是两者用处完全是相反的。

在函数调用的时候等价于apply

myFunction(...iterableObj);

提示: 实际上, 展开语法和 Object.assign() 行为一致, 执行的都是浅拷贝(只遍历一层)。就像下面一样。

var a = [[1], [2], [3]];
var b = [...a];
b.shift().shift(); // 1
console.log(a) //[[],[2],[3]]

浅拷贝或者浅拷贝的合并可以使用展开语法

var obj1 = { foo: 'bar', x: 42 };
var obj2 = { foo: 'baz', y: 13 };
var clonedObj = { ...obj1 };
// 克隆后的对象: { foo: "bar", x: 42 }
var mergedObj = { ...obj1, ...obj2 };
// 合并后的对象: { foo: "baz", x: 42, y: 13 }

9、箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this,由于箭头函数没有自己的this指针,通过 call()apply() 方法调用一个函数时,只能传递参数(不能绑定this),他们的第一个参数会被忽略。

箭头函数不绑定arguments,只是引用了封闭作用域内的arguments,使用剩余参数是更好的选择。

箭头函数没有prototype

10、解构赋值,通过解构赋值, 可以将属性/值从对象/数组中取出,赋值给其他变量。反正蛮好用的。

解构数组

var a, b, rest;
[a, b] = [10, 20]; 

[a, b, ...rest] = [10, 20, 30, 40, 50];
console.log(a); // 10
console.log(b); // 20
console.log(rest); // [30, 40, 50]

//默认值
[a=5, b=7] = [1];
console.log(a); // 1
console.log(b); // 7

// 交换值
[a, b] = [b, a];//这个经常用,刷题看到的

// 忽略某些值
function f() {
  return [1, 2, 3];
}
var [a, , b] = f();
console.log(a); // 1
console.log(b); // 3

解构对象

({ a, b } = { a: 10, b: 20 });
console.log(a); // 10
console.log(b); // 20

// Stage 4(已完成)提案中的特性
({a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40});
console.log(a); // 10
console.log(b); // 20
console.log(rest); // {c: 30, d: 40}

// 可以从一个对象中提取变量并赋值给和对象属性名不同的新的变量名。这个写起来有点怪怪的,倒也是一一对应
var o = {p: 42, q: true};
var {p: foo, q: bar} = o;
console.log(foo); // 42
console.log(bar); // true 
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇