JS基础

124 阅读9分钟

js内置的数据类型

  1. 基本类型:null、undefined、number、string、boolean、symbol
  2. 引用类型:object(array、function、date,regexp)

null

  1. 空对象指针,当定义将来要保存对象值的变量时,最好用null来初始化。

undefined

  1. 当未初始化变量时,变量会自动被赋值为undefined

boolean

  1. 2个值,区分大小写。
  2. 所有其它类型的值都有对应的布尔值的等价形式。
  3. 要将其它类型的值转换为布尔值,可以使用Boolean函数,转换规则如下:
数据类型转换为true的值转换为false的值
Booleantruefalse
Number非零数值(包括无穷值)0、NaN
Object任意对象null
undefinedN/A(不存在)undefined

number

  1. number用双精度值表示整数和浮点值。

  2. 不同的数值类型有不同的数值字面量格式:十进制、八进制(0或0o)、十六进制(0x)

  3. 如果字面量中包含的数字超出了有的范围,就会忽略前面的0,后面数字会被当成十进制数。比如08就是无效的八进制数,因为八进制的范围是0-7。所以08会被当成8处理。

  4. 浮点值:存储浮点值所用的空间是整数值的两倍,所以js会想法把值转化为整数,比如,忽略1.0后面的0。

  5. 科学计数法:表示较大或较小的数,例如3.12e5表示3.12*10^5。

  6. js并不支持表示世界上所有的数值,无法表示的负数为-Infinity、Infinity。

  7. 要确定一个数是否位于js能表示的最小值与最大值之间,可以用isFinite()函数,它会返回一个布尔值。

  8. NaN 9. 用于表示本来要返回数值的操作失败了,比如0除以任何数在js中都会得到NaN。而0作为分母时,则会得到-Infinity或Infinity的结果。 10. 所有涉及NaN的操作,都会返回NaN。 11.NaN不等于包括NaN在内的所有值。 12.isNaN()可以判断一个值是否为NaN。

  9. 将其它类型的值转换为数值类型

    • Number()
    类型
    布尔值true=1、false-0
    数值直接返回
    null0
    undefinedNaN
    对象调用valueOf()/toString(),会得到基本类型的值。
    string如果字符串包含数值字符,包括数值字符前面带加、减号的情况,则转换为一个十进制数值。因此Number("1")返回1,Number("123")返回123,Number("011")返回11(忽略前面的0)。
    如果字符串包含有效的浮点值格式如1.1,则会转换为响应的浮点值,同样忽略前面的0
    如果字符串包含有效的十六进制格式如"0xf",则会转换为与该十六进制值对应的十进制整数值
    如果是空字符串,则返回0
    如果字符串包含除上述情况外的其它字符,则返回NaN
    • parseInt()
      • 在需要得到整数时,优先使用
      • 转换时,最前面的0会被忽略,从第一个非空格字符开始转换,直到字符串末尾或碰到非数值字符(如果第一个数值不是数值/+/-,则立即返回NaN)
      • 最终得到的是一个十进制数
      • 可接受第2个参数n,表示把第一个参数作为n进制来识别。比如:把一个二进制数值转化成十进制,parseInt(1100,2) = 12。
    • parseFloat()
      • 始终忽略开头的0,且只能解析十进制数值
      • 只能解析到字符串末尾或一个无效的浮点数值字符为止,第一次出现的小数点的有效的,第2次是无效的。

string

- 将一个值转换为字符串的方法
    - toString(),可用于将数值、布尔值、字符串、对象和数值转换为字符串,null和undefined无此方法。
    - String(),如果值有toString方法,就调用toString。
    - 在字符串后面加一个空格

object

- constructor,用于创建当前对象的函数
- hasOwnProperty(),用于判断当前实例(不包含原型链)上是否存在指定属性,要检查的属性名必须是字符串。
- isPrototypeOf(),用于判断当前对象是否为另一个对象的原型
- toString(),返回对象的字符串表示
- valueOf(),返回对象的字符串/数值/布尔值表示,通常与toString()的返回值相同
- object是所有对象的基类,所以任何对象都有这些属性或方法,但有些方法可能会重写。

判断数据类型

  1. 使用typeof关键词
    • typeof的原理:js底层在存储变量时,是用机器码的前3位存储类型信息的。比如000-对象,010-浮点数、100-字符串、110-布尔、1-整数。但是存储null和undefined时有些特殊,存储null时,它的机器码全是0,存储undefined时,是用-2^30。而typeof就是校验机器码的前三位来判断类型的。
    • typeof是一个操作符,并不是一个函数,因此使用的时候无需用给函数传参的方式。
    • 对一个值使用此操作符,会返回如下字符串之一:undefined、number、string、boolean、symbol、function、object。严格来讲,function并不是一种数据类型,但之所以能够判断出为function,是由于它自己的特殊性,所以把它和对象区分开来。
    • 缺点:当值为object时,无法判断具体是哪一种object
    typeof null //object 
    
  2. 使用instanceof
    • 原理:判断A的原型链是否在B的prototype上。因为每个js对象都有一个隐式__proto__,它指向构造函数的prototype。
    • 无法判断基本类型,且用来判断数组的时候居多。
[] instanceof Array //true
//模拟实现简版instanceof
const myInstance = (left, Right) => {
    if(typeof left !== 'object') {
        return false;
    };

    while(true) {
        if(left === null) {
            return false;
        };
        if(Right.prototype === left.__proto__) {
            return true;
        };
        left = left.__proto__;
    }
}
  1. Object.prototype.toString.call
    • 原理:Object原型上的方法是可以输出数据类型的。
    • 按理说,js中所有的类都继承了Object,所以也继承了Object的toString()方法,那为什么不使用类上面的toString方法呢?因为所有的类在继承的时候都自己改写了toString方法,所以必须使用原型上的toString方法。
    • 特点:万能方法,但是写法比较复杂。
Object.prototype.toString.call('jack')//[object String]
Object.prototype.toString.call(1)//[object Number]
  1. constructor
    • 原理:所有 特点:不好用,因为如果是null/undefined的话,直接.constructor程序会报错。
var foo = 10;
console.log(foo.constructor);

js的隐式类型转换

JS 本身是一种弱类型的语言,我们不用提前声明好类型,程序运行时,类型会自动分配。

  1. 使用+运算符时的类型转换规则
    • 当使用 + 运算符计算 string 其他类型相加时,都会转换成 string 类型。( 另外的值转成字符串 )
    • 除以上情况外,会转成 number 类型,但是 undefined 会转成 NaN, 结果也是 NaN
    • 当 + 号两边存在 NaN, 则结果为 NaN (Not a Number)
    • 如果 Infinity + Infinity 为 Infinity, Infinity + (-Infinity) 为 NaN
    • 如果 string 相加,直接拼接
    • 如果 + 号两边,有一个是对象,则调用 valueOf() 或 toString()。具体调用哪个取决于内置函数toPrimitive。
    • 其他情况,都会转换成 number 类型
ps:网红题目
can (a == 1 && a == 2 && a == 3) ever evalute to true?
解法:
const a = {
    value: 1,
    toString: function() {
        return a.value ++
    }
}


let value = 0;
Object.defineProperty(global, 'a', {
    get: function() {
        return ++value;
    }
})

JS 的 引用与拷贝

  1. 基本的数据类型,保存在栈中(占用空间小、大小固定、频繁使用);
  2. 其他Array, Object, Function 都是 引用数据类型(占用空间较大,大小不固定);

Object.assign

特点:

  1. 值拷贝;
  2. 返回值为第一个参数;
  3. 浅层 merge;
const foo = {
    a: 1, 
    c: { 
        d: 50 
    }
};
const bar = { a: 10, b: 20};
const baz = Object.assign(bar, foo);
foo.c = 200;
console.log(baz);//baz.c.d=50.

arr.slice

特点:拷贝的是地址。所以修改时,会相互影响。

const arr = [
    {name:'luuuu'},
    {name:'luuuu2'}]
const newArr = arr.slice(0,1)
arr[0].name 'zzzzzzz';
console.log(newArr)//[{name:'zzzzzzz'},{name:'luuuu2'}]

函数

定义方式

  1. function foo(){}
  2. const foo = function(){}

函数的调用方法

  1. 作为函数的调用
    function foo() {
        console.log('foooo');
    };
    foo();
    
  2. 作为方法的调用
    const bar = {
        foo() {
            console.log(`this is foo method of ${this.name}`)
        },
    
        name: "BAZ"
    };
    global.name = "GLOBAL";
    const foo = bar.foo;
    foo(); // this is foo method of GLOBAL
    bar.foo(); // this is foo method of BAZ
    
    
  3. 作为构造函数的调用
    const Bar = function(name, fn) {
        this.name = name;
        this.handler = fn;
    };
    
    const bar = new Bar('bar', function() {
        console.log(this.name);
    });
    
    console.log(bar);
    
    bar.handler();
    
  4. 被call或者apply
const bar = {
    // bar 这个对象,有个函数,叫做foo,可以打印对象调用者的名字和参数。
    foo(param) {
        console.log(`this is foo method of ${this.name},the param is ${param}`)
    },
    name: "BAR",
};

// 假如,我现在,有一个对象,叫做 obj, 这个 obj 想要使用这个 bar 的 foo 方法
// 但是我没有,我就只有“借用”。
const obj = { name: 'simple object' };

bar.foo.apply(obj, ['objParam'])

// foo -- frist object oriented
// bar -- binary arbitrary reason 任意二进制原因 

方法链

const person = {
    addAge: function(age) {
        this.age = age;
        return this;
    },

    addName: function(name) {
        this.name = name;
        return this;
    },

    addSkills: function(...skills) {
        this.skills = skills.join(',');
        return this;
    },

    print: function() {
        console.log(`I am ${this.name}, I am ${this.age} years old. My skills are ${this.skills}`);
    },
}

person.addName('luyi')
    .addAge(35)
    .addSkills('FE', 'Teaching')
    .print();

实参和形参

形参

  • JavaScript 语言本身,既没有给形参限制类型,也没有给形参做类型检查。
    • JS 是没有重载的。
    • 重载指一个类中可以有多个相同名称的方法,但是这些方法的参数不同。
  • 形参是可选的,
    • 我传入的实参,既可以比形参多,也可以比形参少。
  1. 当我的实参传入,少于形参的时候,按顺序走,多出来的,是 undefined;

function createPerson(name, age, isMale) {
    // 要求,
    return {
        name, age, sex: isMale ? 'male' : 'female'
    }
};


// console.log(createPerson('luyi', 35, true));
// console.log(createPerson('yunyin', true));


function createPerson2({ name, age, isMale }) {
    // 要求,
    return {
        name, age, sex: isMale ? 'male' : 'female'
    }
};

console.log(createPerson2({name: 'luyi', isMale: true }));
  1. 当我的实参传入,多于形参的时候
    1. arguments
    2. ...rest
function createPerson(name, ...rest) {
   // 要求,
   console.log(`${name}‘s messages is ${rest.join(', ')}`);
};

createPerson('luyi', 'a male', '35 years old', 'a FE', 'like reading and teaching');
function createPerson(name) {
    // 要求,
    // console.log(arguments); 
    // 伪数组
    const rest = Array.prototype.slice.call(arguments, 1);
    console.log(`${name}‘s messages is ${rest.join(', ')}`);
};

createPerson('luyi', 'a male', '35 years old', 'a FE', 'like reading and teaching');

实战: 写一个无限加法

function add(...rest) {
    return rest.reduce((total, item) => total + item, 0);
}

function add2() {
    return Array.prototype.reduce.apply(arguments, [(total, item) => total + item, 0]);
}

console.log(add(1,2,3,4,5));
console.log(add2(1,2,3,4,5));

高阶函数:函数是一等公民

1. 函数作为值

// 函数可以作为值
function square(x) { return x * x };

const a = square;

console.log(square(2));
console.log(a(2));

2. 函数作为参数

function calculate(param, cb) {
    setTimeout(() => {
        const res = param * param - param;
        cb(res);
    }, 500);
};

calculate(234, (res) => {
    console.log(`after 500ms, the result is ${res}`);
});

3. 函数作为返回值

function discount(ratio) {

    let _ratio = ratio;
    
    return function(price) {
        console.log(_ratio * price);
    };
};

const discount75 = discount(0.75);
discount75(100);
discount75(100);
discount75(100);
discount75(100);
discount75(100);

实战:防抖与截流

// luyi
// luyi // 300ms 你没有在输入,
// 防抖与节流
function debounce(fn, delay = 300) {
    // 默认 300 毫秒
    let timer;
    return function() {
        const args = arguments;
        if(timer) {
            clearTimeout(timer);
        }

        timer = setTimeout(() => {
            fn.apply(this, args);
        }, delay);
    }
};

function foo() {
    console.log('foo');
}

const de_handleChange = debounce(handleChange, 300);

de_handleChange();
de_handleChange();
dfoo();
// throttle
function throttle(fn, delay) {
    let flag = true;
    return () => {
        if(!flag) return;
        flag = false;
        setTimeout(() => {
            fn();
            flag = true;
        }, delay);
    }
};

function foo() {
    console.log('foo');
}

const tfoo = throttle(foo, 300);
tfoo();
tfoo();
tfoo();
setTimeout(() => {
    tfoo();
    tfoo();
    tfoo();
    tfoo();
    tfoo();
},400);