连续赋值(引用和操作符优先级)

知乎答案 : https://www.zhihu.com/question/41220520

  1. var a = {n: 1};
  2. var b = a;
  3. a.x = a = {n: 2};
  4. console.log(a.x);//undefined
  5. console.log(b.x);//{n: 2}

代码分析

  1. var b = a
  2. 创建一个变量b,为其赋值对象a。指向同一个堆
  3. a.x = a = {n:2}

首先 先获取等号左侧的a.x,但是a.x并不存在,于是js为(堆内存中)对象创建一个新成员,这个成员的初始值为undefined

为什么直接引用一个未定义的变量会报错,但是直接引用一个对象的不存在的成员时,会返回undefined

然后开始执行等号右边的表达式 a={n:2}
这时候a的新值等于了{n:2} ,注意这时候a的指针指向一个新的堆内存
然后将这个新的对象a的堆内存指针赋值给刚刚的 x ,对象成员x便等于新的对象a

补充 : 优先级: .的优先级高于 = ,所以先执行a.x .

图文说明:
image.png


引用回答
这个需要结合JS引擎对堆内存和栈内存的管理来理解,即可一目了然。

  1. var a = {n: 1};

/这一行都明白,声明一个变量a,并为其赋值一个对象。/

  1. var b = a;

/ 这一行也好理解, 创建一个变量b,为其赋值对象a。在栈内存中,a与b是不同的,是两个变量,但是他们的指针是相同的,指向同一个堆。/

  1. a.x = a = {n: 2};

/这一行比较复杂。先获取等号左侧的a.x,但a.x并不存在,于是JS为(堆内存中的)对象创建一个新成员x,这个成员的初始值为undefined,
(这也是为什么直接引用一个未定义的变量会报错,但是直接引用一个对象的不存在的成员时,会返回undefined.)
创建完成后,目标指针已经指向了这个新成员x,并会先挂起,单等等号右侧的内容有结果了,便完成赋值。
接着执行赋值语句的右侧,发现a={n:2}是个简单的赋值操作,于是a的新值等于了{n:2}。
这里特别注意,这个a已经不是开头的那个a,而是一个全新的a,这个新a指针已经不是指向原来的值的那个堆内存,而是分配了一个新的堆内存。但是原来旧的堆内存因为还有b在占用,所以并未被回收。
然后,将这个新的对象a的堆内存指针,赋值给了刚才挂起的新成员x,此时,对象成员x便等于了新的对象a。
所以,现在b={n:1,x:{n:2}};a={n:2};a===b.x(true,注意对象的相等,不是值的相等,而是引用的相等,也就是说,相等表示指针是指向同一个堆内存。)
/

  1. console.log(a.x);
  2. console.log(b);

/最后这就好理解了,a.x当然没有了,但是因为对象a存在,所以JS不会报错,a.x等于undefined/

总结一下:
关键点一:a.x即完成了x的声明,其值为undefined。
关键点二:对象成员等待赋值时,锁定的赋值目标是成员,而非对象。
关键点三:对象重新赋值时,并非是修改原堆内存的值,而是重新分配堆内存,栈内存中的指针会做相应修改。(如果原堆内存有多个栈内存指向它,由于引用还存在,原堆内存的数据不会消失。如果堆内存再无其它引用,则会被JS的垃圾回收机制回收。对象的成员对象也一样。PS:引用类型应该都如此)

好多资料里为了表示方便,把对象的子集都画在一起,其实他们在内存中的物理位置不一定是连续的,要记住引用类型的特点:栈+堆。


every 用法

every 检测数组所有元素是否都符合判断条件

注意:函数的返回值需要自己 return

知识点 :

  1. // 当箭头函数的函数体只有一个 `return` 语句时,可以省略 `return` 关键字和方法体的花括号
  1. 错误写法
  2. const array1 = [2,34,5,6]
  3. const res = array1.every((x)=>{
  4. x > 1 //注意 这里并没有return
  5. })
  6. console.log(res)
  7. 箭头函数写法
  8. const array1 = [2,34,5,6]
  9. const res = array1.every(x=>x>1)
  10. console.log(res)

不能在if里面声明函数

image.png
https://blog.csdn.net/weixin_30929011/article/details/99506619



var self = this 的原因

疑问解答:this使用 var self = this 的原因

疑问整理 - 图3

  1. function foo(){
  2. var self = this
  3. setTimeout(function(){
  4. console.log(self.a)
  5. },100)
  6. }
  7. var obj = {
  8. a:2
  9. }
  10. foo.call(obj) //2

首先看一个例子
在一个对象里面定义了一个普通函数(不是该对象的属性函数),为了能够在该普通函数里面访问到对象的属性,可以先把对象的this赋值给一个变量self,然后在该普通函数里通过self来获取到对象的属性。

这里理解: 函数也是一个对象,写在里面的b函数不是这个对象的属性函数,所以他需要访问a函数里面的作用域,这时候就需要将a的作用域保存然后给他,因为此时的this指向是不一样的。
写这个var self = this 就是为了在其他函数中访问到对象属性,

  1. function a(){
  2. var self = this;
  3. this.name = "hello"
  4. function b(){ //b只是一个普通函数,不是a的属性方法
  5. console.log(self.name)
  6. }
  7. b()
  8. }
  9. new a() //hello

这里模拟一下 setTimeout 实现的伪代码

  1. function setTimeout(fn,delay){
  2. //等待 delay 毫秒
  3. fn() // 调用位置
  4. }

所以上面的代码就可以看成是对象中含有一个普通函数,不是foo的一个属性方法。

for 循环是怎么执行的

for定义

  1. for(begin;condition;step){
  2. //...循环体
  3. }
  1. for(let i = 0;i<3;i++){
  2. console.log(i)
  3. }

分析 for循环

begin i=0 进入循环时执行一次
condition i<3 在每次循环迭代之前检查,如果为false,停止循环
body(循环体) console.log(i) 条件为真时,重复运行
step i++ 在每次循环迭代后执行

所以 begin 只执行一次,然后进行迭代,每个检查条件之后,在执行body和step

  1. // for (let i = 0; i < 3; i++) alert(i)
  2. // 开始
  3. let i = 0
  4. // 如果条件为真,运行下一步
  5. if (i < 3) { alert(i); i++ }
  6. // 如果条件为真,运行下一步
  7. if (i < 3) { alert(i); i++ }
  8. // 如果条件为真,运行下一步
  9. if (i < 3) { alert(i); i++ }
  10. // ……结束,因为现在 i == 3

image.png

闭包的应用

原文链接

第一版:

理解忽略的事情: 存储的内存地址不会执行,只有函数被调用时才会拿出来执行、
i 并没有被传进去,执行的时候 i 才被传进去

  1. for (var i = 0, arr = []; i <= 3; i++) {
  2. arr.push(function () {
  3. console.log(i)
  4. })
  5. }
  6. arr[0]() //4
  7. arr[1]() //4

代码执行分析过程

arr中每一项执行时,都会去上级作用域寻找i,而i在for循环执行结束后就已经变成了4,所以arr中执行的每一项结果都是一样。 函数在预解析阶段,都被当成字符串存入堆内存,在真正执行时,才会被拿出来啊执行,数组中存储的,其实只是指向这个堆内存的指针,i并没有传进去,执行的时候i才被传进去。

闭包问题过程.png

第二版:

理解:立即执行函数本身是一个函数 这之中又返回了一个函数,等于是开辟了两个堆空间 并且存在作用域的关系,所以就会去上一级作用域找i的值,可以找到

  1. for (var i = 0, arr = []; i <= 3; i++) {
  2. arr.push((function (i) {
  3. return function () {
  4. console.log(i)
  5. }
  6. })(i))
  7. }
  8. arr[0]() //0
  9. arr[1]() //1

分析过程

arr中每次添加新项目是都会使得自执行函数执行,并将i作为参量传入自执行函数,关键点 function(i){…}(i)中第一个i是参数的形参,是私有变量,与外面的i没有关系,被私有作用域保护起来了,第二个i才是函数中外面的i(也就是说第一个i只是一个迷惑人的量,你改成k也是一样的结果,只不过把i赋给k而已)
这样一来,每次触发自执行函数时,都相当于将当前循环的变量i存储起来了。当arr中每一项执行时,调用了自执行函数返回的一个新地址的函数,这个新地址的函数会去上级作用域找i,上级作用域是形成这个新地址时的自执行函数(意思就是0xA1这个地址是从0XAA函数第一次执行return得到的,0XAA第一次执行时的栈内存就是0XA1的上级作用域),上级作用域中的i(或者说是上级作用域中那个形参)就是他要找的i,故能达到你要的效果。这种保护私有变量的机制就是闭包。

疑问整理 - 图6

setTimeout问题

  1. for(var i =0;i<10;i++){
  2. setTimeout(function(){
  3. console.log(i)
  4. },i*1000)
  5. } //每隔一秒输出一个10 一共输出10次

疑问:为什么输出的结果是10 但是还是会按照一秒一次的顺序输出,,我以为i*1000中的i也是10才对、

解析:

问题原因:setTimeout() 里的回调函数共享了全局作用域,for循环执行完结果i是10,所以这个回调函数最终都用的是这个10。而延时的时间依次就是1000 2000 3000… 之后再将作业添加到作业队列中。
当全部加载到作业队列中,才开始执行代码 先执行第一个1秒的任务。依次…

js中所有异步的回调函数永远都在正常代码执行完之后才执行。setTimeout 延迟0秒也是如此。

解决问题:为每个回调构建单独的闭包作用域,通常用IIFE
每个j都是新的,这是个函数拥有各自独立的闭包环境。

  1. for (var i = 0; i < 10; i++) {
  2. (function (j) { //形参
  3. setTimeout(function () {
  4. console.log(j)
  5. }, i * 1000)
  6. })(i) //实参
  7. }

更优的解决方案 ES6 中的 let

var a = b = 2 执行

先 b = 2
再 var a = b

[]==![]

  1. [] == ![] // true

疑问整理 - 图7

![] => !Boolean([]) => !true =>false
[]== false => [] ==Number(false) =>[]==0
“” == 0 => Number(“”) == 0 =-> 0==0


拓展:

  1. [] == [] //false

说明:当两个值都是对象(引用值)时,比较的是两个引用值在内存中是否是同一个对象。

JS文件引入位置

作为最佳实践,我们会在关闭body标签前引入javascript代码,这样浏览器就会在加载脚本之前解析和显示html,有利于提升页面性能。

foEach等高级函数使用注意

问题 : 无法跳出当前循环
已经整理到 数组相关 文档中

if…if… 和 if…else if 的区别是什么

这种格式,程序会依次判断条件1和条件2是否成立并根据结果决定是否执行语句1和语句2,也就是说,第一个if块和第二个if块没有影响。除非在执行第一个if块的时候就return了。

  1. if (条件1)
  2. {
  3. //语句1
  4. }
  5. if (条件2)
  6. {
  7. //语句2
  8. }

if块和else if 块本质上是互斥的,也就是说,一旦语句1执行了,程序会跳过 else if 块,else if 块中的判断语句以及语句2一定会被跳过;同时语句2的执行也暗含了条件1判断失败和语句1没有执行;当然还有第3个情况,就是条件1和条件2都判断失败,语句1和语句2都没有得到执行。

  1. if (条件1)
  2. {
  3. //语句1
  4. }
  5. else if (条件2)
  6. {
  7. //语句2
  8. }

总结:if if 这种如果条件都满足的话,那么就都会执行。而 if else 第一个判断为false才会进行第二个判断。

!! 双重否定

使用这个技巧将结果强制为布尔值(true或false),场景:假如我有一个表达式,我希望结果是布尔值。

  1. // 例子
  2. function supports_canvas() {
  3. return !!document.createElement('canvas').getContext;
  4. }

上面例子,如果 getContext 给你一个假值,!!会使它返回布尔值 false

  1. // 这些都是
  2. false
  3. NaN
  4. undefined
  5. null
  6. ""(空字符串)
  7. 0

false || undefined vs undefined || false

  1. > false || undefined
  2. undefined
  3. > undefined || false
  4. false

回答一:
The logical OR operator isn’t commutative like +, *, etc. It returns the first expression which can be converted into true. (Source Mozilla Doc)

  1. In false || undefined, false can’t be converted to true by definition (since it’s the opposite), so it returns the second operand (undefined)
  2. In undefined || false, undefined is a value, but considered as false in Javascript, so the logical operator evaluate the second operand and returns false (because both operands are false).

回答二:
当在两个 falsy 值之间运行 逻辑|| 运算时,在js中,它总是返回 or 运算符右侧的值。原因是 || 运算符如果可以强制为真,通常返回左值。但是如果不能将运算符左侧的值强制为true,则无论右侧的值是什么,都始终返回正右侧的值。

  1. false.
  2. 0 (zero)
  3. '' or "" (an empty string)
  4. null
  5. undefined
  6. NaN