JavaScript数据类型、引用类型、操作符、语句

数据类型

动态类型语言大多包含引用类型,C语言的所有变量都是值模型,但是指针可以实现引用

数据分类:

引用类型(值存储在堆):Object、Array
值类型(值存储在栈):

非空类型:Boolean、Number、String、Symbol
空类型:Undefined、Null 最空的对象

在js中不能定义自己的数据类型,所有值都可以用上述数据类型之一表示。

Java 中,声明变量需要指定类型。JavaScript 是一种弱类型的语言,不需要这样做。虽然只有七种类型,但是很灵活,一种类型可以当多种数据类型来使用。

原始值与引用值的区别

原始值不能有属性,只有引用值可以添加属性
typeof操作符对检测原始值很有效

不能区别未初始化变量和未定义变量
不能区别null、object、array
typeof NaN是number

let n="hhh";
n.age=2;
let m=new String('hhh');//new==>object
m.age=2;
console.log(n.age);//undefined
console.log(typeof n);//String
console.log(m.age);//2
console.log(typeof m);//object
console.log(typeof null);//object,认为是对空对象的引用

variable instanceof constructor 操作符返回布尔
关于使用instanceof的前提:只有一个全局执行上下文。如果网页里有多个框架,则可能涉及多个版本的内置类型的构造函数。如果数组在a上下文创建,在b使用instanceof判断,就会false。

最好使用专用的判断方法,e.g: Array.isArray

PS 深/浅拷贝

注意:操作 Object, Array 这样的引用对象时候需要注意

浅拷贝(相互影响):把一个引用A的堆地址给了另一个引用A1
深拷贝:为A1新开辟了一个堆地址内存,把一个对象A中的所有属性、方法一个一个找到存储进去

?属性也是一个引用的时候,即需要留意对象的层级:属性是否是一个引用、以及对应引用的属性是否又是一个引用

浅拷贝 :符号 = ,splice、fill、push、 pop、 shift、unshift、Object.assign
深拷贝:JSON.parse(JSON.stringify(old))、 concat 、 slice、 js扩展运算符(…)、flat、 Array.from

辩证的看带 concat 、 slice、 js扩展运算符(…)、Array.from 的深拷贝,只是一层深拷贝,如果是嵌套的情况,嵌套部分也是引用

JSON.parse(JSON.stringify(old))存在弊端
深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object
如果对象中存在循环引用的情况也无法正确处理。
JSON.stringify() 时,以 symbol 值作为键的属性会被完全忽略
对于正则表达式类型、函数类型等无法进行深拷贝(而且会直接丢失相应的值)
在这里插入图片描述

Array.from 浅

Array.from返回一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例。
在这里插入图片描述
注意:可选参数fn,类似map方法

console.log(Array.from([1, 2, 3], x => x + x));
// expected output: Array [2, 4, 6]

Object.assign 浅

Object.assign
将所有 可枚举和自有属性、键为符号的属性 从一个或多个源对象分配到目标对象,返回目标对象。
当可枚举属性是一个引用的时候,就是浅拷贝,源和目标对象共享一个引用数据,任何一方的修都会影响到另一方。

let obj1 = { a: 0 , b: { c: 0}};
let obj2 = Object.assign({}, obj1);
console.log(JSON.stringify(obj2));
// { a: 0, b: { c: 0}}
obj2.b.c = 3;
console.log(JSON.stringify(obj1));
// { a: 0, b: { c: 3}}
console.log(JSON.stringify(obj2));
// { a:0, b: { c: 3}}
obj1.b.c = 4;
console.log(JSON.stringify(obj1));
// { a: 0, b: { c: 4}}
console.log(JSON.stringify(obj2));
// { a:0, b: { c: 4}}
扩展运算符 浅

向对象添加属性

const user = {
  firstname: 'Chris',
  lastname: 'Bongers'
};
const output = {...user, age: 31};

对象的扩展运算符(…)用于取出参数对象的所有可枚举属性,拷贝到当前对象之中。

let a = [1,2,3];
let b = [...a];
a == b // false

a[0] = 11;
console.log(a); // [ 11, 2, 3 ]
console.log(b); // [ 1, 2, 3 ]

let a = [1,2,[1,2,3]];
let b = [...a];
a[2][1] = 11;
console.log(a); // [ 1, 2, [ 1, 11, 3 ] ]
console.log(b); // [ 1, 2, [ 1, 11, 3 ] ]
console.log(a[2] === b[2]); // true

通过for循环递归调用

function deepClone(obj){
           //判断obj是否是数组
        let objClone = Array.isArray(obj)?[]:{};  
        if(obj && typeof obj==="object"){
            for(key in obj){
                if(obj.hasOwnProperty(key)){
                    //判断ojb子元素是否为对象,如果是,递归复制
                    if(obj[key]&&typeof obj[key] ==="object"){
                        objClone[key] = deepClone(obj[key]);
                    }else{
                        //如果不是,简单复制
                        objClone[key] = obj[key];
                    }
                }
            }
        }
        return objClone;
    } 
    var arr4 = deepClone(arr1);
    arr4.name='bananer';
    console.log(arr4);
    console.warn(arr1);

可以导入三方包

import '_' from 'lodash';
let data = _.cloneDeep(this.state.data);
P.PS 可枚举属性

前文提到:扩展符、Object.assign也是只能获取到对象的可枚举属性

可枚举属性: 属性的描述符号 “可枚举” 标志enumerable值为 true

通过直接的赋值和属性初始化的属性——该标识值默认为即为 true
通过 Object.defineProperty 等定义的属性——该标识值默认为 false

属性的所有权是通过判断该属性是否直接属于某个对象决定的,而不是通过原型链继承的。hasOwnProperty方法判断,即使属性的值是 null 或 undefined,只要属性存在,hasOwnProperty 依旧会返回 true。

注意: in 运算符不同,是另一分类角度
in表示可访问,包括从原型链上继承到的属性、不可枚举属性,但是会排除 Symbol属性

判断属性

判断是否可枚举属性 : hasOwnProperty 获取属性后使用 propertyIsEnumerable 过滤可枚举属性

var buz = {
  fog: 'stack'
};

for (var name in buz) {
  if (buz.hasOwnProperty(name)) {
    console.log('this is fog (' +
      name + ') for sure. Value: ' + buz[name]);
  }
  else {
    console.log(name); // toString or something else
  }
}

const object1 = {};
const array1 = [];
object1.property1 = 42;
array1[0] = 42;

console.log(object1.propertyIsEnumerable('property1'));
// expected output: true

console.log(array1.propertyIsEnumerable(0));
// expected output: true

console.log(array1.propertyIsEnumerable('length'));
// expected output: false

console.log(array1.hasOwnProperty('length'));
// expected output: true

注意:JavaScript 并没有保护 hasOwnProperty 这个属性名
对象可能自有一个占用该属性名的属性时,就需要使用外部的 hasOwnProperty 获得正确的结果:

var foo = {
  hasOwnProperty: function() {
    return false;
  },
  bar: 'Here be dragons'
};

foo.hasOwnProperty('bar'); // 始终返回 false

// 如果担心这种情况,
// 可以直接使用原型链上真正的 hasOwnProperty 方法
({}).hasOwnProperty.call(foo, 'bar'); // true

// 也可以使用 Object 原型上的 hasOwnProperty 属性
Object.prototype.hasOwnProperty.call(foo, 'bar'); // true
获取属性

Object.keys(obj) 返回obj的所有可枚举属性的字符串数组
Object.getOwnPropertyNames(obj) 返回obj的所有自身属性的属性名(包括不可枚举属性但不包括Symbol值作为名称的属性)组成的数组

在这里插入图片描述
Object.getOwnPropertySymbols(obj) 方法返回obj自身的所有 Symbol 属性的数组(包括:可/不可枚举)

getOwnPropertyNames+getOwnPropertySymbols获取全部属性
getOwnPropertyNames、getOwnPropertySymbols 获取属性后使用 propertyIsEnumerable 过滤可枚举属性

var obj = {};
var a = Symbol("a");
var b = Symbol.for("b");

obj[a] = "localSymbol";
obj[b] = "globalSymbol";

var objectSymbols = Object.getOwnPropertySymbols(obj);

console.log(objectSymbols)         // [Symbol(a), Symbol(b)]

附录typeOf

typeof 操作符可以显示大多数常用类型信息,但是

不能区别未初始化变量和未定义变量
不能区别null、object、array
typeof NaN是number

区别对象类型:
实例.constructor == 构造函数名字
实例 instanceof 构造函数名字
在这里插入图片描述
function 同样算是一种 object,为什么不像 Array 一样返回 object 而是返回 function?
具有内部[[Call]]属性的对象被称为函数。[[Call]]是除了[[Class]]属性(相当于“Function”)object 识别的主要部分。

在这里插入图片描述

typeof的返回值是string

2.0 Undefined

当var、let声明后未赋值即为Undefined类型,只有一个值undefined
该值的意义主要是用来区分空对象指针null未初始化变量undefined

null标识尚未存在的对象,常用来表示函数企图返回一个不存在的对象,注意函数没有返回值时,默认返回undefined

typeof不能区别未初始化变量和未定义变量,这两者都无法执行实际的操作,但是这两者是不一样的

let m;
//let n;
console.log(typeof m);//undefined
console.log(typeof n);//undefined
console.log(m);//undefined
console.log(n);//报错

声明-定义,赋值-初始化,建议在声明时进行初始化

2.1 Null类型

只有单值null
不建议显式赋值undefined,但是在定义将来要保存对象值的变量时建议用null初始化

null标识尚未存在的对象,常用来表示函数企图返回一个不存在的对象,注意函数没有返回值时,默认返回undefined

null还可以做一个简单的对象使用,比{ }更简单
{ }是一个不完全空对象,原型链上有Object,null为原型链顶端,是完全空对象,原型链也没有。

选择渲染DOM,DOM对象:null
与bind搭配:

/**
 学习笔记:
 bind()的另一个最简单的用法是使一个函数拥有预设的初始参数。只要将这些参数(如果有的话)作为bind()的参数写在this后面。
 当绑定函数被调用时,这些参数会被插入到目标函数的参数列表的开始位置,传递给绑定函数的参数会跟在它们后面。
 */
 
function addArguments(arg1, arg2) {
    return arg1 + arg2
}
 
var addThirtySeven = addArguments.bind(null, 37);
 
var result = addThirtySeven(5);
 
console.log(result);
 
result = addThirtySeven(5, 10);  // 注意这个,其实输出42,第二个10被丢弃了
 
console.log(result);

是为了让第一个参数强制传递指定的参数,如session信息。

bind返回一个原函数的拷贝,并拥有指定的 this 值和初始参数。
如果 bind 函数的参数列表为空,或者thisArg是null或undefined,执行作用域的 this 将被视为新函数的 thisArg。

在实际的应用场景中,做功能扩展时候需要上述:不改变原函数的代码又可以复用、扩展

2.2 Boolean类型

Boolean(其他类型的变量);//返回布尔

{ }是一个不完全空对象,原型链上有Object,null为原型链顶端,是完全空对象,原型链也没有。
[]原型链上还比{}多一个Array

在转化布尔值时,空字符串、0、undefined、null、NaN这类基础类型转为false, 其他的 [] {}等衍生类型都会被转为true.

// 结果都是false
console.log(false ? 'true' : 'false');
console.log(0 ? 'true' : 'false');
console.log(NaN ? 'true' : 'false');
console.log('' ? 'true' : 'false');
console.log(null ? 'true' : 'false');
console.log(undefined ? 'true' : 'false');
// 结果都是true
console.log('0' ? 'true' : 'false');
console.log('false' ? 'true' : 'false');
console.log([] ? 'true' : 'false');
console.log({} ? 'true' : 'false');
console.log(([]==false?true:false)); // true
console.log((false == null)?true:false) // false???
console.log(({}==false)?true:false) // false

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

ps相等运算符

严格相等运算符:
不同类型返回false
对象——仅在指同一对象时返回true
相等运算符(==和!=):
比较目标类型相同的时候,和严格相等一样
比较目标类型不相同的时候:会尝试强制类型转换并且比较不同类型的操作数

null,另一个操作数是undefined,则返回true
如果操作数之一是Boolean,则将布尔操作数转换为1或0
如果操作数之一是对象,另一个是数字或字符串,会尝试使用对象的Number()和toString()方法将对象转换为原始值?
当数字与字符串进行比较时,会尝试将字符串转换为数字值

console.log(Number(null)); // 0
console.log(Number([])); // 0
console.log(Number({})); // NaN

console.log(valueOf(null)); //  Uncaught TypeError: Cannot convert undefined or null to object
console.log(valueOf([])); // Uncaught TypeError: Cannot convert undefined or null to object
console.log(valueOf({})); // Uncaught TypeError: Cannot convert undefined or null to object

在这里插入图片描述
在这里插入图片描述

ps Object.is

在比较上的粒度等同于=== ,且对一些情况进行了修正如下。

console.log([]===[]);//false
console.log(Object.is([],[]));//false
console.log({}==={});//false
console.log(Object.is({},{}));//false
console.log(-0===0&&0===+0);;//true
console.log(Object.is(+0,0));//true
console.log(Object.is(+0,-0));//false
console.log(Object.is(-0,0));//false
console.log(NaN===NaN);//false
console.log(isNaN(NaN));//true
console.log(Object.is(NaN,NaN));//true

2.3 Number类型

typeof NaN是’number‘

八进制0开头
十六进制0x开头
js保存数据的范围在 Number.MIN_VALUE——Number.MAX_VALUE,超过这个范围就是+Infinity或者-Infinity
用isFinite(数值)返回布尔值,是否有限
NaN
isNaN();是否不能转化为数字

NaN===NaN;//false
isNaN("10");//false
isNaN(“blue");//true
Number.isNaN(true);//false可以转换为1

2.3.0 数值转换

Number();//适用于所有

=》0
null、""、[]

=》NaN
undefined、非进制数表达字符串、{}

Number类型提供了将数值格式化为字符串的方法

let num=10;
console.log(num.toFixed(2));//10.00

parseInt();//从第一个非空字符串开始,顺序检测+—数值、至非数值字符,没有数值字符=>NaN;可以指定底数(进制)
parseFloat();

p.s: JavaScript 小数

toFixed
parseFloat
parseInt
JS在比较数字的时候先转换,不要直接比较。因为var 、let是弱的类型,默认是string,而js中字符串的比较是从左往右的,5比10大,因为编译器最先判断的是首位字符,5比1大,所以数字如果要判断大小要注意类型转换。
toFixed()方法是保留小数点的方法,用在数值类型,返回的是字符串类型
Number()方法不仅返回整型数值,还可以返回浮点型,推荐使用。
在这里插入图片描述
在这里插入图片描述

由于浮点数精度的问题,那么我们将两个比较的数相减,定义一个误差值,如果在这个范围内,表示两个数相等

// 整数如下:
if(parseInt(xxx)>parseInt(xxxx)){

}

// 小数可以这样:
if(parseFloat(xxx)>parseFloat(xxxx)){

}

2.3.1 进制转换

有时间整理一下

2.3.2 取整

注意JavaScript的除法不会默认取整,提供了丰富的方法来取整

//取整数部分 
console.log(parseInt(5.57));//5
console.log(parseInt(5.3));//5
console.log(parseInt(-5.57));//-5
console.log(parseInt(-5.3));//-5
//整数四舍五入,再拼接符号
console.log(Math.round(5.57));//6
console.log(Math.round(5.3));//5
console.log(Math.round(-5.57));//-6
console.log(Math.round(-5.3));//-5
//大于等于自己的第一个整数
console.log(Math.ceil(5.57));//6
console.log(Math.ceil(5.3));//6
console.log(Math.ceil(-5.57));//-5
console.log(Math.ceil(-5.3));//-5
console.log(Math.ceil(5));//5
//小于等于自己的第一个整数
console.log(Math.floor(5.57));//5
console.log(Math.floor(5.3));//5
console.log(Math.floor(-5.57));//-6
console.log(Math.floor(-5.3));//-6
console.log(Math.floor(-5));//-5

parseInt也支持解析string

+拼接or加法?

运算符后面要有一个操作数
在这里插入图片描述

console.log(+'2');// 2
console.log(1+ +'2'+'2');//32
console.log(+'2'+2); //4
console.log(1+'2');//12
console.log('2'+1); //21

console.log(typeof(+'2')); // number
console.log(typeof(1+  )); // number
console.log(typeof(1+ +'2')); // number
console.log(typeof(1+ +'2'+'2'));// string
console.log(typeof(+'2'+2)); // number
console.log(typeof(1+'2')); // string
console.log(typeof('2'+1)); // string

+'2’是执行的第一个运算,+
两个操作值中只要有一个是string,执行的就是拼接操作,但是单独一个 + string会将其转换为数字

console.log(+'sad'); //  NaN
console.log(typeof(+'sad')); //  number

浮点数 parseFloat

在这里插入图片描述

// 返回3.14
parseFloat(3.14);
parseFloat('3.14');
parseFloat('  3.14  ');
parseFloat('314e-2');
parseFloat('0.0314E+2');
parseFloat('3.14some non-digit characters');
parseFloat({ toString: function() { return "3.14" } });

// 返回NaN
parseFloat("FF2"); 

2.4 Symbol类型(ES6新增)

Symbol MDN

根据规范,对象的属性键只能是 String 类型或者 Symbol 类型。

符号是原始值,且符号实例是唯一的、不可变的,符号用作属性没有字面量语法
创建symbol()实例并将其用作对象的新属性,就可以保证它不会覆盖已有的对象属性(无论是符号属性还是字符串属性)
Symbol()不能与new关键字一起作为构造函数来使用(避免创建符号包装对象,)。

Symbol为键的弊端:
for…in 迭代、Object.getOwnPropertyNames() 不会返回 symbol 对象的属性,但是 Object.getOwnPropertySymbols() 可以。
当使用 JSON.stringify() 时,以 symbol 值作为键的属性会被完全忽略

2.4.0 全局符号注册表

运行时不同部分需要共享和重用符号实例。
Symbol.for(‘name’); // 全局符号注册表中如果有返回如果没有创建
Symbol.keyFor(‘name’); // 获取全局symbol 注册表中与某个 symbol 关联的键

let globalSym = Symbol.for("foo");
console.log(Symbol.keyFor(globalSym)); // "foo"
console.log(Symbol.for("foo")); // Symbol("foo")

在这里插入图片描述

2.4.1常用Symbol

暴露语言内部行为,可以直接访问、重写、模拟
实质就是全局函数Symbol的字符串属性
语法@@

Symbol.iterator
Symbol.iterator
//等同于
@@iterator

一个对象的 Symbol.iterator 为对象定义了默认的迭代器,该迭代器被 for…of 调用,达到可迭代的效果。
内置类型Array、TypedArray、String、Map、Set 的原型对象上拥有Symbol.iterator,所以可迭代
其他类型(如 Object)则没有,我们可以这样自己定义:

var myIterable = {}
myIterable[Symbol.iterator] = function* () {
    yield 1;
    yield 2;
    yield 3;
};
[...myIterable] // [1, 2, 3]

详见:javaScript的迭代模式:迭代器与生成器

Symbol.hasInstance函数

用于判断某对象是否为某构造器的实例。 因此你可以用它自定义instanceof操作符在某个类上的行为。

这个属性定义在Function的原型上,因此默认在所有的函数和类上都能调用。类的本质:立即执行函数,JavaScript是通过函数去自扩展出块作用域的概念的

let obj_1 = {

}
let obj_2 = {
  [Symbol.hasInstance](){
    console.log('me');
    return 0;
  }
}
console.log(obj_1 instanceof obj_2)//false,即[Symbol.hasInstance]的返回值转换为布尔值
console.log(Symbol.hasInstance);//Symbol(Symbol.hasInstance)


let f=new Foo();
console.log(f instanceof Foo);
console.log(Foo[Symbol.hasInstance](f));

自定义 MyArray 的 instanceof 行为:

class MyArray {  
  static [Symbol.hasInstance](instance) {
    return Array.isArray(instance);
  }
}
console.log([] instanceof MyArray); // true

2.5 String

字符串是原始值,不涉及引用,所以对string进行修改的函数都是返回一个新的字符串,不会影响源字符串。
但是有些方法,数组使用就要注意引用问题,可能会改变原数组
专属于字符串操作的有 split, substring, substr
string和Array都有slice, concat

涉及切分:
split(字符串)、slice深, concat深(字符串/数组)、splice(数组)

split()

split()根据string、分隔符生成一个新数组,原字符串不变
在这里插入图片描述

注意split()函数转换成字符数组还是字符串数组

var str="How are you doing today?"
str.split(" ");//[How,are,you,doing,today?]
str.split("");//[H,o,w, ,a,r,e, ,y,o,u, ,d,o,i,n,g, ,t,o,d,a,y,?]
str.split(" ",3));//[How,are,you]
"2:3:4:5".split(":") //将返回["2", "3", "4", "5"]
"|a|b|c".split("|") //将返回["", "a", "b", "c"]

slice、substring、substr 提取子串

substr不提倡,缺乏规范

let s="hello world"
//单参
console.log(s.slice(3));//lo world
console.log(s.substring(3));
console.log(s.substr(3));
//两
console.log(s.slice(3,7));//lo w  3-6
console.log(s.substring(3,7));
console.log(s.substr(3,7));lo worl
//负参处理
console.log(s.slice(-3));//11-3=8 rld
console.log(s.substr(-3));//rld
console.log(s.substring(-3));//所有负参=0 hello world

定位

let mess="abcd";
console.log(mess.charAt(2); // c
console.log(mess.charCodeAt(2)); // 99
console.log(String.fromCharCode(0x63,0x64)); // c,d

两个方法方向不一样
也可以用于数组

indexOf('单字符’,指定查找起始位);//第二个参数缺省时,默认0
lastIndexOf('单‘,始位);

包含关系

包含
返回布尔值

第二个参数缺省时,只在开头/结尾匹配一次;
有第二个参数,就从指定位开始多次匹配

startsWith("字符串“,指定匹配起始位);
endsWith("字符串“,指定匹配起始位);

第二个参数缺省时,全局匹配;
有第二个参数,就从指定位开始一次匹配

includes("字符串“,指定匹配起始位);

其他

trim();返回一个除掉开头和结尾空格的string副本
repeat(次数);返回重复拼接字符串
padStart(副本长度,“补足位符号串”);//默认补空格 实现右对齐
padEnd();
function trim(str){
    if(str && typeof str === 'string'){
        return str.replace(/^\s+|\s+$/g,'');
    }
}

trim的替代:正则
^\s+匹配开头的多个空白
\s+$ 匹配结尾的多个空白
在这里插入图片描述

模板字面量

使用String.raw() 方法创建原始字符串
模板字面量

字面量:直接出现在程序中的数据值,包含整数字面量1、浮点字面量、字符串字面量等

模板字面量:允许嵌入表达式的字符串字面量,使用反引号 () 来代替普通字符串中的用双引号和单引号来定义字符串

字符串插值:模板字符串可以包含特定语法(${expression})的占位符。占位符中的表达式和周围的文本会一起传递给一个默认函数,该函数负责将所有的部分连接起来。

${JavaScript表达式}

arr[`part_key${i}`];//访问part_key*的属性

标签函数
如果一个模板字符串由表达式开头,则该字符串被称为带标签的模板字符串,该表达式通常是一个函数,它会在模板字符串处理后被调用,在输出最终结果前,你都可以通过该函数来对模板字符串进行操作处理。

在模版字符串内使用反引号(`)时,需要在它前面加转义符(\)。

var person = 'Mike';
var age = 28;

function myTag(strings, personExp, ageExp) {

  var str0 = strings[0]; // "that "
  var str1 = strings[1]; // " is a "

  // There is technically a string after
  // the final expression (in our example),
  // but it is empty (""), so disregard.
  // var str2 = strings[2];

  var ageStr;
  if (ageExp > 99){
    ageStr = 'centenarian';
  } else {
    ageStr = 'youngster';
  }

  return str0 + personExp + str1 + ageStr;

}

var output = myTag`that ${ person } is a ${ age }`;

console.log(output);
// that Mike is a youngster

that ${ person } is a ${ age }调用标签函数,第一个参数是一个数组,对应模板中的字符串。变量对应形参

注意 :如果标签函数传参首尾有变量会产生空元素,如下

myTag` ${ person } is a ${ age }`;

原理:标签函数拆分传参的方式是

args.split(/\$\{.*?\}/)

'|b|c'.split('|'); // [ ' ', 'b' , 'c' ]

动态构建字符串时使用模板字符串而不是拼接

// bad
function sayHi(name) {
  return 'How are you, ' + name + '?';
}

// good
function sayHi(name) {
  return `How are you, ${name}?`;
}

基本引用类型

Object类型

每个Object实例都存在如下属性和方法(object是所有对象的基类,所以所有对象都有)。属性名必须为String或者Symbol

constructor:指向构造函数
hasOwnProperty(propertyName):用于判断当前实例上是否存在给定的属性值
isPrototypeOf(object):判断当前对象是否是另一对象的原型
propertyIsEnumerable(propertyName):判断给定的属性可不可以使用for-in枚举
toLocaleString()返回对象的字符串表示,该字符串反应对象所在的本地化执行环境。
toString():返回对象的字符串表示。
valueOf()返回对象对应的字符串、数值或布尔值

ES5之后有一个内建方法Object.keys(对象),返回一个对象所含属性的数组。由此得以得出对象的属性的个数等

只对非法标识符的属性使用引号
在这里插入图片描述

优先使用对象展开运算符(深拷贝)而不是对象浅拷贝Object.assign

o={...obj}; // 只会复制obj自身属性,不复制原型
Object.defineProperty()
Object.defineProperty(obj, 'key', descriptor可选);

方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
注:应当直接在 Object 构造器对象上调用此方法,而不是在任意一个 Object 类型的实例上调用:

var o = {}; // 创建一个新对象

// 在对象中添加一个属性与数据描述符的示例
Object.defineProperty(o, "a", {
  value : 37,
  writable : true,
  enumerable : true,
  configurable : true
});

对象的可扩展性

**Object.preventExtensions()**方法让一个对象变的不可扩展,也就是永远不能再添加新的属性
**Object.seal()**方法封闭一个对象,阻止添加新属性并将所有现有属性标记为不可配置。当前属性的值只要原来是可写的就可以改变
Object.freeze() 方法可以冻结一个对象。一个被冻结的对象再也不能被修改、不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值。此外,冻结一个对象后该对象的原型也不能被修改。

三个方法均返回和传入的参数相同的对象

RegExp

JavaScript正则表达式

Array

会返回新的数组:slice, concat
在原数组上修改:splice

JavaScript中的数组只是特殊的对象,效率不如其他语言的数组。数字索引在内部被转换为字符串类型(因为之前JavaScript中属性名必须是string)
前文中我们提到数组是一个对象,所以对于数组的方法我们需要注意区分一个深浅

其他

isArray(); // 判断一个对象是否是数组的
array.sort对string[]排序时候按照ASCII码来。所以大写排在小写前面
在这里插入图片描述
在这里插入图片描述

Array()、 Array.from浅、Array.of

of();// 一组参数=>数组实例

		let a=new Array(1,2,3,"dd");[1,2,3,dd]
        let b=new Array(5);//[ , , , ,]稀疏数组
        let d=Array.of(5);//[5]
        let c=Array.from(5);//数组长度为0,不会这么用,参数一般为可迭代对象
		a[9]=10;
        console.log(a);//[1, 2, 3, "dd", 空 ×5, 10]稀疏数组

from();// 类数组结构=>数组实例

所谓类数组对象,最基本的要求就是具有length属性的对象。
涉及复制拷贝问题上,注意深浅拷贝的本质。可以看到,方法拿到的就是引用,浅复制

//类数组对象
let arrayLike = {
    0: 'tom', 
    1: '65',
    2: '男',
    3: ['jane','john','Mary'],
    'length': 4
}
let arr = Array.from(arrayLike)
console.log(arr) // ['tom','65','男',['jane','john','Mary']]
arr[3][1]="hhh";
console.log(arr) // ['tom','65','男',['jane','hhh','Mary']]
console.log(arrayLike) // ['tom','65','男',['jane','hhh','Mary']]
//set
let arr = [12,45,97,9797,564,134,45642]
let set = new Set(arr)
console.log(Array.from(set))  // [ 12, 45, 97, 9797, 564, 134, 45642 ]
//map
const m=new Map().set(12.set(3,4);
console.log(Array.from(m)) ;//[[1,2],[3,4]]                 

Array查找includes、find

找到匹配值后就停止查找

arr.includes();
alert(people.find((element,index,array)=>element.age<27))//name:"marry",age:26
alert(people.findIndex((element,index,array)=>element.age<27))//0

Array迭代器keys、values、entries

支持迭代语法,例如for-of
数组和类数组对象、Map有

keys();
values();
entries();
obj[ Symbol.iterator ]();
var obj = { foo: 'bar', baz : 42};
Object.keys(obj)
//  ["foo", "baz"]

Array迭代方法forEach、map…

//无返回值
forEach();
//返回一个布尔值
every();//&&
some();//||
//返回执行后为true的数组真子集
filter();
//返回结果组成的数组
map();

flat 深

flat( depth可选 ) 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组,会移除数组中的空项,返回

depth,整数,拉平的层数,默认为1; Infinity关键字作为参数,如果不管有多少层嵌套,都转成一维数组。
flatMap()方法对原数组的每个成员执行传入的函数参数,即Array.prototype.map(), 然后对返回值组成的数组执行flat()方法。

let arr = [1, 2, [3, [4]]];
// 展开一层数组
console.log(arr.flat());

展开一层

arr.reduce((acc, val) => acc.concat(val), []);
// [1, 2, 3, 4]

// 使用扩展运算符 ...
const flattened = arr => [].concat(...arr);

展开很多层

var arr1 = [1,2,3,[1,2,3,4, [2,3,4]]];

function flatDeep(arr, d = 1) {
   return d > 0 ? arr.reduce((acc, val) => acc.concat(Array.isArray(val) ? flatDeep(val, d - 1) : val), [])
                : arr.slice();
};

flatDeep(arr1, Infinity);
// [1, 2, 3, 1, 2, 3, 4, 2, 3, 4]

Array归并方法reduce

reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。可以作为一个高阶函数,用于函数的 compose。
该函数内部前一个参数是一个箭头函数,(此时箭头函数的参数即reduce的默认参数列表如下),后一个是函数起始值(一般不赋值,取空来限定数据类型),作为第一次reduce的total,箭头函数的返回值作为下一次reduce的total

array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
reduceRight();

使用reduce方法可以写出map、foreach等迭代方法

返回一个立即执行的reduce函数

 const map = function map (arr, fn) {
     return arr.reduce(
        (lastReturn,ele)=>{
            return [...lastReturn,fn(ele)]
        },
        []
    )
    }
    const arr = [2,4,5,6]
    console.log(map(arr, n => n * 2))
const mforEach = function (arr, fn) {
      return arr.reduce(
       (lastReturn, ele) => {
         fn(ele) 
        },
          []
     )
    }
    const arr = [2,4,5,6]
    console.log(mforEach(arr,n => console.log(n)))
    console.log(arr.forEach(n => console.log(n)))

数组解构

表达式是惰性求值,只有在用到才会求值
解构中,在等式左边使用表达式给数组元素赋默认值,只有当右边没有该元素的值的时候才会执行表达式。

let[a,b=3]=[1]; // b=3
let[a,b=fun()]=[1,2];  // b=2,fun不执行

让返回数组的函数变得敏捷

let arr=['hhh','uuu','jjj'];
let [a,b,c]=arr;
console.log(a,b,c);//hhh
let[x,...y]=arr;//y==['uuu','jjj']
let [s,[d,f]]=[1,[2,3],4];//d==2,f==3
let [first,...end]="hello";//end==[e,l,l,o]
let m=new Map().set("one",1).set("two",1).set("o",1);//迭代Map返回数组
for(let [k,v] of m){console.log(k,v);}

解构赋值还可以用于对象
复制内置对象的全局函数,从而简化代码

const {sin,cos,tan,notMath}=Math;
//相当于
const sin=Math.sin;
//notMath==undefined

当左侧出现不是右侧对象属性的变量名时候,undefined
也可采用 冒号匹配属性名

队栈push, pop, shift, unshift 浅

改变原数组的方法有 以下四个+splice

push();
pop();
shift();//删除并返回首项
unshift();

数组可以调用push,push过程中注意括号问题。打印结果中包含数组本身的最外层的括号

let track = [];
let result = [];
track.push(1); //[1]
console.log(track);
result.push([track])//[[[1]]]
console.log(result);
track.push(2);//[1,2]
console.log(track); 
result.push([...track])//...去括号[[1,2]]
console.log(result);
result.push(track)//[[1,2]]
console.log(result);

排序 reverse、sort 浅

reverse();
sort(可省的比较函数);
默认的排序函数(升序)

function comp(v1,v2){
	if(v1<v2){
	return -1;
	}else if(v1>v2){
	return 1;
	}else{
	return 0;
	}
}

//或者直接用箭头函数
myArray.sort((v1,v2)=>v1<v2?1:a>b?-1:0);
或者((a, b) => a - b);

切割concat(str、Array)、splice(Array)

let n=[1,2];
n.concat([2,2]);//不影响原数组
console.log(n);//[1,2,2,2]
n.splice(5,3);//删除三位:5、6、7
n.splice(5,0,2,3,4);//插入三位:5、6、7
n.splice(5,3,2,3,4);//替换三位:5、6、7
n.slice(start,end)//不影响原数组start~end-1

注意split(字符串)、slice, concat(字符串/数组)、splice(数组)

fill()浅

使用自己想要的参数替换原数组内容,会改变原来的数组。

该方法有三个参数:

fill(value, start, end)
value:想要替换的内容。
start:开始位置(数组的下标),可以省略。
end:替换结束位置(数组的下标),如果省略不写就默认为数组结束。
fill()方法创建二维数组
let arr=Array(3).fill(0).map(x => Array(3).fill(0));
let brr=Array(3).fill(Array(3).fill(0));
console.log(arr) //[[0,0,0],[0,0,0],[0,0,0]]
console.log(brr) //[[0,0,0],[0,0,0],[0,0,0]]

arr[0][0] = 1;
brr[0][0] = 1;
console.log(arr)  //[[1,0,0],[0,0,0],[0,0,0]]
console.log(brr)  //[[1,0,0],[1,0,0],[1,0,0]]

伪数组

函数内部的argument
DOM对象列表

伪数组调用数组的方法

let fakeArray = {
    0:'a',
    1:'b',
    2:'c',
    length:3,
}
console.log(fakeArray[2]);
[].push.call(fakeArray,"d");
console.log(fakeArray);
fakeArray[4]='e';
console.log(fakeArray);

在这里插入图片描述

Map

object只能采用数值、字符串、符号为键,而map可以采用JavaScript所有类型为键,map内部采用sameValueZero比较(类似===)来匹配键
相同数据量,map占的存储单元更小
map删除更安全,object delete饱受诟病

set(key,value);
get(key);//返回value
has(key);//返回布尔值
delete(key); // 删除对象中的属性
clear();
entries();
keys();
values();
let m=new Map().set("one",1).set("two",1).set("o",1);
// 等同于用二维数组初始化
let m=new Map([["one",1],["two",1],["o",1]]);

不是伪数组,怎么遍历map?
Array.from、…扩展符可以把map变成二维数组
在这里插入图片描述

let a=new Map();
a.set(1,1);
a.set(2,1);
a.set(3,1);
console.log(a);
a.forEach((ele)=>console.log(ele)); // 1,1,1可以迭代访问到value

Set

add();
has();
delete();
clear();
keys();
values();

弱映射、弱集合看另一篇博客

操作符

注意实际代码中多次使用操作符的左、右结合性问题。最好加圆括号明确求值顺序。

数字操作

按位非~

位操作是在数值底层完成的,速度会快很多
~n=—n—1
按位与&、或|、异或^

左移<<

有符号右移(逆左)>>

保留符号位的右移

无符号右移 >>>

高位补零

指数操作符**

Math.pow有了自己的操作符
3**2=9

指数赋值操作符**=

let n=3;
n**=2;
console.log(n);//9

乘性操作符*

NaN*/任意=NaN
Infinity0=NaN
Infinity
非零=正负Infinity
Infinity/Infinity=NaN
0/0=NaN
非0的有限值/0=正负Infinity
无限值%有限值=NaN
有限值%无限值=有限值
如果操作数非数值,则先在后台用Number将其转换为数值

布尔操作符

逻辑非!

当操作数是空字符串、0、null、NaN、undefined时取true

!!相当于Boolean()把任意值转换为布尔值

解构操作符…

字符串=>字符数组

let m="asd';
console.log([...m]);//["a","s',”d"]

&&与||

当数值参与逻辑与运算时,结果为true,那么会返回的会是第二个为真的值;如果结果为false,返回的会是第一个为假的值。

console.log(2 && 3 );//3
console.log(1 || 2 );//1

??缺值合并

ES2020

		a??b;
        //等同于
        (a!==null&&a!==undefined)?a:b;

?

book?.author
book&&book.author

delete操作符

删除对象的属性,不能删除变量(可以删除隐式全局变量)

全局变量 x
隐式全局变量 x2

var x =6;
x2=7;
delete x;
console.log(typeof x);
console.log(x);

delete x2;
console.log(typeof x2);
console.log(x2);

在这里插入图片描述

void操作符

void是一个操作符,该操作符指定要计算一个表达式但是返回值永远是undefined。调用方法同时防止页面刷新

新浪微博,淘宝等大站的首页JS操作的href都是javascript:void(0);呢?既保证了返回值是undefined,又保证如果连接点击需要处理一些代码,随时将0替换掉就可以。

// 阻止链接跳转,URL不会有任何变化
<a href="javascript:void(0)" rel="nofollow ugc">点击此处</a>

// 虽然阻止了链接跳转,但URL尾部会多个#,改变了当前URL。(# 主要用于配合 location.hash)
<a href="#" rel="nofollow ugc">点击此处</a>

// 同理,# 可以的话,? 也能达到阻止页面跳转的效果,但也相同的改变了URL。(? 主要用于配合 location.search)
<a href="?" rel="nofollow ugc">点击此处</a>

// Chrome 中即使 javascript:0; 也没变化,firefox中会变成一个字符串0
<a href="javascript:0" rel="nofollow ugc">点击此处</a>

语句

迭代语句

for-in枚举对象的非符号键属性

for(const propName in window){
	document.write(propName);
}//循环展示了BOM对象window的所有属性

for-of语句迭代对象的元素

若对象不支持迭代则报错
不同于forEach,它可以与continue、break、return配合使用

const products = ['oranges', 'apples'];

for (const product of products) {
  console.log(product);
}
// 'oranges'
// 'apples'

标签语句

与break、continue一起

with语句

针对一个对象反复操作,严格模式不允许
let qs=location.search.substring(1);
let host=location.hostname;
//可替换为
with(location){
let qs=search.substring(1);
let host=hostname;
}

switch语句

比较条件用的是===全等,不进行类型转换。

eval()

一个危险的函数, 拥有与调用者相同的权限执行代码。再者第三方代码可以看到某一个 eval() 被调用时的作用域

eval() 通常比其他替代方法更慢,因为它必须调用 JS 解释器。现代JavaScript解释器将javascript转换为机器代码。 这意味着任何变量命名的概念都会被删除。 因此,任意一个eval的使用都会强制浏览器进行冗长的变量名称查找,以确定变量在机器代码中的位置并设置其值。 另外,新内容将会通过 eval() 引进给变量, 比如更改该变量的类型,因此会强制浏览器重新执行所有已经生成的机器代码以进行补偿。

一个非常好的eval替代方法:只需使用 window.Function

断言assert

断言是 调试技巧, 在大多编程语言中都支持断言这种机制来调式程序,JavaScript 借助浏览器提供的 console 方法 来实现的。

提前预判 程序的结果,如果之后执行的程序不符合我们的预判,那么则报错,例如下面这个例子。

function f(n) {
 console.assert(n !== 0, '零不能作为被除数') // 这里就是断言 | 提前预判
 return 10 / n
}

console.log(f(1)) // 10
console.log(f(2)) // 5
console.log(f(0)) // 报错 | Assertion failed: 零不能作为被除数

在这里插入图片描述

规范

函数

使用具名函数表达式而非函数声明,命名表达式-匿名函数很难在Error调用栈中定位问题.

Error.captureStackTrace

// bad
function foo() {
  // ...
}
// bad
const foo = function () {
  // ...
};
// good
const foo = function bar() {
  // ...
};

不使用Function构造函数创建函数。原因:此方式创建函数和对字符串使用eval()一样会产生漏洞.

eval()把对应的字符串解析成JavaScript代码并执行(执行两次:解析+实际执行)

// bad
var add = new Function('a', 'b', 'return a + b');
// still bad
var subtract = Function('a', 'b', 'return a - b');

使用 extends 继承,原因:是实现继承功能而不会破坏instanceof的内置方法.
方法可以返回this来进行链式调用.

模块化

在非标准模块系统的基础上使用模块 (import/export)

// bad
const AirbnbStyleGuide = require('./AirbnbStyleGuide');
module.exports = AirbnbStyleGuide.es6;

// ok
import AirbnbStyleGuide from './AirbnbStyleGuide';
export default AirbnbStyleGuide.es6;

// best{成员}
import { es6 } from './AirbnbStyleGuide';
export default es6;

不要export可变的绑定值,原因:通常来说可变应该被避免

// bad
let foo = 3;
export { foo }

// good
const foo = 3;
export { foo }

单一export的模块中,建议export default而不是具名export.

在模块import声明中禁止Webpack的加载器语法
原因:在import中使用Webpack语法会导致代码耦合到模块绑定中。建议在webpack.config.js中使用加载器语法.

// bad
import fooSass from 'css!sass!foo.scss';
import barCss from 'style!css!bar.css';
// good
import fooSass from 'foo.scss';
import barCss from 'bar.css';

语法补充

function a(){
  return {
    id:1
  }
}

function b(){
  return 
  {
    id:1
  }
}
console.log(a());
console.log(b());// undefined

分号在JavaScript里是一个可选项

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值