JS基础及基本算法

js基础

一. 作用域

1.1 前言

都知道JS存在三种作用域,即全局,函数,块级。其实是没有块级作用域的,但我们可以通过闭包来实现它。

1.2 函数作用域

很好理解,在函数内部声明的变量在函数调用后会销毁

1.3 块级作用域

在if,while,for等代码体用{}包括的,成为块级作用域,但是在JS中没这说法。
比如

  

问题出来了,按理说正常情况是控制台输出0~9,弹出undefined,但是alert出来的却是9,

那这足以说明咱们的JS是不存在块级作用域的,块里面的变量是被挂载在window上的。

那么这会引发一个问题,看下面代码:

  var arrs=[];
  

这里你不管执行 第几个函数都是打印9,原因有两个,第一因为JS中不存在块级作用域,i的声明是挂载在window上的,再一个,函数在执行的时候获取到的i为循环最后一次+1的i,所以恒定为9。
解决办法:利用闭包虚构一个块级作用域,并将i的值传入。

二. 闭包

注意事项:
1.函数的传参是传入的复制值
2.闭包其实就是能获取到函数内部变量的函数

var a1 = function(){
          var n = 999;
          a2 = function(){
                n++;
          }
          return n;
}
console.log(a1());    //   999
a2();
console.log(a1());    // 1000

3.闭包的两个主要作用:
i.获取函数内部的变量
ii.使函数内部的变量不被回收

 var arrs=[];
  

此时当函数执行的时候会拿到这个立即执行函数的i的值,而立即执行函数的值是for循环传入的,所以就能正常输出0~9。

三. JavaScript数据类型

分为基本类型和引用类型
值类型(基本类型):
字符串(String)、数字(Number)、布尔(Boolean)、空(Null)、未定义(Undefined)、Symbol
引用数据类型:
对象(Object)、数组(Array)、函数(Function)

判断数据类型的方法

    var arr = [];

1.typeof

    console.log(typeof arr)   // object    对于复杂类型的对象只能返回object
    console.log(typeof null)   // object 

2.instanceof

    console.log(arr instanceof Array)   // true    可以判断变量的引用类型

3.Object.prototype.toString.call()

    Object.prototype.toString.call(arr) == "[object Array]";     //基本类型和引用类型都可以得到

四. undefined和null的区别

null表示"没有对象",即该处不应该有值。典型用法是:

(1) 作为函数的参数,表示该函数的参数不是对象。
(2) 作为对象原型链的终点。
(3) 释放内存

    Object.getPrototypeOf(Object.prototype)    // null


     var arr = [1,2,5,41,54,] 
     arr = null;  

undefined表示"缺少值",就是此处应该有一个值,但是还没有定义。典型用法是:

(1)变量被声明了,但没有赋值时,就等于undefined。
(2) 调用函数时,应该提供的参数没有提供,该参数等于undefined。
(3)对象没有赋值的属性,该属性的值为undefined。
(4)函数没有返回值时,默认返回undefined。

    console.log(typeof undefined);  //undefined
    console.log(typeof null);  //object

    console.log(Number(undefined));//NaN
    console.log(Number(10+undefined));//NaN

    console.log(Number(null));//0
    console.log(Number(10+null));//10

五.Boolean与条件判断

首先明白两点
1.只有false,"",0,null,NaN,undefined会被转换成false。
2.任何类型的数据在与布尔值比较时都会被转换为number类型。

    console.log(Boolean(false));  //false

    console.log(Boolean("false"));  //true

其实我们的if语句的条件没有运算符的情况下就是执行了Boolean操作

    if(x){
        
    }
    //等价于
    if(Boolean(x))

那么能看懂下面的东西就对这块掌握的比较好了

    console.log(Number([]));      //0
    console.log([] ? true : false);   //true     if语句同等效果

    console.log(Number({}));      //NaN
    console.log({} ? true : false);   //true     if语句同等效果

    console.log([] == false);    //true
    console.log({} == false);    //false

六.Symbol

Symbol是由ES6规范引入的一项新特性,它的功能类似于一种标识唯一性的ID
创建方式:
Symbol()

    const name = Symbol('username')

特性一:每一次创建的Symbol都是唯一的,就算描述一致

    console.log(Symbol("username") === Symbol("username"));   //  false

特性二:可以作为对象的属性,只能以[]方式访问

const name = Symbol('username');   
let obj = {
   [name]: '张三',  
   age: 18,
   sex: 'male'
}
//或者
let obj = {
   age: 18,
   sex: 'male'
}
obj[name] = '张三';

console.log(obj[name]);   //张三
console.log(obj.name);   //undefined

特性三:可以被外部访问,但不能被枚举

const name = Symbol('username');   
let obj = {
   [name]: '张三',  
   age: 18,
   sex: 'male'
}
console.log(obj[name]);   //Symbol只能以[]方式访问

Object.keys(obj)   //  返回一个由obj所有自身属性的属性名(不包括不可枚举属性也不包括Symbol值作为名称的属性)组成的数组   ['age', 'sex'] 

for (let p in obj) {
   console.log(p)   // 分别会输出:'age' 和 'sex'
 }

Object.getOwnPropertyNames(obj)   // 返回一个由obj所有自身属性的属性名(包括不可枚举属性但不包括Symbol值作为名称的属性)组成的数组    ['age', 'sex']

console.log(Object.getOwnPropertySymbols(obj))   //   [Symbol(username)]

Reflect.ownKeys(obj) // 返回一个由obj所有自身属性的属性名(包括不可枚举属性和Symbol值作为名称的属性)组成的数组 [Symbol(username), 'age', 'title']

全局Symbol,使用Symbol.for可以保证创建的symbol唯一

console.log(Symbol.for("username") === Symbol.for("username"));   //  true

七.Bind函数

下面是MDN的bind函数实现,这里对下面的一些写法进行剖析。

bind做了什么?
1.调用bind,就会返回一个新的函数。
2.新的函数里面的this就指向bind的第一个参数,同时this后面的参数会提前传给这个新的函数。调用该新的函数时,再传递的参数会放到预置的参数后一起传递进新函数。

实现:

if (!Function.prototype.bind) {
        Function.prototype.bind = function (oThis) {
            if (typeof this !== 'function') {
                // closest thing possible to the ECMAScript 5
                // internal IsCallable function
                throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
            }

            var aArgs = Array.prototype.slice.call(arguments, 1),
                fToBind = this,
                fNOP = function () {},
                fBound = function () {
                    // this instanceof fBound === true时,说明返回的fBound被当做new的构造函数调用
                    return fToBind.apply(this instanceof fBound ?
                        this :
                        oThis,
                        // 获取调用时(fBound)的传参.bind 返回的函数入参往往是这么传递的
                        aArgs.concat(Array.prototype.slice.call(arguments)));
                };

            // 维护原型关系
            if (this.prototype) {
                // Function.prototype doesn't have a prototype property
                fNOP.prototype = this.prototype;
            }
            // 下行的代码使fBound.prototype是fNOP的实例,因此
            // 返回的fBound若作为new的构造函数,new生成的新对象作为this传入fBound,新对象的__proto__就是fNOP的实例
            fBound.prototype = new fNOP();

            return fBound;
        };
    }
  • 1.Array.prototype.slice.call(arguments, 1)

我们都知道arguments是一个类数组对象,所以这里调用数组的slice方法给arguments使用。因为arguments能像访问数组的形式进行访问(arguments[0]),且具有length属性,因此我们可以得到返回的一个有length长度的数组。
后面传入参数1,是slice(start, end)中的一个参数start,表示从arguments的小标为1,即第二个参数开始切割。 这里是将bind函数的参数数组取出来,第一个参数不要(就是不要oThis)也就是要被绑定方法的那个对象

  • 2.fNOP和fBound函数
  //假设bind过程如下
  function a(){
       this.name = 'a';
  }
  var b = a.bind(oThis);
  • 正常调用bind函数
    返回一个新函数,该函数的this指向调用bind方法的一个参数。即代码里的:
  //bind函数内部解析
  fToBind = this;   //这里的this就是a;
  fToBind.apply(oThis,........................)  //这里便是将oThis替换a函数的this;
  return fBound;    //即b的this便是指向oThis;

到这里正常调用bind并传参就完成了

  • bind返回的函数作为构造函数的情况。首先我们得理解new做了什么,如下:
  var o = new fun();
  //实际操作
  var o = new Object();
  o.__proto__ = Foo.prototype;
  fun.call(o);

这里可以看到,当fun被当做构造函数执行时,fun的this指向的是o,且o可以访问fun的原型的属性。

  fNOP = function () {} ;
  if(this.prototype) {
    // 规避Function.prototype没有prototype属性
    fNOP.prototype = this.prototype;    // 构造新函数,将a函数的prototype做一个中转防止原型链污染
  }
  fBound.prototype = new fNOP();

这样便兼容了新函数作为构造函数的情况。
这里有一个疑问,为什么新函数的要继承a函数的原型,以下是mdn的解释:

bind() 函数会创建一个新绑定函数(bound function,BF)。绑定函数是一个exotic function object(怪异函数对象,ECMAScript 2015中的术语),它包装了原函数对象。调用绑定函数通常会导致执行包装函数。

基本算法

一.冒泡算法

作为经典入门算法之一,因为其每轮的比较都会确认一个最大(最小)的数,就像冒出来一样而得名。

设计思路:
  • 确定程序执行次数
  • 两两比较,按照规定交换位置
程序实现
const arr = [10,5,6,25,1000,1];
function bubbleSort(arr) {  
    for(let i = 0,len=arr.length;iarr[j+1]) {
                let tem = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = tem;
            }
        }
    }
    return arr;
}
console.log(bubbleSort(arr))  //[1, 5, 6, 10, 25, 1000]

按照上面的数组来说就是

第一轮.......

第一次:10和5比较,交互位置,此时数组为[5,10,6,25,1000,1]
第二次:10和6比较,交换位置,此时数组为[5,6,10,25,1000,1]
..................................................
第length-1次:此时数组为[5,6,10,25,1,1000]
可以看到我们已经确定了一个最大数1000,那么接下来就将[5,6,10,25,1]继续执行第二轮

第二轮.......
第length-1轮.......得到最终排好序的数组

冒泡算法效率低,我们在此基础上尽量的去优化其效率
重点解释两个地方

1.外层循环次数length-1

  • 按照我们的算法设计逻辑,因为每一次都会有一个数被确定,所以当执行了l-1轮,就剩最后一个数。自然是最小(最大)的,所以无需再执行

2.内层循环次数length-1-i

  • 首先一个数组两两比较需要的次数为length-1次,上面我们说到每轮都会确定一个数,那么确定的数无需进行比较所以再-i

二.选择排序

类似与冒泡,不同的是每轮确定一个最小(最大)的数,再将其依次安放

程序实现
const arr = [10,5,6,25,1000,1];
    function selectSort(arr) {
      const len = arr.length;
      let minIndex, temp;
      for (var i = 0; i < len - 1; i++) {
        minIndex = i;   //当前位置设置为最小
        for (var j = i + 1; j < len; j++) {
          if (arr[j] < arr[minIndex]) {      //检查其他位置上有无比设定的最小位置更小的
            minIndex = j;
          }
        }
        if(minIndex != i){     //当前位置不是最小值就交换
            temp = arr[i];
            arr[i] = arr[minIndex];
            arr[minIndex] = temp;
        }
      }
      return arr;
    }
console.log(selectSort(arr))  //[1, 5, 6, 10, 25, 1000]

三.快速排序

确定一个中间值,和其他元素进行比较,小的放左边数组,大的放右边数组。再递归左边的数组和右边的数组,最后合并

const arr = [10,5,6,25,1000,1];
    function quickSort(arr) {
      if(arr.length <=1){
         return arr;
      }
      let c = arr[0],leftArr=[],rightArr=[];
      for(var i = 1;i c){
            rightArr.push(arr[i]);
          }else{
            leftArr.push(arr[i]);
          }
      }
      return [].concat(quickSort(leftArr),c,quickSort(rightArr));
    }
console.log(quickSort(arr))  //[1, 5, 6, 10, 25, 1000]

你可能感兴趣的:(JS基础及基本算法)