js笔记

2019-04-18  本文已影响0人  Gaarahan

1. 关于循环

  1. for( A ) { B };
    for循环的A和B是两个不同的作用域,B作用域是A作用域的子域,如下面的代码
for(let i = 0; i < 3;i++){
  let i = "han";
  console.log(i);
}
// => han
//    han
//    han

在上面的代码中

2. 变量提升(没有块级作用域)的问题

var tmp = new Date();
function f(){
  console.log(tmp);
  if(false){
    var tmp = 'hello world';
  }
}
// => undefined

如上代码,本来console.log()输出的应该是全局的变量tmp,即当前的时间,但由于if语句中的tmp变量
提升,覆盖了全局变量tmp的值,输出为undefined

var s = 'hello';
for (var i = 0; i < s.length; i++) {
  console.log(s[i]);
}
console.log(i); 
// => 5

上面代码中,变量i本来只属于循环,但由于变量提升,它被声明为了全局变量

3. 变量声明

es6 有6种变量声明的方法
var(es5) function(es5) let const import class
js 的七种数据类型
string boolean number null undefined Object Symbol

4. 一些新增的常用的字符串处理方法

  let s = "Gaarahan";
  //1.  内容查询
  s.startsWith('Gaa'); //true
  s.endsWith('han');   //true
  s.includes('aha');   //true
  
  //第二个参数表示开始搜索的位置
  {
    //这俩搜索的是从0到第二个参数n
    s.startsWith('Gaa',1); //false
    s.includes('aha',s.indexOf('aha') ); //true
    // 这个搜索的是前n个字符
    s.endsWith('han',s.length - 2); //false 
    s.endsWith('han',s.length);     //true
  }

  //2. 重复
  'han'.repeat(3);   // "hanhanhan"
  'han'.repeat(3.6);   // "hanhanhan"

  //3. 字符串补全
  s.padStart();
  s.padEnd();

  'Gaara'.padStart(8,'han')  //"hanGaara"
  'Gaara'.padStart(6,'han')  //"hGaara"
  'Gaara'.padStart(5,'han')  //"Gaara"
  'Gaara'.padStart(7)        //"  Gaara" 无第二参数,默认空格补全

5. 正则的方法

let str = "aaa_aa_aaaa";
let r1 = /a+/g;

r1.exec(str); 
... //多次运行的结果为 =>  ["aaa"] , ["aa"], ["aaaa"] , null, ["aaa"] , ["aa"] ...

注意必须将上面的正则表达式创建为一个变量,才能每次都从上次匹配的结果处继续

let str = "aaa_aa_aaaa";

/a+/g.exec(str); 

// => 多次运行结果为 ["aaa"] ["aaa"] ["aaa"] ["aaa"] ..
//  始终指向第一处匹配,因为每一次都是用的一个新的正则表达式,其中并不会存储上次的
//  查询位置

6. 关于数字

Number.isInteger(25); //true 
Number.isInteger(25.0); //true
Number.isInteger(25.1); //false
//超过范围的数字无法准确判断
Math.pow(2,53) === Math(2,53) + 1; // true
Math.ceil(4.9)    // 5
Math.floor(4.9)   // 4

Math.ceil(- 4.9)   // -4
Math.floor(- 4.9)  // -5

7. 尾调用优化与递归

  let x = ()=>{
    let i = 1;
    return g(i); //尾调用
  }
  let y = ()=>{
    let i = 1;
    let j = 2;
    let g = (b) = >{
      return b + j;
    }
    return g(i); 
//不是尾调用,除了传参之外,还使用了外部函数的内部变量,此时函数y的调用帧不能被删除
  }

提出尾调用的目的是为了优化代码,函数中的嵌套调用的过程中,我们将当前的主函数的状态
(子函数的调用位置,以及主函数中的内部变量)保留
,为子函数以及其参数重新创建一个调用帧
(call frame),当子函数调用完成后,我们才删除这个调用帧,并将结果带回主函数,继续我们主函
数的调用帧。
但是,对于尾调用来说,我们已经不再需要主函数中的调用位置以及各种内部变量了,返回到主函
数后,我们唯一需要做的就是将子函数的返回值再做一次返回,如下:

  //两个定义好的函数,y尾调用了z
  function y(){
    let i = 3;
    return z(3);
  }
  function z(i){
    return i*2;
  }

  //函数x
  function x(){
    ...
    y();
    ...
  }
  // 优化后相当于:
  function x(){
    z(3);
  }

从上面的函数,我们可以看出,尾调用的优化是很有必要的,而优化的过程可以这样简单理解:
对于第一个x函数,我们调用了函数y,为函数y创建了一个调用帧,而函数y又尾调用了函数z

我们知道,尾调用的函数z已经不需要再使用函数y中的调用位置了(因为z是最后一步操作,
它在y中的下一步操作就是返回到函数x中去),同时函数y中的内部变量也已经通过传值的方式
保存了下来,那么函数y的调用帧已经没有存在的必要了(我们可以直接把函数z返回到主函数x
中去,而不需要返回到y,再由y返回到x)

此时,我们可以删除函数y的调用帧,用内部函数z的调用帧取代函数y的调用帧,就可以节省
一部分调用栈(call stack)的空间
  function a(){
    let i = 1;
    return b(i);
  }
  function b(i){
    let j = i + 2;
    return c(j);
  }
  function c(j){
    return j;
  }

如上,函数a,b都使用了尾调用,当我们调用函数a时,为函数a创建了一个调用帧,接着当我们尾调用
函数b时,我们使用函数b的调用帧替代了函数a的调用帧,此时调用栈中仍然只存在一个调用帧b,而
当我们继续执行,调用函数c时,我们又使用函数c的调用帧来替代调用栈中唯一的调用帧b

  //原始的递归求阶乘代码
  function func1(i){
    if(i === 1) return 1;
    return func1(i - 1) * i;
  }
  // 改写为尾调用函数
  function func2(i,temp){
    'use strict'
    if(i === 1){
      return temp;
    }
    temp *= i;
    return func2(i - 1,temp);
  }
  //在浏览器控制台运行
  func1(100000); // 栈溢出
  func2(100000,1); // 栈溢出 ???


  // 2. 原始的菲波纳切函数
  function fe1(i){
    if (i <= 1)
      return 1;
    return f(i - 1) + f(i - 2);
  }
  function Fibonacci2 (n , ac1 = 1 , ac2 = 1) {
    if( n <= 1 ) {return ac2};
    return Fibonacci2 (n - 1, ac2, ac1 + ac2);
  }

  Fibonacci2(100) // 573147844013817200000
  Fibonacci2(1000) // 7.0330367711422765e+208
  Fibonacci2(10000) // Infinity

8. call apply bind

  1. 注意,箭头函数的this指向是固定的,始终指向函数定义时的对象,而不是使用时的对象
    无法使用这三个函数来为其指定this的指向
  let a = {x:3};
  let func = ()=>{
    console.log(this); 
    //箭头函数没有自己的this,它的this是该函数外层代码块的this
    // 在此处是window对象
  }
  func(); //=> window

  //call()
  func.call(a); // =>window
  //apply
  func.apply(a); // => window
  // bind()
  let func2 = func.bind(a);
  console.log(func2);// => ()=>{console.log(this);}
  func2(); // window

1.function.call(thisArg,arg1,arg2, ...)
每个函数都有自己的this指向,使用call方法可以为我们调用的函数指定其this指向

  function con(x){
    console.log(this.a);
  }
  con(1);  //  => undefined
  
  //使用call函数为其指出this指向
  con.call({a:2},1); // => 2
  function con(){
    console.log(this); // => Window对象
    return 1;
  }
  let a = con.call(null); 
  console.log(a); // 1
class Product{
  protected String name;
  protected String price;
  public Product(String name,String price){
    this.name = name;
    this.price = price;
  }
}
class Food extends Product{
  private String kind;
  public Food(String name,String price,String kind){
    super(name,price);
    this.kind = kind;
  }
}
Food apple = new Food("apple","15","friut");

在js中,我们可以借助call这样来写

  function Product(name,price){
    this.name = name;
    this.price = price;
  }
  function Food(name,price,kind){
    Product.call(this,name,price);
    this.kind = kind;
  }
  let apple = new Food('apple','15','friut');
  console.log(apple); // => Food{name:'apple',price:'15',kind:'friut'};

对比上面的写法,我们在js中使用了call来调用函数Product,类似与在java中使用super()
函数来调用父类的构造方法

  let argsArr = Array.prototype.slice.call(arguments);

使用forEach方法来遍历HTMl collection

  [].forEach.call(htmlCol,(val,index,arra=>{
    console.log(val);
  }))
  1. function.apply(thisArg,[args])
    apply的用法和call差不多,区别在与apply所调用函数的参数是以数组或者类数组方式提供的
    也就是说,call中能使用的地方,都可以使用apply来替代,有时apply写的还更简单
  function Product(name,price){
    this.name = name;
    this.price = price;
  }
  function Food(name,price,kind){
    Product.apply(this,arguments); 
    //这里直接将Food函数的arguments对象传过去,虽然多传了一个参数kind,但在写法上更简单
    this.kind = kind;
  }
  let apple = new Food('apple','15','friut');
  console.log(apple); // => Food{name:'apple',price:'15',kind:'friut'};
  let a = [1,2,3];
  let b = [4,5,6];
  [].push.apply(a,b);
  console.log(a); // => [1,2,3,4,5,6]
  let a = [1,2,3,45,6,7];

  let res = Math.max.apply(null,a);
  console.log(res); // => 45

  //另一种简化写法 : es6
  Math.max(...a); // => 45
  1. function.bind(thisArg,arg1,arg2 ...)
    bind函数不是一次原函数的调用,它会返回一个新的函数,具有指定的this指向,并且在
    调用新函数时,在bind时指定的参数会作为新函数的前几项参数值
  let a = {x:3};
  function func(){
    console.log(this.x);
  }
  func(); //=> undefined

  let func2 = func.bind(a);
  console.log(func2);// => ()=>{console.log(this.x);}
  func2();  // =>3

关于继承的常用方法

a instanceof A // 判断a是否为A的实例
Object.getPrototypeOf(a) === A   // 获取实例a的原型
Object.getPrototypeOf(Gaara) === Person   // 获取子类Gaara的父类,判断继承

上一篇下一篇

猜你喜欢

热点阅读