JS学习笔记整理三 引用类型

259 阅读14分钟

引用类型

Object类型

通过对象字面量创建对象时,实际上不会调用Object构造函数。因此,通过这种方式更快速更直观。

对象字面量比较适合用于传参,利用typeof来检查对象传入的每一个属性是否存在。

对象的属性可以用点表示法和方括号表示法。

方括号表示法可以利用变量访问属性。同时属性可名可以包括非字母和非数字,这种情况只能用方括号形式访问。在定义时这种情况的属性名要用引号。

Array类型

不要这么声明数组:

var a=[,,,,];或者var a=[1,2,];会出bug

数组的length属性不是只读的,我们可以设置长度,length加长时,未初始化的值都命名为undefined,变短时末尾的部分被移除。

检测数组。

typeof

这个地方还是说明一下,typeof能检测以下几种类型:

undefined,string,number,boolean,object,function es6新增了symbol. 宿主对象

但是对于数组,typeof操作符就没办法。array类型和null类型最后都会返回object。

typeof的一些特殊行为

typeof 1/0; //NaN(这个NaN不是字符串类型,是数值类型)
typeof typeof 1/0; //NaN(这个NaN不是字符串类型,是数值类型)
typeof(1/0); //"number"
typeof typeof(1/0); //"string"
typeof (typeof 1/0); //"number"

instanceof

对于instanceof操作符。原理是查找被检测构造函数的prototype是否在现在对象的原型链上。如果是返回true,否则false。因此,instanceof可以检测判断自定义对象。但是在多个全局作用域的情况中,instanceof无法检测另一个全局作用域的对象类型。而且,浏览器开始支持原生JSON。现在很难用instanceof来判断是原生还是自定义了。

Object.prototype.toString.call(value)

使用Object.prototype.toString.call(value);可以返回[object nativeConstructorName]格式字符串。通过这种方式,我们能检测出原生构造函数名。比如Array和正则等。但是自定义的不行,只会返回[object object]。

之所以使用Object.prototype.toString是因为其他类型继承Object后把方法都重写了。

(红皮书p88、p596)

Array.isArray(value)

es5新增了Array.isArray()方法,用来最终确定某个值到底是不是数组,而不管它是在那个全局执行环境中创建的。

转换方法。

数组的toString其实是调用的每一项元素的toString(),toLocaleString调用的是每一项元素的toLocaleString()。

栈方法

后进先出LIFO

pushpop。push可以一次性传入多个数组元素,最后返回添加后的数组长度。pop则推出最后一个数组元素并返回值。

队列方法

先进先出FIFO

使用pushshift

重新排序方法

reverse()反转数组 sort()排序

var a=[13,24,1,64,27];
console.log(a.sort((v1,v2)=>v1-v2));

数组的操作方法

concatslice 原数组不变,返回新数组

splice则可以添加替换删除数组内元素,算是最强大的数组方法。

var a2=a1.spalice(起始位置,删除项数,插入项1,插入项2...);

位置方法

indexOflastIndexOf

迭代方法

everyfilterforEachmapsome

它们都是对数组运行一个给定的函数

every是当函数对每一项都返回true,则返回true。比如对dom数组进行遍历,当满足某一条件返回false。终止接下来的遍历。

some是当函数对其中一项返回true,则返回true

filter是返回符合条件为true的元素组成的数组

map是返回运算后元素组成的数组

forEach没有返回值。

array.forEach(function(currentValue, index, arr), thisValue);

需要注意的是这些遍历方法的第二个参数。如果有第二个参数,则将其作为this值指定的对象传入到回调函数内。之前讲过匿名函数this是指向全局的,这种相当于改变了作用域链。

归并方法

reducereduceRight这两个函数非常好用,

reduce进阶:转载:https://segmentfault.com/a/1190000010731933

红皮书p98,犀牛书158

reduce有两个参数,第一个参数是要执行并归计算的函数,此函数包含四个参数:初始值、当前元素、索引(可选)、数组(可选)。第二个可选是执行操作的初始值。如果不指定第二个参数,则第一次并归计算将使用数组第一个和第二个元素作为函数的两个参数传入。

补充一个unshift,红皮书里没找到。在数组的头部插入元素。但记住,一次性插入参数和一个一个插入,顺序是不一样的。

Date类型

玩游戏的时候会经常遇到通知发售日或维护时间。有时以UTC有时用GMT,还有CST、EST、JST、HKT

说到时区,忍不住百度了下中学地理2333。地球公转和自转有23度左右夹角。因为角动量守恒,地轴方向基本是不变的。地球自转和公转方向都是自西向东。所以太阳总从东边出来。

位于东边的地区总是先迎接太阳。

GMT叫格林威治时间, UTC叫协调标准时

我们使用东八区的北京时间。因此,我们的本地时间应该是标准时UTC加8小时。

北京时间=UTC+8=GMT+8。

使用new Date()创建日期对象

如果不给参数,直接获得当前时间。如果需要参数,则需传入表示该日期的毫秒数。如果传入字符串,则会先调用Date.parse()解析成毫秒数。也可以模仿Date.UTC格式参数,但是会把参数当成本地时间解析。无法解析返回Invalid Date。

Now=new Date();

直接输出Now和Now.toString()内容是不一样的。直接输出是标准时间,toString输出本地时间。Date.parse接受的字符串如果不特意注明时区,是按照本地时间解析的。而Date.UTC则是按照标准时间解析

Mon Mar 18 2019 10:26:18 GMT+0800(GMT+08:00)

Gmt+8是北京时间。一定要注意,上面字符串写法中GMT+0800后面两个零代表分钟数。因此,+0860等同于+0900。

一个倒计时练习。

(function(){
    var targetTime=Date.parse("2019-7-19 11:10:00 gmt+0800");//选一个还没到的日期
    var sub =targetTime- Date.now();
    if(sub>0){
        var min,sec,hour,day,st,intervalId;
        intervalId=setInterval(countDown,1000);
    }
    function countDown(){
        sub = targetTime - Date.now();
        if(sub>0){
            st=sub/1000;
            day=~~(st/86400);
            hour=~~((st-day*86400)/3600);
            min=~~(st/60)%60;
            sec=~~st%60;
            console.log(day+"天"+hour+"时"+min+"分"+sec+"秒");
        }else{
            clearInterval(intervalId);
   }
    }
})();

不要写超出时间范围的值,不同浏览器的解析有差异。

Date.now()表示调用时的时间毫秒数。如果在不支持的旧版本浏览器,可以使用+new Date()来获取时间戳。因为使用加号会调用valueOf。用getTime不够简洁。

RegExp类型

Regular expression 正则表达式其实是一块学起来特别有意思的部分。

表达式格式:/ pattern / flags

pattern:模式,flags:标志。

正则表达式的元字符:(){}[].?=!:/\|^$*+

如果需要匹配这些字符,必须使用反斜\进行转义

可以用汉字“八”来辅助记忆 / 正斜 \ 反斜

元字符和部分字母的转义组成有特殊含义的符号,当然,部分字符只在特定的上下文中才有意义。敲一遍常见的\n,\t,\v,\r,\f

字符类

[...] 方括号内的任意字符

[^...] 不在方括号内的任意字符

. 除换行符和其他Unicode行终止符以外的任意字符(排除\n和\r)

\w 任何ASCII字符组成的单词,等价于[a-zA-Z0-9]

\W 任何非ASCII字符组成的单词,等价于[^a-zA-Z0-9]

\s包含Unicode任意空白符。经测试,\s涵盖\n,\t,\v,\r,\f这几项。

\S 任何非Unicode空白符,注意\w和\S不同

\d 任何ASCII数字,等价于[0-9]

\D 除了ASCII数字之外的任何字符,等价于[^0-9]

\b 可以理解为单词的边界 注意区别[\b]退格符

重复

?={0,1} *={0,} +={1,}

重复分为贪婪的重复和非贪婪的重复。贪婪的重复会尽可能多的匹配直到最大的次数。而非贪婪的匹配,需在匹配内容后加?,只要配上了就返回,而后面的重复可以继续重新匹配。需要注意的是非贪婪匹配可能返回与期望值不一致的结果。比如对于字符串"aaaab"和正则表达式/a+?b/,返回的不是"ab",而是"aaaab"。因为模式匹配总会寻找字符串中第一个匹配的位置。

常见的两种类型模式匹配的语句:

  1. str.match(pattern);

    全局修饰符g时,会返回一个匹配成功后的字符串数组。否则返回首个匹配元素和索引等信息。

  2. pattern.exec(str)

    如果匹配成功,始终返回一个数组

选择、分组和引用

|可以用于分割供选择的字符。不同的项从左到右匹配,如果左侧的匹配了,则会跳过右侧的,即便有更优的选择。

()圆括号可以对括号内的内容当做一个独立的单元处理。另外的作用就是定义子模式,并供后部引用。根据左括号的排序数字如:\2 引用之前匹配到的一样的字符内容,而不是引用一样的模式。 (?: )可以作为不生成引用的分组。

var s = 'aaa_aa_a';
console.log(s.match(/(a+)_\1/));//[ 'aa_aa' ]

AscII 一个字节。可以表示256个字符。

Unicode是两字节,共能表示65536个字符。Ascii其实算是unicode的子集。首字节都是0,则末字节刚好表示ascii的256个编码。

Utf-8是针对Unicode的可变长度字符编码,俗称万国码。通常情况是三个字节。也有六子字节的情况,很少见。

1110xxxx

10xxxxxx

10xxxxxx

关于汉字的匹配:转载:https://www.cnblogs.com/ChengDong/articles/3771880.html 虽然繁简体的汉字加一起六七万个。但是常用的就六七千个。

疑问:如果gb2132的页面,能否通过这种方式判断成功呢?大致查了下,

网上有些人用的是排除法。

指定匹配位置

犀牛书把先行断言lookahead assert(零宽断言)和其他的锚字符一起讲了。因为他们都代表一个零宽的位置。

网上一些教程把断言和()圆括号分组一起讲了。因为他们都是表现为分组的形式。

锚字符整理:

^ 匹配字符串的开头,在多行检索中匹配一行的开头。

$ 匹配字符串的结尾,在多行检索中匹配一行的结尾

\b 匹配一个单词的边界,就是\w和\W之间的位置,或\w与字符串开头或结尾之间的位置。

\B 匹配非单词边界的位置

(?=p) 零宽正向先行断言,要求接下来的字符都和p匹配,但不能包括匹配p的那些字符

(?!p) 零宽负向先行断言,要求接下来的字符不与p匹配

另外,犀牛书里目前只看到两种,叫先行断言。其实还有后行断言。

结合分组写到一起:

() 捕获型分组可以被后面捕获并引用

(?:) 非捕获型分组 不能被捕获

(?= pattern) 正向先行断言

(?! pattern) 负向先行断言

(?<= pattern) 正向后行断言 后行断言要放在判断字符的前面

(?<! pattern) 负向先行断言

犀牛书p253注释说明js不支持后行断言。但是我测试的时候并没有报错,而且返回正确结果了。我用的ide是webstorm,按照nodejs运行的,nodejs就是基于v8的。查了一下,新标准里有后行断言扩展,版本新一点的v8引擎已经支持后行断言。

后向引用(也叫反向引用?),如果引用放到圆括号前面,会发生什么?测试了一下,有的时候能返回想要的内容。但又想了一下,这样没什么意义。在pattern里引用写为\number,程序里引用写为$number

后向引用可以自己定义组名,这样可以增强表达式的可读性。

(?<NAME>pattern)

正则表达式内引用时使用: \k<NAME>,程序里引用写为$<NAME>

红皮书p109说ecmascript不支持命名的捕获组。但是上面的测试通过了,难道又是新版es支持的扩展? 经查,V8 目前已经完全实现了命名捕获分组的提案。

转载:https://tc39.github.io/proposal-regexp-named-groups/

一旦支持了命名的捕获组,是否能够支持平衡组,递归匹配?反复看了看,没有找到依据,javascript相关资料太少,太耽误时间,放弃了。

经测,条件判断表达式不可用。(?(expression)yes|no)红皮书109

犀牛书p253注释说明js不支持注释。。。

(?#注释用格式,对表达式不会有影响) //报错

修饰符flag

g 全局,检索所有匹配。

m多行,除了匹配字符串的开始和结尾,还可以用^匹配一行的开头,$匹配到一行的结尾。

i 忽略大小写

es6新增了修饰符uy

u 含义为“Unicode 模式”,用来正确处理大于\uFFFF的 Unicode 字符。也就是说,会正确处理四个字节的 UTF-16 编码。

y 叫做“粘连”(sticky)修饰符。作用与g修饰符类似,也是全局匹配,后一次匹配都从上一次匹配成功的下一个位置开始。不同之处在于,g修饰符只要剩余位置中存在匹配就可,而y修饰符确保匹配必须从剩余的第一个位置开始,这也就是“粘连”的涵义。

用于模式匹配的String方法

match 返回匹配的数组,非全局匹配返回的数组包含子表达式和分组信息等 search 返回匹配到内容的索引,返回第一个匹配的起始位置,会忽略修饰符g replace 可以使用引用,注意这个方法不会改变str本身。所以,需要赋值表达式把原值销毁替换为新值。

var s = 'aaa_aa_a';
console.log(s.replace(/(a+)_\1/g,'"$1"'));

split

RegExp对象

RegExp属性

regexp实例的常用属性:

sourcelastIndexglobalmultilineignoreCaseflags

RegExp方法

re.exec(str)方法和str.match(pattern)返回的结果很相似。区别在于不管是不是全局,总是返回一个匹配结果构成的数组。当有全局修饰符g的情况下,调用完方法该对象会把自己的lastIndex属性置于匹配字符后。下次调用从lastIndex开始查找匹配。

//犀牛书p265示例
var pattern=/Java/g;
var text="JavaScript is more fun than Java!";
var result;
while((result=pattern.exec(text))!=null){
    console.log("match'"+result[0]+"'at position"+result.index+",next search begins at"+pattern.lastIndex);
    console.log(result);
}
//match'Java'at position0,next search begins at4
//[ 'Java',index: 0,input: 'JavaScript is more fun than Java!',groups: undefined ]

//match'Java'at position28,next search begins at32
//[ 'Java',index: 28,input: 'JavaScript is more fun than Java!',groups: undefined ]

re.test(str)返回值和exec()方法不一样,其他的行为是和exec一样的。

一些情况需要手动设置lastIndex为0,以方便将来的检索。

对于正则表达式字面量,新的v8等等都是返回新实例对象。而不像过去共享同一个对象。

re.toString()和re.toLocaleString()都返回正则字面量,而re.valueOf()返回re对象本身。。。

Regexp构造函数包含一些属性,适用于作用域中所有的正则表达式。按照最后一次正则表达式操作为准。

RegExp.inputRegExp.lastMatchRegExp.lastParenRegExp.multilineRegExp.leftContextRegExp.rightContext

对于匹配的子表达式,使用$1$9储存。在使用exec()或者test()方法时,上面这些属性会被自动填充。

转载一个正则表达式30分钟入门教程:deerchao.net/tutorials/r…

对理解正则表达式很有帮助,但是其中一部分在javascript中还没有支持,有些语法也不尽相同。

Function类型

函数声明、函数表达式、构造函数

定义函数的三种方法,前两种都比较常见:

  1. 函数声明语法
    function sum(num1,num2){
      return num1+num2
    }
    
  2. 函数表达式语法
    var sum=function(num1,num2){
      return num1+num2
    }
    
  3. 构造函数
    var sum=new Function(“num1”,”num2”,”return num1+num2”);
    
    最后一种形式,存在几个个问题:1.会导致二次解析代码。因为传入的参数是字符串。2.每次调用构造函数都会返回新的函数。3.这种形式创建的函数不会使用词法作用域。会在顶级被编译。
    var y = "global";
    function constructFunction() {
        var y = "local";
        return new Function("return y");
    }
    console.log(constructFunction()());
    
function a(){
}
console.log(Object.getOwnPropertyNames(a));

Object.getOwnPropertyNames 列出自身可枚举和不可枚举的属性。 prototype指向构造函数的原型对象。在es5内是无法枚举的对象。所以for-in无法发现。

var haha=function a(){
}
console.log(haha.name);//a;
console.log(a);//未定义,因为是当成表达式,会忽略函数声明的方法名

函数的内部属性

函数的内部有两个特殊的对象,arguments和this

arguments是个类数组,有两个常用属性 :

  • callee指向拥有此对象的函数。
  • length,arguments的长度

call和apply区别就是传递参数的不同。

this机制文章:转载:https://www.cnblogs.com/xiaohuochai/p/5735901.html

var o = {
    m: function(){
        return this;
    }
}
var obj=new o.m();
console.log(obj,obj === o);//{} false
console.log(obj.constructor === o.m);

这里虽然o.m看起来是作为o的方法调用。但实际上return this返回的却不是o,这里的this实际是作为构造对象m的新对象返回的。

函数的四种调用方式:独立调用、方法调用、间接调用和构造函数调用。

转载的文章最后一句话很好:说到底,javascript如此复杂的原因是因为函数过于强大。因为,函数是对象,所以原型链比较复杂。因为函数可以作为值被传递,所以执行环境栈比较复杂。同样地,因为函数具有多种调用方式,所以this的绑定规则也比较复杂。

关于setTimeout函数里this指向修正:

转载:http://www.cnblogs.com/zsqos/p/6188835.html

  1. 存为变量,用闭包访问外层变量。
  2. bind
  3. 箭头函数=>

数组的遍历方法 every some filter map forEach都可以通过第二个参数绑定this值。

另外再po一个写了好多的文章 转载:https://www.cnblogs.com/venoral/p/5280805.html

基本包装对象

之前学过基本类型主要有五种:null,undefined,string,number,boolean。

为了便于操作基本类型值,es提供了三个引用类型:Boolean,Number,String

包装对象在调用对应基本类型值的时候被创建,调用结束后就被销毁。所以在运行时不能动态的为包装对象添加方法和属性。

尽量不要显示调用特殊类型创建实例。这样typeof会显示为object。

Object构造函数会像工厂方法一样,返回基本类型实例。

Boolean类型

重写了valueOf方法,返回基本类型值。toString返回字符串。

但是存在一个问题。放在布尔表达式中,显示创建的new Boolean(false)对象,会被当作为真。所以,如果要这么用,加上valueOf。其实这个对象用处不大。建议永远别用这个对象。。。

对于基本类型值,instanceof Boolean和instanceof Object都是false。

Number类型

number.toFixed(2) 格式化为字符串,显示小数点后两位:"2.00"

number.toPercision 把数字格式化为指定的长度,其可能返回固定大小格式也可能返回指数格式。

number.toExponential(1) 指数表示方法

String类型

不管是ascii还是unicode字符,都按字符数去计算length

字符方法

charAt

charCodeAt输出字符串编码

字符串操作方法

注意这几个方法都不会修改原始值,而是返回新值 concat 可接受任意多个参数进行拼接

slice

substr

substring

字符串位置方法

indexOflastIndexOf搜索字符串,返回子字符串位置,如果没有返回-1。查找方向不同,但位置都是从开头计数。

trim方法

trim创建不带任何空格的字符串副本并返回

大小写转换

toUpperCase toLocaleUpperCase

toLowerCase toLocaleLowerCase

模式匹配方法

match search split replace

function htmlEscape(text){
    return text.replace(/[<>"&]/g,function(match,pos,orginText){
        switch(match){
            case "<":
                return "&lt;";
            case ">":
                return "&gt;";
            case "\"":
                return "&quot;";
            case "&":
                return "$amp;";
        }
     });
}
console.log(htmlEscape("<p class=\"greeting\">Hello World!</p>"));

存在一个问题,split如果包含捕获组,会被合并到结果数组内,这是个比较有意思的特性。ie8之前会忽略捕获组。

var str="ababaabab";
var pattern=/(a){2}/;
console.log(str.split(pattern));//[ 'abab', 'a', 'bab' ]

如果使用时出了这种状况,应该明白是怎么回事

fromCharCode

function tohanzi(data)
{
    if(data == '') return '请输入十六进制unicode';
    data = data.split("\\u");
    var str ='';
    for(var i=0;i<data.length;i++)
    {
        str+=String.fromCharCode(parseInt(data[i],16).toString());
    }
    return str;
}
function tounicode(data)
{
    if(data == '') return '请输入汉字';
    var str ='';
    for(var i=0;i<data.length;i++)
    {
        str+="\\u"+parseInt(data[i].charCodeAt(0),10).toString(16);
    }
    return str;}
console.log(tohanzi(tounicode("铁蛋abc\\n\\")));//铁蛋abc\n\
var num=62;
console.log("abc"[1].charCodeAt().toString(16));//62

单体内置对象

global对象

URI UniformResource Identifier

encodeURI不会对URI的特殊字符进行编码。用于跳转

encodeURIComponent对发现的任何非标准字符进行编码。用于传参

decodeURI

decodeURIComponent

eval方法

功能很强大,但是尽量别用。。。

在eval中定义的函数和变量不会提升,因为到语句执行的时候才会创建。

Math对象

随机数算法

function selectFrom(lowValue,upperValue){
    var range=upperValue-lowValue+1;
    return Math.floor(Math.random()*range)+lowValue;
}