【完结】JS高级-总结-从0到1

这篇博客详细介绍了JS的高级特性,包括类和实例化对象、构造函数与原型的关系、闭包、递归、浅拷贝和深拷贝、正则表达式、let和const的使用。通过实例解析了this的指向、原型链的工作原理,以及解构和箭头函数的运用。此外,还涉及字符串和数据结构Set的知识,以及数组的扩展方法。
摘要由CSDN通过智能技术生成

学完js高级,1.学习面向对象的编程思想即万物都对象。2.Es6前后对象和类与构造函数的关系。Es6之前仿类都是通过构造函数来创建对象。Es6之后才有了类的概念。3.构造函数、原型和实例化对象三者的关系。4.闭包5.递归函数6.浅拷贝和深拷贝7.正则表达式8.let定义变量、const定义常量。9.解构和箭头函数10.数组的额外方法11.字符串的新定义 方法和优点12.新数据结构Set,它和数组差不多。


一、类和实例化对象

有了类,可以开辟很多空间创造出很多实例对象。 

体验类的写法。

class Father{
    constructor(a,b){
     console.log(a);
     console.log(b);   
    }

    chifan(){
    console.log('你正在吃饭');        
    }
}

var zhangsan=new Father(a,b);

1-创建类

class Father{}                                                                                                                                      类名要大写,如Father,类里面属性和方法后不用加‘,’分隔,最后面不用加;

2-constructor

constructor是类的构造函数,用来传递参数和返回实例对象。 当我们外部写new Father(a,b)时, new命令自动调用类里面的这个方法,传递了a和b两个参数,生成了一个实例对象。且这个构造函数可以省略,省略后拿不到参数。

实例化zhangsan后,zhangsan可以调用Father里面的属性和方法。

3-添加方法

直接在类里面写:函数名(){}

4-继承

通过下面的代码来理解extends。

class Son extends Father{
}

Son.chifan();

这里的Son继承了Father的属性和方法,所以可以调用Father里所拥有的属性和方法。

5-super

super用于调用父类的构造函数。通过下面的代码来理解。

class Father {
say() {
console.log('我是爸爸');}
}
        
class Son extends Father {
say() {
console.log(super.say() + '的儿子');}
}

var son = new Son();
son.say();

实例化Son后,调用say方法,首先会调用super.say();,在这里可以把super看成Father。然后会输出‘我是爸爸’,接着返回来输出undefined的儿子。为什么会undefined呢?因为我们在Father里面的say方法里面并没有返回任何东西。

所以,这种情况调用方法首先要看有没有返回值。

6-继承后可以扩展自己的方法同时可以将变量传递给父类。

class Father {
constructor(x, y) {
this.x = x;
this.y = y;
}
            
sum() {
console.log(this.x + this.y);
}
}

class Son extends Father {
constructor(x, y) {
super(x, y);
this.x = x;
this.y = y;
}
subtract() {
console.log(this.x - this.y);
}
}
var son = new Son(5, 3);
son.subtract();
son.sum();

实例化,5和3会分别传递给Son.x和Son.y。当调用Subtract(),就会得出结果,但是调用sum(),这个方法属于父亲里面,那么需要把参数传递给父类。这时候需要用到super,且super需要放到最前面。

7-注意事项

1.先定义类,才有实例化对象。

2.如果有公共属性和方法要加this。

3.注意this的指向问题。

constructor里面的this指向实例对象,而方法里的this指向调用者。

8-接着我们来看看this的指向

<button></button>

<sricpt>

class Father{
constructor(){
var btn=document.querySelector('button');
this.btn.onclick=this.sing();
}

sing{
consolo.log(this)
}
        
}

</srcipt>

上面的this和下面的this不同,下面的this指向调用者。


二、构造函数创建实例对象        构造函数和原型

除了类可以创造实例对象,构造函数也可以。

 ES6之前是没有类的概念的,都是通过构造函数来解决问题。现在绝大部分浏览器都支持ES6。 

1-创建实例对象:

创建对象有三种方法。

1.new Object()

var a=new Object;

2.字面量

var a={};

3.构造函数

var a=function(){

this.name='zhangsan';

this.sing=function(){alert('正在唱歌');}}

var b=new a;

通过new创建实例对象,首先看到new后会开辟一个空间,然后构造函数里面的this都会指向这个空间,里面的属性和方法都会放到这个空间里面,放完后就返回这个空间,这个空间就变成了我们定义的对象。

2-实例成员和静态成员

.1.实例成员

构造函数里通过this.来写的属性和方法都是实例成员,这些成员都可以在外部通过定义的对象.属性或方法来访问。

2.静态成员

构造函数里面没有通过this来写得属性和方法都是静态的,这些无法通过实例化的对象拿到。只能通过构造函数本身来拿到。

构造函数实例化后,会造成内存浪费,那么需要通过原型来解决。

3-prototype对象

每个构造函数都有一个属性指向这个prorotype对象,所以这个对象中的属性和方法都被构造函数所拥有。因此为了避免内存浪费,我们把那些不变的方法定义在prorotype对象里面。

总结:把属性放到构造函数里面,方法放到prorotype对象里面。

当然这边还存在一个问题,当把方法定义到原型对象里面,构造函数就没有定义这个方法,我们实例化对象后,实例完的对象为什么可以直接调用这个方法?这里就涉及到对象的一个属性_proto_。

4-_proto_属性

对象里面都有一个_proto_属性,且这个属性的指向和其对应的构造函数里面的prorotype属性的指向是一样的,都是指向原型对象。因此可以使用构造函数里的原型方法。

5-construction

原型对象里面有一个属性construction,这个属性指回构造函数本身。如果我们给原型对象赋值,就会出现下面的这种情况,这时候手动给原型对象添加construction属性,让属性指回构造函数。

假设有一个构造函数a,原型里面有方法b和c,然后往原型赋值方法b和c,即a.prototype={

b,c};那么这个原型对象不再指向构造函数,因为赋值完的原型对象里面不再有construction属性。

6-原型链

只要是对象都有原型,同理原型对象的原型是什么呢?答案那就是Object._proto_。那么这个大写的Object的原型对象是谁创造出来的?当然是Object这个构造函数。接着分析,接着奏乐,Object的原型对象指向谁呢?Null为空即返回结果为空。

因为有了原型链,所以可以从下往上查找对象成员。

7-成员查找机制

当访问一个对象的属性和方法时,首先看看这个对象自身有没有该东西,没有就看看这个对象的原型,也就是_proto_指向的对象,如果没有的话就找原型对象的原型即obeject构造函数的原型对象。再没有就没有了,因为找下去就为空,就找不到了。

8-this指向问题

构造函数中的this和原型对象里面的方法的this都是指向调用者。

9-原型对象的应用

利用原型对象可以对内置对象如数组添加自定义方法。

Array.prototype.名字=function(){},然后就可以直接使用了。如果用对象形式添加方法则要手动指回去构造函数。

10-call()

调用了方法,里面有参数会改变调用方法的this指向。

11-继承

因为ES6之前,没有像类那些extend继承父类的属性和方法,是通过构造函数继承属性,原型对象继承方法来实现继承。

下面代码继承属性。

function Father (uname,age){
    this.uname=uname;
    this.age=ageh;
}

function Son(uname,age){
Father.call(this,uname,age)
}

下面是方法继承。

function Father(){};
Father.prototype.sing();
function Son(){};
如果Son要继承Father里面的sing则把父亲的原型对象赋值给Son的原型对象。
Son.prototype=Father.prototype;

通过赋值来继承的话,会出现改变儿子的原型指向问题,那么这两个构造函数的原型是同一个,显然没有达到预期的需求。那么怎么解决?就是把Father实例后赋值给Son的原型就可以达到效果。即下面的代码。

function Father(){};
Father.prototype.sing();
function Son(){};
Son.prototype=new Father;

三、闭包

思考题1:

var name = "The Window";

var object = {
    name: "My Object",
    getNameFunc: function() {
        return function() {
            return this.name;};
}};

console.log(object.getNameFunc()())

这个代码不是闭包,但是分析一下过程:

让f=object.getNameFunc(); 运行object.getNameFunc();

f=function(){return this.name;}; f()

相当于function(){return this.name;}()

上面式子为立即执行函数。this指向window。

思考题2:

var name = "The Window";
var object = {
    name: "My Object",
    getNameFunc: function() {
        var that = this;
        return function() {
            return that.name;
        };
    }
};
console.log(object.getNameFunc()())

function(){return that.name;}()

that指向了上面的对象,that.name即My Object。


四、递归

 1-递归函数,即函数在自己里面调用自己。

function fn(){
    fn();
}
fn();

1.1_分析代码

var num=1;

function fn(){
    console.log('我要打印6句话'+num)
    if(num===6){return;};
    num++
    fn();
};
fn();

执行fn,先打印第一句话,判断否,继续执行,num=2,继续调用函数即打印第二句话,当循环到打印第六句话时,判断条件ture,然后跳出递归,外层的fn()执行完毕。

递归,需要return来跳出,否则将无线循环下去!

1.2_递归练习_求阶层

var num=1;
function fn(n){
    num*=n;
    n=n-1;
    if(n===0){
    return;}
    
    fn(n);
}

fn(2);

或者

function fn(n){
    if(n==1){return 1;}
    return n*fn(n-1);
}

1.3_递归练习_兔子序列

function fn(n){
if(n===1||n===2){return 1;}
    return fn(n-1)+fn(n-2);
}
fn(3);

求第三项由前面两项决定,然后递归,最终为1+1。

1.4_递归_遍历数据

1.4.1_forEach(function(e){})

var a=[
    {id:1,goods:[{id:1},{id:2}]},
    {id:2,goods:[{id:1},{id:2}]},
]

a.foreach(function(e){ console.log(e);});

输出结果为:

{id:1,goods:array(2)}

{id:2,goods:array(2)}

显然,forEach是依次遍历数组的方法,引擎会先看数组的第一个内容执行函数操作,然后再看第二个内容执行函数操作,依次类推。

var a=[
    {id:1,goods:[{id:11},{id:12}]},
    {id:2,goods:[{id:21},{id:22}]},
]

getid(b,id){
    b.forEach(function(e){
    if(e.id===id){console.log(e);}
    else if(e.goods&&e.goods.length>0){
        getid(e.goods,id);
}
})
}

调试:
输出ID为1的对象 console.log(getid(a,1));

但是,写实际项目的时候,不需要打印对象,需要拿到对象。

function getid(arr,id){
var o={};    
    arr.forEach(function(e){
    if(e.id==id){
    o=e;
}else if(e.goods&&e.goods.length>0){
    o=getid(e.goods,id);
}
}
    return o;
);}


前一个o和后一个o属于开辟的不同空间,注意逻辑性。通过上面的代码,可以通过id拿到所需要的对象。


五、浅拷贝和深拷贝

对象的浅拷贝和深拷贝在类的地方有提到过。对象里面的属性的值是一个对象的话,那么这个值存放的是指向另一个对象的地址。浅拷贝即拷贝表面的对象,里面的对象是拷贝地址。而深拷贝把整个对象都拷贝。

1-对象

var a={
    id:1,
    name:'zhangsan',
    gongneng:{
    sing:function(){},
    chifan:function(){}
    }

}

【分析】引擎首先开辟了一个空间,然后储存id,name,gongneng,因为gongneng的值是个对象,引擎开辟了一个新空间,储存这个对象,而原本gongneng的空间储存的就是指向这个空间的地址。

可以通过图片理解。

1.2_浅拷贝

1.2.1_原始的浅拷贝

原始的浅拷贝(a拷贝给b)是通过遍历a的属性然后给b,即:

var a={
    id:1,
    name:'zhangsan'
    gongneng:{
        sing:function(){
          console.log('你正在唱歌');
}
    }
};
var b={};
for(k in a){
    b[k]=a[k];
}

然后a拷贝给b的gongneng属性的值是另一个空间的地址,所以a和b共用一个地址,显然没有达到深拷贝的目的。

1.2.2_Es6提供的浅拷贝方法

如果觉得写原始的方法比较麻烦,可以使用Es6提供的办法。Object.assign(o,obj);即把obj拷贝给o。

1-3_深拷贝 

深拷贝即每一层数据都拷贝。

原始做法-递归遍历:

var a={
    id:1,
    name:{name1:'zhangsan',name2:'lisi'},
    sex:[{sex:'nan'},'nv']
}

var b={}

function shenkaobei(b,a){
    for(k in a){
    if(a[k] instansof Object){b[k]={}; shenkaobei(b[k],a[k])}
    else if(a[k] instansof Array){b[k]=[];shenkaobei(b[k],a[k])}
    else{b[k]=a[k]}
}
};
shenkaobei(b,a);

逻辑代码如上,可以尝试分析分析,很有趣。


六、正则表达式

 正则表达式即有规律的表达式,在js中是对象。

1-作用

匹配、替换replace、提取。

2-特点

正则表达式灵活,逻辑性强,功能也强大,可以很简单的控制复杂的字符串,但是对于刚接触的人来说,比较晦涩难懂。

虽然难懂,但是,实际开发中都是直接复制写号的正则表达式,但是要求会使用正则表达式和根据实际情况修改正则表达式。

3-在js中的使用

3.1_了解正则表达式

一个表达式可以由简单字符或和特殊字符的组成。其中,特殊字符在表达式中是具有特殊意义的专用字符。可以特殊字符的意义参考MDN,也可以在Jq api手册里面查看。

3.2_创建正则表达式

第一种方法:

new RegExp(/表达式/);

第二种方法:

var 名=/表达式/;

看方法就知道用第二种,简单!

3.3_测试正则表达式test();

test()方法可以测试表达式是否符合正则表达式的要求。

3.4_理解下面正则表达式

^和$:/^abc/ 、/abc$/ 、/^abc$/

[]和-:/[abc]/、/ [a-zA-Z-_]/、/^[a-z]$/ 中括号里面的^表示取反和外面的^意思不一样。/^[^a-z]/

量词符号:

(1)*即可以出现>=0次、+即出现>=1次、?即出现1或者0次

如果要求内容重复精确的次数用下面的特殊符号

(2){3}即只能出现3次、{3,}即大于等于3、{3,16}即大于等于3并且小于等于16。

在实际开发中,总是让某个模式重复多少次,比如,/ ^[a-zA-Z0-9-_]$/这个模式代表用户只能输入英文字母、数字、下划线和短横线。但是前尾^$限定了只能输入一个即a为ture,ab为false。那么通过量词让这个模式进行重复,如/^[a-zA-Z0-9_-]{6,9}$/即只能输入6-9位限定输入范围的表达式。

案例-验证表单用户名

如果用户名合法,则span显示绿色,不合法改为红色。

var a=/^[a-zA-Z0-9-_]{6,9}$/

var uname=document.querySelector('.uname');
var span=document.querySelector('span');
uname.onblur=function(){
    if(a.test(this.value)){
    span.style.backgroundcolor='#green'}
else {span.style.backgroundcolor='#red'}
};

 也可以下面这么写:

如果用户名合法,则span显示绿色,不合法改为红色。

var a=/^[a-zA-Z0-9-_]{6,9}$/

var uname=document.querySelector('.uname');
var span=document.querySelector('span');
uname.onblur=function(){
    if(a.test(this.value)){
    span.className='right';span.innerHTML='用户名输入正确'}
else {span.className='wrong';span.innerHTML='用户名输入错误'};
};

3.5-替换replace

被替换的参数可以是字符串也可以是正则表达式。

<textarea name:'' id='message'></textarea> <button>提交</button>
<div></div>

var textarea =document.querySelector('#message');
var button =document.querySelector('button');
var div =document.querySelector('div');

button.onclick(){
    textarea.value.replace(/激情|黄/g,'**');
};

g全局,|或,i不区分字母大小写。


七、let和const

1-let

let的作用域于在于块。也没有变量提升,也就是说,需要先声明后使用。暂时性死区,用let声明变量,这个变量就和块绑定。

1.1_let的作用域在于块

var i=1;
for(let i=0;i<2;i++){};
console.log(i);

上面输出的结果为1。在for循环用了let声明了变量i,i就属于for循环{}那个块。

1.2_let没有变量提升

for(let i=0;i<2;i++){
    console.log(a);
    let a=2;
};

这个代码会报错,let没有变量提升,需要先定义后使用。

1.3_暂时性死区

var a=3;
if(ture){
    console.log(a);
    let a=2;
};

这个代码也会报错,a没有定义,因为在块级作用域里面,用let声明了这个变量,块里面的这个变量都会与块绑定。

【面试题1】

var arr=[];
for(let i=0;i<2;i++){
    arr[i]=function(){
        console.log(i);
    };
};
arr[0]();
arr[1]();

每一次循环都会产生一个块作用域。函数执行时,引擎会在各自的块作用域寻找变量。

【面试题2】

var arr=[];
for(var i=0;i<2;i++){
    arr[i]=function(){
        console.log(i);
    };
};
arr[0]();
arr[1]();

循环的时候放函数进去数组里面,没有产生作用域,当调用时,在全局作用域寻变量。

 2-const

const声明常量,具有块作用域。且定义常量时必须赋值。一旦赋值,值(指地址)就不能更改。

2.1_块作用域

if(ture){
    const a=1;
};
console.log(a);

在if里面定义的常量在外面是拿不到的。继续观察下面代码。

if(ture){
    const a=1;
    if(ture){
     const a=2;
     console.log(a);   
        }
};
console.log(a);

结果输出2和报错。

2.2_定义常量时必须赋值。

比如const a;和const a=2;前面的报错,后面的正确。

2.3_一旦赋值,值(指地址)就不能更改。

const PI=2; PI=3;这会报错。

const arr=[100,200];        arr[0]='a';arr[1]='b';这是可以的,因为没有修改arr的地址。然而arr=['a','b'];这是不可以的,因为修改了arr的地址。

3-var let const的区别

1、var 作用域在该语句所在函数,存在变量提升。

2、let 作用域在该语句所在代码块。

3、const 后面出现的代码不能再修改值。


八、解构和箭头函数

1.解构赋值

1.1_解构数组

从数组中提取值,按照对应关系,对变量赋值。如果变量没有对应关系则变量的值为undefinded。

let[a,b,c]=[1,2,3];
console.log(a);
console.log(b);
console.log(c);
let arr[a,b]=[1];

因为b的位置没有值,则解构失败,b的值为undefinded。

1.2_解构对象

let person={name:'zhangsan',age:18};
let {name,age}=person;

这个代码按照对应关系匹配把对象的属性值赋值给变量。

还有一种是按照是否有相同的属性匹配。

let person{name:'zhangsan',age:18};
let {name:Myname,age:Myage}=person;

name和age用来匹配,Myname和Myage是变量。

2-箭头函数

2.1

()=>{}用来简化定义函数并赋值给一个变量,const fn=()=>{}。

如果只有一个代码,代码执行完是返回值,大括号可以省略。 ()=>a+b;非省略写法:function a(){return a+b;}。

除了大括号可以省略,小括号在只有一个形参的情况下也可以省略。

2.2

箭头函数中的this指向被定义位置的上下文。

const fn=()=>{
    console.log(this);
    return ()=>{console.log(this)};
};

const a={name:'zhangsan'};

var fn=fn.call(a);

fn();

定义一个箭头函数,输出this,指向的是window,然后返回一个箭头函数。在外部改变了箭头函数的指向a对象,在调用返回的箭头函数,因为this已经改变为a对象了,所以指向a对象。

【面试题】

var a={
    age:20,
    say:()=>{console.log(this.age);}
};
a.say();

为什么结果是undefinded;因为obj是一个对象,不产生作用域,箭头函数被定义在window,且箭头函数没有自己的this,箭头函数的this指向定义的上下文。所以this指向的是window。


九、剩余参数和扩展运算符

 1.剩余参数

1.1

如果传递的参数太多,没有那么多的形参接收,可以把额外的参数接收为数组。语法是在形参前加三个点即...a。

const fn=(...a)>{};
sum(10,20,30);

这时候a=[10,20,30];所以说把参数用数组来接收。

【练习】把传递进来的参数相加

const sum(...a)=>{
    let sum=0;
    a.forEach(b=>{sum+=b};);
    return sum;
};

数组可以用forEach遍历。

1.2

剩余参数除了用在形参里面用于接收参数然后转变数组。还可以用在解构中。

let student=['zhangsan','lisi','wangwu','laoliu];
let[a,b,...c]=student;

2-扩展运算符

扩展运算符用于把数组和对象拆解为逗号分隔的序列。...和剩余参数的不一样。

let a=[1,2,3];
...a
console.log(...a);

...a的结果是1,2,3。但是放在console.log输出的时候会把逗号变成空格。输出结果为1 2 3。

2.1-合并数组

let a=[1,2,3];
let b=[2,3,4];

let c=[...a,...b];

a.push(...b);

 2.2-将伪数组转换为真实数组调用数组的方法

伪数组无法调用数组的方法。当我们获取页面元素的时候获取的其实是伪数组,然后想要使用数组的方法必须转换为真正的数组。

var div=document.querySelector('div');
假设这里获取10个div。
[...div]这里就是真正的数组了。

十、字符串和数据结构Set

 1-模板字符串

反引号可以用来定义字符串。

1.1_解析变量

反引号定义的字符串可以解析变量$(变量名)

以前字符都是通过引引加加的方式,ES6提供的新定义字符串的方法可以解析变量。

let name='zhangsan';
let a=`hello this is mobanzifuchuang user $(name)`;

1.2_字符串可以换行

let html=`
    <div>
        <span></span>
        <span></span>
        <span></span>
    </div>
`

1.3_字符串内可以调用函数。

const a=()=>{
    return'哈哈哈哈 追不到我把 我就是这么强大'
}

let b=`${a()} 呵呵哈哈哈`

输出结果:哈哈哈哈 追不到我把 我就是这么强大 呵呵哈哈哈

2-startsWith()和endsWith()

startWith()判断给定的字符串是否在原字符串的头部。返回布尔值。

endsWith()判断给定的字符串是否在原字符串的尾部。返回布尔值。

3-repeat()

repeat()将字符串重复n次,返回新字符串。

'a'.repeat(3); 返回aaa

4-数据结构set

set类似于对象。其成员都是唯一的。Set本身就是一个构造函数,且可以接收一个数组作为参数。

const a=new Set([1,2,3]);

const b=new Set();

Set的size属性可以看到数据的个数。

4.1数组去除

const a=new Set(['a','a','b','b']);

console.log(a);

可以用...将Set结构转换为逗号分隔的序列,在外部添[]即[...a],就是新的数组。观察到数组已经去重。

4.2_Set方法

既然Set是数据结构,那么Set下必然有一些方法来操控这个数据结构。

add();        添加某个值,返回数据结构本身

delete();        删除某个值,返回布尔值。


十一、数组扩展方法

 1-Array方法

1.from()

将伪数组和对象转换为真数组。

let a={
    '0':1,
    '1':2,
    'length':2
};
let newAry=Array.from(a);

第一参数接收待转换的伪数组,第二参数对每个元素进行处理,处理完返回数组。

let a={
    '0':1,
    '1':2,
    'length':2
};
let newAry=Array.from(a,c=>c*2);

2.find()

寻找数组第一个符合条件的成员,没有返回undefinded。

find((item,index)=>{ return item.id==2});

3.findndex()

找出第一个符合条件的数组成员位置,没有找到返回-1

var a=[1,2,3,4,5];

let index =a.findIndex((value,index)=>{return value>9});

4.includes()

判断数组是否包含给定的值,返回布尔值。

[1,2,3].includes(2); ture

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值