你不知道的JavaScript(上卷)- - 书本知识点记录

JavaScript你可能不知道的事(小黄书) - 上册

小黄书是公司带我的姐姐推荐给给我看的,她跟我说JS好才是王道,有空把三本都看了,于是我在摸鱼的时候看完了小黄书上册…

小黄书涉及到的知识点还是非常的精细的,这里记录的是对于我来说让我眼前一亮的知识点,换句话就是我之前都不是很理解或者说是没有涉猎到的知识点。也不是代表除了这些别的我都懂了。总之读书是真的很重要。以后会一直记录下去。

函数作用域
function test(a) {
  b = a * 5;  // 报错 b没有被定义
  let b;
  console.log(b);
}

------------------------------------------------------------------------

function test(a) {
  b = a * 5;  
  var b; // 正常运行 var会变量提升
  console.log(b);
}
console.log(b)  // 报错 找不到b

------------------------------------------------------------------------

function test(a) {
  b = a * 5;  
   // 正常运行 函数作用域找不到b 就会去外层找
  console.log(b);
}
var b;

总结:

  • 如果函数内部找不到 就会往外层找 直到到最外层的Global中
  • 在函数中 就算使用 var 定义的变量 也不会被提升到全局的 Global中,但是还是会变量提升
  • 最好的建议还是使用 let 和 const 因为这里的两个是不会变量提升的,不容易出错
闭包不污染全局变量
function test1() {
	var a = 1
}
test1() // 正常运行

--------------------------------------------------------------------------
(function test2() {})(); 
test2(); // 报错 找不到 test2这个函数

总结:

  • 正常使用函数确实是可以让函数里面的成员变量私有化,但是函数自己本身是被定义和赋值到全局对象中了,这个或多或少也是一种污染
  • 使用闭包可以解决这个问题,函数变量名被隐藏在自身中意味着不会非必要地污染外部作用域
  • 当函数可以记住并访问所在的词法作用域,即使函数是在当前词法作用域之外执行,这时就产生了闭包。
块作用域
if(true){
    var a = 5;
}
console.log(a); // 5

--------------------------------------------------------------------------

if (true) {
  let b = 5;
}
console.log(b); // ReferenceError: b is not defined

总结:

  • 在过去 JS 是没有块级作用域这个概念的,只有函数作用域,因为使用var定义的变量都会被提升到最上面(如果var定义在函数里面也会被提升到函数的最上面)

  • 但是好在ES6推出了let和const关键字,让JS也拥有了块级作用域

变量提升的细节
a = 2;
var a; 
console.log( a ); 
// 编译后的代码
var a;
a = 2;
console.log( a ); // 答案是2
-------------------------------------------------------------------------
console.log( a );
var a = 2;//浏览器解析到这里 编译器会将这段代码编译成 var a; a = 2; var a会被提到最前面
// 最终经过编译的最终代码是
var a
console.log( a );  // undefined 因为 a只是定义还没有赋值  所以是 undefined
a = 2
-------------------------------------------------------------------------
foo(); // 不是 ReferenceError, 而是 TypeError!
var foo = function bar() { // ... }; // 这里不是函数声明 而是函数表达式
// 编译器处理后
var foo
foo()  // TypeError 类型错误 还没声明 foo是函数就使用函数的调用方法
foo = function bar(){}
-------------------------------------------------------------------------
foo(); // 1
var foo;
function foo() { console.log( 1 ); } // 函数会被提升到最前面,换句话说,函数最先提升
foo = function() { console.log( 2 ); };

总结:

  • 只有声明本身会被提升,赋值和其他的逻辑会留在原地
  • var 和 函数声明都会提升
  • 函数声明会被提升,但是函数表达式却不会被提升
  • 函数会首先被提升,然后才是变量
闭包
function setupBot(name, selector) { 
    $( selector ).click( function activator() { 
        console.log( "Activating: " + name ); 
    } ); 
} 
setupBot( "Closure Bot 1", "#bot_1" ); 
setupBot( "Closure Bot 2", "#bot_2" );
// 以上的函数就是一个闭包
-------------------------------------------------------------------------
function foo() {
  var a = 2;
  function bar() { 
      console.log( a ); 
  }
  return bar;
}
var baz = foo(); baz(); // 2 —— 朋友,这就是闭包的效果。

总结:

  • 函数是在它本身的词法作用域以外执行。

  • 只要使用了回调函数,实际上就是在使用闭包!

循环和闭包
for (var i=1; i<=5; i++) { 
    setTimeout( function timer() { 
        console.log( i ); 
    }, i*1000 ); 
}
// 结果: 输出5次6
// 因为setTimeout()是一个异步的函数,for循环结束了才会执行setTimeout,而for循环结束的时候 i是等于6的 所以最终会输出5次6
// 因为我们试图在每次循环的时候都能保存下一个i的副本,但是实际上因为作用域,每次都是同一个i,最终i被赋值成了6,所以输出了5次6

解决方案:
for (var i=1; i<=5; i++) { 
    (function(j) { 
        setTimeout( function timer() { 
            console.log( j ); 
        }, j*1000 ); 
    })( i ); 
}
// 这样就可以解决了, 每次运行的时候都将i存进一个立即执行函数里面,立即执行函数又作用域又可以防治变量污染,就可以解决这个问题了

最优方案:
for (let i=1; i<=5; i++) { 
    setTimeout( function timer() { 
        console.log( i ); 
    }, i*1000 ); 
}
// let 声明,可以用来劫 持块作用域,并且在这个块作用域中声明一个变量
函数的this绑定

this绑定如果分类的话可以分为四类:默认绑定、显示绑定、隐式绑定、new绑定

  • 默认绑定

    function demo(){
        cconsole.log(this.a)
    }
    var a = 2
    demo() // 2
    // demo 等于是自己运行自己,这里相当于是 window.demo() this指向的window,所以最终指向了全局的那个等于2的a
    // 注意 如果是严格模式下这个 this指向的就是 undefined 因为 严格模式下的是不能将变量挂载在全局的
    
  • 隐式绑定

    function foo() { 
        console.log( this.a ); 
    }
    var obj = { a: 2, foo: foo };
    obj.foo(); // 2
    // 饮食绑定的简单理解就是 谁调用的函数,this就指向的是谁
    // 这是是 obj.foo() obj就是调用者 所以 this指向的是 obj
    
  • 显示绑定

    function foo() {     console.log( this.a ); }var obj = { a: 2 };foo.call(obj); // 2// 显示绑定很明显,就是使用call或者apply显而意见的告诉绑定的this是谁
    
  • new绑定

    function foo(a) {    this.a = a; }var bar = new foo(2); console.log( bar.a ); // 2//使用 new 来调用 foo(..) 时,我们会构造一个新对象并把它绑定到 foo(..) 调用中的 this 上。new 是最后一种可以影响函数调用时 this 绑定行为的方法,我们称之为 new 绑定。
    
对象和字面量的细节
var strPrimitive = "I am a string";typeof strPrimitive; // "string" strPrimitive instanceof String; // false// instanceof 判断一个对象是由那个构造函数所new出来的/*	字面量并不是真正的对象,如这里的字符串字面量,它并不是通过 String()这个构造函数所new出来的,	那为什么可以使用 .length chatAt() 等这些Stinrg对象的方法呢?		我们可以理解为内部借助了 Object中的toString()方法,*/var strObject = new String( "I am a string" );typeof strObject; // "object" strObject instanceof String; // true/*	幸好,在必要时语言会自动把字符串字面量转换成一个 String 对象,也就是说你并不需要 显式创建一个对象。*/42.359.toFixed(2) // 也是解析器将42.359这个字面量转成 Number对象,所以可以使用Number对象的一些方法
深拷贝和浅拷贝

首先这个概念针对的是引用类型的数据,如数组和对象,顾名思义,深拷贝就是深层次的拷贝,拷贝到要拷贝数据的引用地址去了,而浅拷贝只是简单的把数据的值给拷贝出来

对象浅拷贝方法
  • const obj = Object.assign({},a)
    • assign(对象1,对象2,对象3)
    • 整个的逻辑其实是将对象2,对象3…(源对象)身上可枚举的属性复制到 对象1(目标对象) 最终返回
  • const obj = {...a}
数组的浅拷贝方法
  • const arr = Array.from(brr)
  • const arr = [...brr]
对象的细节
  • 属性不一定包含值——它们可能是具备 getter/setter 的“访问描述符”。此外,属性可以是可枚举或者不可枚举的,这决定了它们是否会出现在 for…in 循环中。
Object.create()的用处

使用Object.create(params1)可以非常轻松的让我们实现继承(原型链指向)。

例子
let father = {    name:'i am father'}let son = Object.create(father)console.log(son.name) // i am father--------------------------------------------------------son.prototye.name = 'i am father'
  • 好处显而易见,当我们要为一个对象的原型上添加多个数据的时候,可以不用一条条的写繁琐的代码,可以直接定义一对象,再通过Object.create(对象)的方式来创建一个对象,这个对象的原型上自动会有目标对象的所有属性
call apply bind的区别

call 和 apply 是同一类的东西,可以放在一起观察,bind可以和call和apply一起观察

  • call和apply都是可以用来修改函数的上下文的指向,方法传递的第一个参数就是要修改的上下文,

  • call和apply都是只应用服务于函数,只有函数才可以使用call和apply方法

  • call如果需要传递多个参数就只能是在第二个参数开始依次的传递,apply第二个参数是一个数组。且最多只能有两个参数

  • bind() 方法创建一个新的函数,在调用时设置 this 关键字为提供的值。并在调用新函数时,将给定参数列表作为原函数的参数序列的前若干项。

    function add (a, b) {    return a + b;}function sub (a, b) {    return a - b;}add.bind(sub, 5, 3); // 这时,并不会返回 8add.bind(sub, 5, 3)(); // 调用后,返回 8-----------------------------------------------/*	bind是会返回一个新的函数,同时这个函数是已经修改过上下文,并且需要什么参数都已经准备好的函数,只需要执行一下就可以*/
    

总结

当自己陷入一个瓶颈的时候,可以选择看书,书本上涉及到的知识点,看完之后会发现自己对一门语言了解的只是冰山一角。花钱买书和读书是对自己投资最小和收益最大的事情

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值