JS

212 阅读12分钟

数据类型

JS数据类型包括基本数据类型和引用数据类型

  • 基本数据类型包括string,number,boolean,undefined,null,symbol
  • 引用类型包括object (array[1,2] function(){})

二者的区别是什么

  • 基本数据类型占有固定的大小,存在栈中,赋值的方式传值,赋值后不会相互影响
  • 引用类型的大小不固定,在堆中储存指针的指向,赋值后相互影响

检测数据类型的方式:typeof,instancsof,constructor

console.log(typeof 2); // number 
console.log(typeof true); // boolean 
console.log(typeof 'str'); // string 
console.log(typeof []); // object 
console.log(typeof function(){}); // function 
console.log(typeof {}); // object 
console.log(typeof undefined); // undefined 
console.log(typeof null); // object

数组和对象和null会判断为object

console.log(2 instanceof Number); // false 
console.log(true instanceof Boolean); // false 
console.log('str' instanceof String); // false 

console.log([] instanceof Array); // true 
console.log(function(){} instanceof Function); // true
console.log({} instanceof Object); // true

instancof 只可以判断引用类型的数据类型

console.log((2).constructor === Number); // true 
console.log((true).constructor === Boolean); // true
console.log(('str').constructor === String); // true 
console.log(([]).constructor === Array); // true 
console.log((function() {}).constructor === Function); // true console.log(({}).constructor === Object); // true

constructor有两个作用,一是判断数据的类型,二是对象实例通过 constrcutor 对象访问它的构造函数。

字符串的操作方法

  • .length 获取字符串的长度
    var str = 'abcdef'
    str.length // 6
  • indexOf(searchvalue,fromindex) 查找某个字符所在的索引值
    var str = 'abcdefga'
    console.log(str.indexOf("a")) //0
    console.log(str.indexOf("z")) //-1
    console.log(str.indexOf("a", 3)) //7
  • includes(searchvalue, start) 查找字符串是否包含特定的子字符串
    var str = 'abcdefg'
    console.log(str.includes('a')) //true
    console.log(str.includes('z')) //false
    console.log(str.includes('a',4)) //false
  • concat(str1,str2,...,strx) 连接多个字符串
    var str1 = 'abc';
    console.log(str1.concat('efg')) //abcefg
    console.log(str1.concat('efg','sss')) //abcefgsss
  • .split(separator,limit) 把一个字符串分割成一个字符串数组,且不会改变原字符串
    let str = 'abcdef'
    str.split('c') //['ab','def']
    str.split("",4) //['a],'b','c','d']
  • slice(start,end) 截取字符串,并不会改变原字符串,获取数据的范围
    let str = 'abcdef'
    str.slice(0,2) //abc
    str.slice(1) //bcdef
    str.slice(-2) //ef
  • substr(start,length),获取数据的范围是【strat,end)
  • substring(form,to),获取的数据范围是(start,end】
  • toLowerCase(),toUpperCase() 转化大小写
  • replace(),match(),search()字符串模糊匹配
  • trim() 去除字符串首尾的空格,该方法不会改变原始字符串

数组的操作方法

  1. 会改变原数组 push(),pop(),shift(),unshift(),reverse(),splice(),sort()
  2. 不会改变原数组 reduce(),filter(),every(),some(),find(),findIndex(),join(),map(),forEach(),includes()

改变原数组的方法

  • push()&&unshift() 从尾部/头部添加元素 返回值是新数组的长度
var arr = [1,2,4,5,87,8]
arr.push(33) //7 新数组的长度 arr = [1,2,4,5,87,8,33]
arr.unshift(99) // 8 新数组的长度 arr=[99,,1,2,4,5,87,8,33]
  • pop()&&shift() 删除数组的最后一个/第一个元素,返回值是被删除的元素
var arr = [1,2,4,5,87,8]
arr.pop() // 8 返回被删除的元素 arr=[1,2,4,5,87]
arr.shift() // 1 返回被删除的元素 arr=[2,4,5,87]
  • reverse() 颠倒数组元素的方法
var arr = [1,2,4,5,87,8]
arr.reverse() // [8,87,5,4,2,1]
  • splice() 增加/删除元素 返回被删除的元素
    array.splice(index,howmany,item1,.....,itemX)
var arr = [1,2,4,5,87,8]
arr.splice(2,2) //[4,5] 返回被删除的元素
arr.splice(0,1,99) [1] arr=[99,2,87,8]
  • sort() 对数组进行排序 返回新的数组
var arr = [1,2,4,5,87,8]
arr.sort() // [1,2,4,5,8,87]
arr.sort((a,b)=>b-a) // 从大到小排序
arr.sort((a,b)=>a-b) // 从小到打排序

不会改变原数组的方法

  • reduce() 主要于对数组的求和,也可以对字符串,数组进行拼接
    Array.reduce((accumulator,current,index,Array) =>{
    ....... return accumulator } ,int)

    accumulator:当前的聚合值;current:循环的当前的元素;index:索引值 arr:数组本身;int: 初始值
//数组求和
var arr = [1,2,4,5,87,8]
arr.reduce((pre,cur)=>{ return pre+cur},0 ) //107
//数组对象求和
var arr = [{b:3},{b:5}]
arr.reduce((pre,cur)=>{return pre + cur.b},2) // 10
  • filter() 从数组中筛选符合条件的元素
    array.filter(function(currentValue, indedx, arr), thisValue)
var arr = [1,2,4,5,87,8]
arr.filter(item=>item>4) //[5,87,8] 返回符合条件的元素 (浅拷贝)
  • ervey(),some() 前者只有元素都符合条件返回true否则为false;后者只要有个元素符合条件就返回true,都不符合则返回false
var arr = [1,2,4,5,87,8]
arr.every(it=>it>0) //true 数组的每个元素都大于0 
arr.some(it=>it>90) // false 数组只要有一个符合条件就返回TRUE
  • find() findIndex() 返回符合条件的第一个元素/元素下标
    array.find(function(currentValue, index, arr),thisValue)
var arr = [1,2,4,5,87,8]

arr.find(it=>it>0) //1
arr.find(it=>it>99) //undefined

arr.findIndex(it=>it>0) //0
arr.findIndex(it=>it>99) //-1
  • join() 将元素按照字符串连接起来返回
var arr = [1,2,4,5,87,8]
arr.join() //'1,2,4,5,87,8'

arr.join(' ') //'1 2 4 5 87 8'

map()和foreach()的区别

相同点:

  • forEach()和map()方法通常用于遍历Array元素
  • 都不能使用break打断,中断遍历
  • 不会改变原数组

不同点:

  • foreach()返回undefined,map()返回一个新数组
  • map()可以链接其他方法,比如reduce().sort(),filter()

解决if嵌套的方法

  • switch case
  • 用return代替else if
  • array的includes()

深拷贝和浅拷贝

深拷贝和浅拷贝是针对引用类型的

浅拷贝:是拷贝指针的指向,共享同一块内存,改变新对象旧对象也会改变

  • Object.assign()
  • Array.from()

深拷贝:是复制并创建一个新的对象,不共享内存,修改新对象,旧对象不改变

  • JSON.parse(JSON.stringify())
    let B = JSON.parse(JSON.stringify(A))
  • 遍历循环
  • slice()
  • concat()
  • es6运算扩展符...

数组去重的方法

  • es6的Set去重
    let arr = [1,2,3,1,2,3]
    let arr2 = Array.from(new Set(arr)) //[1,2,3]
  • 双重for循环,然后用slice去重
    let arr = [1,2,3,1,2,3]
    for(var i=0;i<arr.length;i++){
       for(var j=i+1; j<arr.length; j++){
           if(arr[i] === arr[j]){
               arr.splice(j,1);
               j--
           }
       } 
    }
    //arr=[1,2,3]
  • indexOf去重
    let arr = [1,2,3,1,2,3]
    let arr2 = [];
    for(var i = 0; i<arr.length; i++){
        if(arr2.indexOf(arr[i])) === -1){
            arr2.push(arr[i])
        }
    }
  • filter去重
    let arr = [1,2,3,1,2,3]
    arr2 = arr.filter((it,index,self)=>{
        return self.indexOf(it) === index;
    })

var let consr的区别

  • 作用域不同:var的作用域是全局,let和const的作用域是块级作用域
  • 变量提升: var定义的变量会发生变量提示,let和const则不会
console.log(num); //undefined
var num = 1;

此时的console.log()没有报错,原因是num进行了变量提升 等价于:

var num;
console.log(num);
num = 1;
  • 重复声明:var可以重复声明,后声明的会覆盖先声明的值;let和const不可以重复声明。
  • 初始值:var和let可以不设置初始值,const必须声明初始值,并且不允许更改
  • 指针的指向:let和const都是es6新声明的语法。let可以更改指针的指向(即可以改值),const不可以
  • 暂时性死区:let和const命令在声明变量之前是不可以使用的。在语法上称为暂时性死区;var则没有

解构

解构是 ES6 提供的一种新的提取数据的模式,这种模式能够从对象或数组里有针对性地拿到想要的数值。

  • 数组解构:由元素的位置为匹配条件来提取想要的数据

    const [a,b,c] = [1,2,3]  //a:1 b:2 c:3
    const[a,,c]=[1,2,3]  //a:1 c:3
  • 对象解构:由对象的属性为匹配条件来提取数据

    const student = {
        age:18;
        name: 'huahua'
    }
    const {name,age} = student;

提取高嵌合度的对象属性:通过冒号:+{对象属性名}的方式来获取

    const school = {
        classes: {
            stu:{
                age:10;
                name: 'amy';
            }
        }
    }
    const {classes: {stu: {name} } = school;
    //name:amy

箭头函数

运用箭头来定义函数 ()=> {}

箭头函数的参数

  • 没有形参 let fun1=()=> { console.log('aaaaaaaaaa') };
  • 有一个形参 let fun2 = name=>{ console.log(Hello ${name} !) }
  • 有多个形参 let fun3 = (name,age)=> { return [age,name]; }

箭头函数的函数体

  • 如果箭头函数的函数体只有一句代码,仅仅是返回一个值或一个简单的表达式,可以省去函数的大括号{}
    let fun1 = val => val
等价于
    let fun2 = function (val) { return val }
    let  sum1 = (num1,num2) => num1 + num2;
等价于
   let sum2 = function(num1,num2){ return num1 + num2 };
  • 如果箭头函数的函数体只有一句代码,只返回一个对象,可以用小括号包裹,但是不可以直接用大括号包裹
    let getItem = id=> ({id:id,name:'Tom'})
    let getItem = id=> {id:id,name:'Tom'} //这个写法是错误的 大括号会解析为函数体的大括号

-如果箭头函数的函数体只有一句代码,且不需要返回值(常见的是调用一个函数),可以给这条语句的前面加一个void关键字。

    let fun = void noReturnFun();

箭头函数常用的作用就是简化回调函数

    //普通函数写法
    [1,2,3].map(function(x){
        return x*x
    })
    //箭头函数写法
     [1,2,3].map( x=> x*x );

箭头函数和普通函数的区别

  • 语法更简洁清晰
  • 箭头函数不会创建自己的this : 箭头函数没有自己的this,它会捕捉自己在定义时(注意是定义时不是调用时)所处的外层执行环境的this,并继承this值。所以箭头函数的this值在定义时就确定了,不会再改变
    var id = 'Global';
    function fun1 (){
        setTimeout(function(){
            console.log(this.id); //Global
        },2000)
    }
    function fun2 (){
        setTimeout(()=>{
            console.log(this.id); //Obj
        },2000)
    }
    fun1.call({id:'Obj'});
    fun2.call({id:'Obj'});

在fun1这个函数是在全局调用的,所以this指向window对象,this.id就指向全局变量id即Global; fun2中的箭头函数的this在定义时就确定了,继承了外层fun2的执行环境中的this,而fun2在调用时this被call方法改变到了对象{id:'Obj'}中,所以输出Obj

    var id = 'GLOBAL'; 
    var obj = { 
        id: 'OBJ', 
        a: function(){ 
            console.log(this.id); 
        }, 
        b: () => { 
            console.log(this.id); } }; 
            obj.a(); // 'OBJ' 
            obj.b(); // 'GLOBAL'

  • 箭头函数继承而来的this指向永远不变!
  • .call(),.apply(),.bind()无法改变箭头函数的this指向
    var id = 'Gloabl';
    let fun1 = ()=>{
        console.log(this.id)
    }
    fun1();
    this的指向不会改变,一直指向widow对象
    fun1.call({id: 'Obj'}); // 'Global' 
    fun1.apply({id: 'Obj'}); // 'Global' 
    fun1.bind({id: 'Obj'})(); // 'Global'
  • 箭头函数不能作为构造函数使用

构造函数的new:①js首先会生成一个对象②再把函数中的this指向该对象③执行构造函数中的语句④返回该对象的实例。 箭头函数是没有自己的this,继承于外层执行环境的this,且不会通过调用而改变,所以箭头函数不能作为构造函数。即构造函数不能用箭头函数定义,否则在new调用时会报错

    let Person = (name,age)=>{
        this.name = name;
        this.age = age;
    }
    let p = new Person('huahua',37); //报错
  • 箭头函数没有自己的argument:

    箭头函数没有自己的arguments,可以获取外层局部函数的执行环境中的值。 arguments是一个相对于传递给函数的参数的类数组对象。它是一个对象不是一个Array,但是有length和索引的属性

    let fun = (val)=>{
        console.log(val); //111
        console.log(arguments); //报错
    }
    fun (1111)
    
    
    function outer(val1,val2){
        let arr = aguments;
        cosnole.log(arr); // [111,222]
        let fun = ()=>{
            let arr2 = arguments; //获取了上一层函数的arguments
            console.log(arr2); //[111,222]
            console.log(arr1 === arrr2); //true
        }
    }
    outer(111,222)

可以在箭头函数中使用rest参数代替arguments对象,来访问箭头函数的参数列表!!

  • 箭头函数没有原型prototype
    let sayHi = ()=>{
        console.log('helloworld');
    }
    console.log(sayHi.prototype) //undefined

原型和原型链

原型:

js中是使用构造函数来创建一个新对象,每一个构造函数的内部都有一个prototype的属性,它的属性值是一个对象,包括构造函数的所有实例共享的属性和方法。当使用构造函数新建一个对象后,这个对象的内部就会包含一个指针,这个指针指向构造函数的prototype属性所对应的值,这个指针被称为原型。新建的对象可以通过_proto_这个属性来访问。但是最好不要使用,不是规范中规定的。es5新增了一个Object.getPrototypeOf()方法来获取原型。

原型链:

当访问一个对象的属性时,如果对象内部不存在这个属性,它就会去他的原型对象里去找,这个原型对象又会有自己的原型对象,于是一直这样找下去,也就是原型链的概念。原型链的一般尽头来说都是Object.prototype。这就是为什么新建的对象可以使用toString()的方法。

特点

js的对象都是通过引用来传值的,创建的每一个新对象没有属于自己的原型副本。所以当修改原型时,与之相关的对象都会继承这一改变。

  • 原型的修改和重写
    function Person(name){
        this.name = name;
    }
    //修改原型
    Person.prototype.getName = function(){}
    var p = new Person('xiaoming');
    console.log(p._proto_ === Person.prototype) //true
    cosnole.log(p._proto_ === p.constructor.prototype) //true
    
    //重写原型
    Person.prototype = {
        getName: function(){};
    }
    var p = new Person('xiaoming');
    console.log(p._proto_ === Person.prototype) //true
    cosnole.log(p._proto_ === p.constructor.prototype) //false

可以看到在修改原型时候,p的构造函数不指向Person,因为直接给Person的原型对象用对象赋值,它的构造函数指向了根构造函数Object,所以这个时候,p.constructor === Object。想要成立p.constructor === Person,需要用constructor指回来。


    Person.prototype = {
        getName: function() {} 
    } 
    var p = new Person('hello') 
    p.constructor = Person 
    console.log(p.__proto__ === Person.prototype) // true   
    console.log(p.__proto__ === p.constructor.prototype) // true
  • 原型链的指向
    p.__proto__ // Person.prototype 
    Person.prototype.__proto__ // Object.prototype 
    p.__proto__.__proto__ //Object.prototype
    p.__proto__.constructor.prototype.__proto__ // Object.prototype
    Person.prototype.constructor.prototype.__proto__ // Object.prototype
    p1.__proto__.constructor // Person Person.prototype.constructor // Person
  • 原型链的终点是什么

    由于Object是构造函数,原型链的终点是Object.prototype.proto,而Object.prototype.proto === null,所以原型链的终点是null。

  • 如何获取对象上非原型链上的属性

    使用hasOwnProperty()方法来判断属性是否属于原型链的属性:

    function isExist(obj){
        var res=[]; 
        for(var key in obj){ 
        if(obj.hasOwnProperty(key)) 
            res.push(key+': '+obj[key]); 
       } return res;
    }

事件循环机制

首先JavaScript是一个单线程语言。分为两个任务种类:同步任务和异步任务。

同步任务:只要被扫描到就会被主线程立刻执行的任务,优先于所有异步任务。

异步任务:即使被扫描到,也不会立马执行,会压入异步任务队列中,等到主线程的任务全部清空了,再被召唤执行。

异步任务分为宏任务和微任务。在异步任务中平均执行周期很长的任务被称为宏任务,而平均执行周期较短的任务称为微任务。

当有异步任务被压入异步任务队列中时,JavaScript会将这些任务分为宏任务和微任务俩个新的队列。然后等所有同步任务执行完成后,异步任务会优先执行在任务队列中的微任务。在所有微任务执行完毕后,再去宏任务队列中执行一个宏任务(注意是一个),执行完一个宏任务再去微任务队列中检查是否有新的微任务,有则全部执行,再回到宏任务中执行一个宏任务,以此循环。这一套流程就叫事件循环(event loop)

  • 宏任务:setTimeout(),setInterval()
  • 微任务:Promise.then() aysnc/await

image.png

    setTimeout(()=>{
        console.log('1')
    },0)                      //异步任务的宏任务
    console.log('2')          //同步任务
    Promise.resolve().then(()=>{
        console.log('3')
    })                        //异步任务的微任务
    console.log('4')          //同步任务
    
    //2 4 3 1
    //第一个宏任务 
    setTimeout(() => { 
        console.log(1); //宏任务中的同步任务 
        Promise.resolve().then(() => { console.log(7) }) //宏任务中的微任务 
    }, 0);              //异步任务 - 宏任务 
    console.log(2);     //同步任务
    Promise.resolve().then(() => { console.log(3) }) //异步任务 - 微任务 
    //第二个宏任务 
    setTimeout(() => { 
        console.log(8);                          //宏任务中的同步任务 
        setTimeout(() => { console.log(5) }, 0) //宏任务中的宏任务 第四个宏任务 
    }, 0); 
    //第三个宏任务 
    setTimeout(() => { 
        Promise.resolve().then(() => { console.log(4) }) //宏任务中的微任务 
    }, 0);
    console.log(6); //同步任务

    // 2 6 3 1 7 8 4 5
async function async1() {
  console.log('async1 start') //2
  await async2()
  console.log('async1 end') //6
}
 
async function async2() {
  console.log('async2') //3
}
 
console.log('script start') //1

setTimeout(function() {
  console.log('setTimeout') //8
}, 0)
 
async1(); 
   
new Promise( function( resolve ) {
 console.log('promise1') //4
 resolve();
} ).then( function() {
 console.log('promise2') //7
} )
 
console.log('script end') //5
https://blog.csdn.net/qq_41681425/article/details/85775077

个人认为最需要注意的细节是,事件循环每一次只执行一个宏任务

console.log(1)
setTimeout(() => {
  console.log(2)
  new Promise(function(resolve) {
    console.log(3)
    resolve();
  }).then(function(){
    console.log(4)
  })
});
new Promise(function(resolve){
  console.log(5)
  resolve()
}).then(function(){
  console.log(6)
})
setTimeout(() => {
  console.log(7)
  new Promise(function(resolve){
    console.log(8)
    resolve('ssss')
  }).then(function(res){
    console.log('结果是'+res)
    console.log(9)
  })
});
console.log(10) 
// 1-5-10-6-2-3-4-7-8-结果是sss-9
new Promise((resolve,reject)=>{
  setTimeout(() => {
    resolve('res11')
    reject("dfjfgdsgfsg")
  }, 3000);
}).then(res=>{
  console.log(res)
})
console.log('start')
new Promise(resolve=>{console.log('promise')})
async function newFunc(){
  await async2();
  console.log('11111111111')
  new Promise((resolve,reject)=>{
    resolve('res22')
    setTimeout(() => {
      console.log('WAITING')
    }, 2000);
    console.log('settimeout')
  }).then(res=>{
    console.log(res+'DDDDDDDDDD')
  })
  console.log('222222222222')
}
function async2(){
  console.log('3333333333')
}
console.log('midden')
newFunc();
console.log('ending') 

//start>promise>midden>3333333>ending>11111111>settimout>2222222>res22DDD>WATING-res11

异步的实现方式

  • 回调函数
  • Promise
  • Generator
  • async/awit (是Generator的语法糖)

回调函数

    ajax('aaa', ()=>{
        // callback 函数体
        ajax('bbb', ()=>{
            // callback 函数体
             ajax('ccc', ()=>{
                 // callback 函数体
             })
        })
    })

es6出现之前,利用回调函数解决异步执行问题。该方式的弊端一是没有顺序可言,嵌套函数带来的调试困难,不利于维护与阅读;而是耦合性太高,某一个嵌套出问题会影响整个回调;会出现回调地狱的问题。

Promise

    let a  = new Promise(function(resolve, reject){
      _this.$http("GET", '/import/get_deal_speed.php?id='+id).then(
          res => {
                 if(res.errorCode){
                    reject(res)
                  }else{
                    resolve(res)
                   }
                })
          })
      return a
   }

通过promise处理异步,代码更加清晰,解决了回调地狱的问题,Promise的then()的链式调用更让人接受。

Promise的三个状态pending resolved rejected

Promise也存在缺点:①Promise的内部错误使用try catch捕捉不到,只能用then的第二个回调或catch来捕获 ②是Promise一旦新建就会立即执行,无法取消

     let pro 
     try{ pro = new Promise((resolve,reject) => { 
         throw Error('err....') }) 
     }catch(err){ 
         console.log('catch',err) // 不会打印 
     } 
     pro.catch(err=>{ console.log('promise',err) // 会打印 })

async/await

   async function fetch() { 
       await ajax('aaa') 
       await ajax('bbb') 
       await ajax('ccc') 
   }

async的返回值是Promise对象,可以调用then()方法

    async function fn() { return 'async' } 
    fn().then(res => { console.log(res) // 'async' })

promise和async/await的区别

Promise 通过使用 then() 和 catch() 方法来处理异步操作的结果,而 async/await 通过使用 async 和 await 关键字来等待异步操作的结果。async/await 是建立在 Promise 之上的语法糖,它使得异步代码更加易读、易写,同时也使得代码结构更加清晰、易于维护。在实际开发中,我们可以根据需要选择合适的异步编程方式来处理异步操作。

  • async/await使异步函数内部是同步执行的,简化回调,直接返回一个promise对象

promise.all()和async/await有什么区别

  • promise.all(c(),b(),a())没有执行顺序
  • async 和awiat搭配使用让方法同步执行方法就是按照程序的书写顺序依次执行的
 function A(){
  return new Promise(
    (resolve,reject)=>{
      setTimeout(() => {
        console.log('aaaaaaaaa')
        resolve('A'+new Date())
      }, 3000);
    }
  )
}
function B(){
  return new Promise(
    (resolve,reject)=>{
      setTimeout(() => {
        console.log('bbbbbbbbb')
        resolve('B'+new Date())
      }, 4000);
    }
  )
}
function C(){
  return new Promise(
    (resolve,reject)=>{
      setTimeout(() => {
        console.log('ccccccccc')
        resolve('C'+new Date())
      }, 5000);
    }
  )
}
 Promise.all([C(),B(),A()]).then(res=>{console.log(res)})
 // 打印 aaaaa>bbbbb>cccc 调用的顺序是C>B>A

async function D(){
  var c = await C();
  var b = await B();
  var a = await A();
  console.log(a)
  console.log(b)
  console.log(c)
}
D();
//打印 cccccccc>bbbbbbb>aaaaaaaaa  按调用顺序执行

Proxy

什么是代理:可以定义对象的各种操作的自定义行为

  • Proxy(target,handles)
    let target = {};
    let handles = {};
    let proxy = new Proxy(target,handles)
    proxy.a = 123;
    console.log(target.a) //123

封装 继承 多态

面向对象的三大特点是: 封装 继承 多态

什么是面向对象: 对象是一组无序的相关属性和方法的集合。

优点是:易维护,易复用,易扩展,封装继承多态的特性可以设计出低耦合的系统,使系统更加灵活。

缺点是:性能比面向过程低(c语言)

封装

什么是封装: 创建一个对象结构,集中保存一个事物的属性和方法

如何创建对象:①对象字面量,用{}来创建

    var obj = {
        name: 'xiaoming';
        age: 19;
        dosometing:function(){}
    }

②通过new实例化类创建

    var obj = new Object();
    obj.name = 'xiaoming';
    obj.age = 18;
    obj.intr = function(){console.log('sss')}

继承

什么是继承: 父对象中的属性和方法,子对象无需重复创建,可以直接调用

js中的继承是通过原型对象来实现的。新建一个构造函数时,会自动生成一个空的原型对象; 构造函数.prototype ->原型对象 ;实例对象.proto -> 构造函数的原型对象

多态

什么是多态:就是一个函数在不同情况下表现的不同状态

多态包括重写和重载

  • 重写是在子对象中定义已经存在在父对象中的属性,使用时优先使用自定义的。
  • 重载是相同的函数名,不同形参列表的函数,在调用时,可以根据传入的参数不同,自动匹配函数来调用。js中不支持重载,如果存在多个同名函数,最后一个函数会覆盖前面的函数,可以利用arguments实现

ES6的class中的静态属性和方法 static关键字

类:类用于创建对象的模板。用代码封装处理该数据。 类实际上是特殊的函数。类语法分为类声明和类表达式。

  • 类声明
    class Rectangle {
        constructor(height, width) {
            this.height = height;
            this.width = width;
          }
    }

类声明和函数声明的的区别在于类声明不会提升。首先要声明类,才能访问它,否则会报错。

  • 类表达式:类表达式是定义类的另一种方法。类表达式可以命名或不命名。
    // 未命名/匿名类
    let Rectangle = class {
      constructor(height, width) {
        this.height = height;
        this.width = width;
      }
    };
    console.log(Rectangle.name);
    // output: "Rectangle"

    // 命名类
    let Rectangle = class Rectangle2 {
      constructor(height, width) {
        this.height = height;
        this.width = width;
      }
    };
    console.log(Rectangle.name);
    // 输出:"Rectangle2"
  • 类的原型方法
    class Rectangle {
        // constructor
        constructor(height, width) {
            this.height = height;
            this.width = width;
        }
        // Getter
        get area() {
            return this.calcArea()
        }
        // Method
        calcArea() {
            return this.height * this.width;
        }
    }
    const square = new Rectangle(10, 10);

    console.log(square.area);
    // 100
  • 类的静态方法

static 关键字用来定义一个类的一个静态方法。调用静态方法不需要实例化 (en-US) 该类,但不能通过一个类实例调用静态方法。静态方法通常用于为一个应用程序创建工具函数。

    class Point {
        constructor(x, y) {
            this.x = x;
            this.y = y;
        }

        static displayName = "Point";

        static distance(a, b) {
            const dx = a.x - b.x;
            const dy = a.y - b.y;
            return Math.hypot(dx, dy);
        }
    }

    const p1 = new Point(5, 5);
    const p2 = new Point(10,10);
    p1.displayName;
    // undefined
    p1.distance;
    // undefined

    console.log(Point.displayName);
    // "Point"
    console.log(Point.distance(p1, p2));
    // 7.0710678118654755
  1. 调用类的静态方法,需要使用 类本身调用
  2. 在类里面的,静态方法中调用其他静态方法,需要使用this关键字
    class StaticMethodCall {
        static staticMethod() {
            return 'Static method has been called';
        }
        static anotherStaticMethod() {
            return this.staticMethod() + ' from another static method';
        }
    }
    console.log(StaticMethodCall.staticMethod())
    // 'Static method has been called'

    console.log(StaticMethodCall.anotherStaticMethod())
    // 'Static method has been called from another static method'
  1. 非静态方法中,不能使用this关键字来访问静态方法 (通过类名来调用;通过 this.constructor来调用)
    class StaticMethodCall {
        constructor() {
            console.log(StaticMethodCall.staticMethod());
            // 'static method has been called.'
            console.log(this.constructor.staticMethod());
            // 'static method has been called.'
        }
        static staticMethod() {
            return 'static method has been called.';
        }
    }

this的作用域

.call(null)

function a() {
  console.log(this);
}
a.call(null);  //打印window对象

根据ECMAScript262规范规定:如果第一个参数传入的对象调用者是null或者undefined,call方法将把全局对象(浏览器上是window对象)作为this的值。所以,不管传入null 还是 undefined,其this都是全局对象window。所以,在浏览器上答案是输出 window 对象。

'use strict';
function a() {
    console.log(this);
}
a.call(null); // null
a.call(undefined); // undefined

在严格模式中,null 就是 null,undefined 就是 undefined

使用new构造函数

var obj = { 
  name : 'cuggz', 
  fun : function(){ 
    console.log(this.name); 
  } 
} 
obj.fun()     // cuggz
new obj.fun() // undefined

使用new构造函数时,其this指向的是全局环境window。

调用函数

var a = 1;
function printA(){
  console.log(this.a);
}
var obj={
  a:2,
  foo:printA,
  bar:function(){
    printA();
  }
}

obj.foo(); // 2
obj.bar(); // 1
var foo = obj.foo;
foo(); // 1

解析:

  1. obj.foo(),foo 的this指向obj对象,所以a会输出2;
  2. obj.bar(),printA在bar方法中执行,所以此时printA的this指向的是window,所以会输出1;
  3. foo(),foo是在全局对象中执行的,所以其this指向的是window,所以会输出1;

匿名函数

var x = 3;
var y = 4;
var obj = {
    x: 1,
    y: 6,
    getX: function() {
        var x = 5;
        return function() {
            return this.x;
        }();
    },
    getY: function() {
        var y = 7;
        return this.y;
    }
}
console.log(obj.getX()) // 3
console.log(obj.getY()) // 6

解析:

  1. 我们知道,匿名函数的this是指向全局对象的,所以this指向window,会打印出3;
  2. getY是由obj调用的,所以其this指向的是obj对象,会打印出6。