我的javascript学习测试

211 阅读12分钟

一. 数组

  • push()
    当push方法中传入的是一个数组/数组变量时,这该数组会直接插到原数组内,而并不会转换成一个个元素,然后再插入
    let arr = [1,2,3]
    arr.push([7,8,9])
    let arr1 = [4,5,6]
    arr.push(arr1)
    console.log(arr)

  • unshift()
    当使用该方法插入元素时,当我们从前端插入多个元素,是按着传入的顺序插入的,而不是写在前面的元素先插入。而且当插入一个数组或数组变量的时候,也是直接插入,不会转换
    let arr = [1,2,3]
    arr.unshift([0],'zh', 'hy')
    console.log(arr)

  • concat()
    当传入的是一个数组/数组变量时,这该数组会转换成一个个元素,组成新数组
    let arr = [1,2,3]
    let arr1 = ['中国', '骄傲']
    let arr2 = arr.concat(['zh', 'hy'])
    console.log(arr2)

  • indexOf()
    数组传入变量,则会将变量的值放入数组,但是我们利用indexOf()方法查找时却找不到,但是我们传入变量时,则会找到
    let a = {name: 'zh'}
    let b = 9
    let c = [6,7]
    let arr1 = [a,b,c, {name: 'hy'}, ['中国']]
    console.log(arr1)
    // 这里查找的是基本数据类型,所以可以查到
    console.log(arr1.indexOf(9))//1

    //当查找引用类型的时候,则只能查找变量,因为变量保存的是引用地址,而非值
    console.log(arr1.indexOf(a))//0
    console.log(arr1.indexOf(c))//2
    console.log(arr1.indexOf({name: 'zh'}))//-1
    console.log(arr1.indexOf(6))//-1
    // 虽然直接访问数组里的引用类型的元素,但是在给位置存的仍然是地址,因此查找不到
    console.log(arr1.indexOf({name: 'hy'}))//-1
    console.log(arr1.indexOf(['中国']))//-1

  • every()
    这个方法只有当该方法的参一的回调函数的返回值都返回true时,它才返回true,否则返回false
    let arr = [1,2,3,4,5,6,7]
    let resTrue = arr.every(() => true)
    let resFalse = arr.every((item) => item > 1)
    console.log(resTrue,resFalse)//true  false
  • reduce()
    该方法的参一回调函数就收4个参数: 前一个值(prev)当前值(cur)项的索引(index)数组对象,参二是:prev的初始化值。如果加上参二,则prev的值就是我们加上的值,如果没有加,则prev初始化的值就是数组的第一项
    //加了初始化值0
    let arr = [1,2,3,4,5,6,7]
    arr.reduce((pre, cur) => {
      console.log(pre)
      return pre + cur
    },0)
    
    //没加初始化值
    let arr = [1,2,3,4,5,6,7]
    arr.reduce((pre, cur) => {
      console.log(pre)
      return pre + cur
    })

二. 正则(RegExp)

  • RegExP的实例属性source
    返回的是正则匹配的字面量的字符串形式,而非构造函数中字符串模板
    let reg1 = /\[bc\]at/i
    let reg2 = new RegExp('\\[bc\\]at','i')
    console.log(reg1.source)
    console.log(reg2.source)

  • RegExp的实例方法exec()
    它只接受一个参数,即要应用模式的字符串,返回值:如果没有匹配到,则返回null,反之,返回一个数组,并且额外包含两个特有的属性indexinput,前者表示配到的字符串开始的下标值,后者表示应用正则表达式的字符串。而数组中,第一项是匹配到的字符串,剩下的都是与模式中的捕获组(就是用()括起来的正则部分)匹配的字符串。
    利用这个方法来多次匹配同一个字符串时,使用全局匹配和不适用全局匹配有很大差异

    没有全局匹配标志时,多次匹配同一个字符串,每一次都会从头开始匹配,而不会接着上一次匹配
    
     let str = "cat, bat, fat, dat"
    // 非全局匹配
    let reg = /.at/
    let res1 = reg.exec(str)
    console.log(res1[0])//cat
    console.log(res1.index)//0
    console.log(res1.input)//"cat, bat, fat, dat"
    //这个属性的意思是开始匹配下一项时的位置
    console.log(reg.lastIndex)//0
    console.log(res1)
    console.log('-----------------------------------')
    let res2 = reg.exec(str)
    console.log(res2[0])//cat
    console.log(res2.index)//0
    console.log(res2.input)//"cat, bat, fat, dat"
    console.log(reg.lastIndex)//0
    console.log(res2)

    有全局匹配标志时,多次匹配同一个字符串,每一次调用该方法匹配都会接着在上一次匹配到的字符串后继续匹配,而不会从第一个字符开始匹配
    let reg = /.at/g
    let res1 = reg.exec(str)
    console.log(res1[0])//cat
    console.log(res1.index)//0
    console.log(res1.input)//"cat, bat, fat, dat"
    //这个属性的意思是开始匹配下一项时的位置
    console.log(reg.lastIndex)//3
    console.log(res1)
    console.log('-----------------------------------')
    let res2 = reg.exec(str)
    console.log(res2[0])//bat
    console.log(res2.index)//5
    console.log(res2.input)//"cat, bat, fat, dat"
    console.log(reg.lastIndex)//8
    console.log(res2)

三. 函数

  • 如果同时使用函数声明和函数表达式(当变量名和函数不同名时),那么我们调用的时候只能用表达式变量调用函数
    let sum = function add(x1, x2) {
      return x1 + x2
    }

    console.log(sum(1,2))
    console.log(add(1,2))

  • arguments的callee属性,该属性是一个指针,指向拥有这个arguments对象的函数,所以可以将他利用在递归函数上,来减少递归函数与函数名的耦合度
    下面我们来看一下阶乘函数的递归算法
    function factorial(num) {
      if(num <= 1) {
        return 1
      }else {
      //未与函数名联系起来
        return num * arguments.callee(num - 1)
      }
    }
    //将函数的引用复制给一个变量保存
    let trueFactorial = factorial
    factorial = function (num) {
      return num
    }
    console.log(trueFactorial(5))
    console.log(factorial(5))//5

注意: 在将原方法重写时,一定要用函数表达式,不能用函数声明,因为函数声明式定义函数,他会把声明提升,但是函数表达式不会。而且arguments.callee属性在严格模式下会报错

  • 函数对象的属性caller
    这个属性保存着调用当前函数的函数的引用(注意:是调用当前函数的函数)即一层嵌套函数
    function outer() {
      inner()
    }
    
    //这里显示的是inner函数,而非outer
    function inner() {
      foo()
      function foo() {
        console.log(foo.caller)
      }
    }

    outer()

注意: es5还定义了arguments.caller属性,在严格模式下访问它会导致错误,而在非严格模式下始终为undefined,定义它是为了区分它与函数的caller属性

  • 函数的length属性
    获取函数形参的个数

  • 在严格模式下,未指定环境对象而直接调用函数,则this指向的是undefined

四. 字符串

  • slice(),substr(),substring()方法的区别

这三个方法均可接受两个参数,第一个参数都是开始截取的位置,而slice(),substring()方法的第二个参数是截取的结束位置,substr()方法则是截取字符串的个数

    let str = "hello world"
    // slice方法
    let res1 = str.slice(1, 6)
    console.log(res1)
    // substring方法
    let res2 = str.substring(1, 6)
    console.log(res2)
    //substr方法
    let res3 = str.substr(1, 6)
    console.log(res3)

当传递的参数是负数的情况下,slice()方法将传入的负值与字符串长度相加来确定其位置。substr()方法只有第一个参数是与串长相加来确定位置,而第二个参入的负数转为0。substring()方法会把所有的负数都转为0,并且转化后如果第二个参数小于第一个参数,那么它将会自动交换下标位置。

   let str = "hello world"
   let res1 = str.slice(3,-4)
   console.log(res1)//lo w
   //substr方法
   let res2 = str.substr(3,-4)
   console.log(res2)//""
   // substring方法
   let res3 = str.substring(3,-4)
   console.log(res3)//hel

*match()
该方法接收一个参数,该参数可以是字符串也可以是字符串,返回值和调用正则的exec()方法一样。

    let str = "hello world"
    let res = str.match('o')
    console.log(res)

  • charAt(), charCodeAt(), String.fromCharCode()
    charAt()是一个实例方法,当传入下标时,返回对应位置的字符串
    charCodeAt()是一个实例方法,当传入下标时,返回对应位置的字符串的字符编码 String.fromCharCode()是一个静态方法,它可以接受任意个字符编码,返回对应字符编码的字符串
    let str = "hello world"
    // charAt方法
    console.log(str.charAt(2))
    //charCodeAt方法
    console.log(str.charCodeAt(2))
    // fromCharCode方法
    console.log(String.fromCharCode(73,32,108,111,118,101,32,121,111,117))

五. 单体内置对象

  • Global对象
    这个对象就很奇怪,不管你从那种角度上看,这个对象多是不存在的。其实,不属于任何其他对象的属性和方法,最终都是他的属性和方法(isNaN(), isFinite(), parseInt(), parseFloat()....)
  • URI编码方法
    该方法可以对URI(通用资源标识符)进行编码,以便发送给浏览器。有效的URI中不能包含某些特殊的字符,例如空格。则该方法用特殊的UTF-8编码替换掉这些特殊字符。
    • encodeURI() 主要用于整个URI,因为它不会对本身属于URI的特殊字符进行编码
    • encodeURIComponent() 主要用于编码URI中某一段,因为他会对URI中任何非标准字符进行编码。
    • decodeURI()用于对encodeURI编码的字符解码
    • decodeURIComponent()能够解码任何特殊字符的编码
        // 编码
        let uri1 = 'https://www.bai du.com'
        console.log(encodeURI(uri1))
        console.log(encodeURIComponent(uri1))
    
        //解码
        let uri2 = 'https%3A%2F%2Fwww.bai%20du.com'
        console.log(decodeURI(uri2))
        console.log(decodeURIComponent(uri2))
    

    一般来说,我们使用encodeURIComponent()方法更多,因为在实践中更常见的是查询字符串参数而不是对基础URI进行编码

    六. Math对象

  • Math的属性
    • Math.E: 自然对数的底数
    • Marh.LN10:10的自然对数
    • Math.Ln2:2的自然对数
    • Math.LOG2E:以2为底e的对数
    • Math.LOG10E:以10为底e的对数
    • Math.PI:π的值
    • Math.SQRT1_2:1/2的平方根
    • Math.SQRT2:2的平方根
  • 确定数组中最大值,最小值的简单方法
    • 利用三点运算符
    • 利用apply调用max,min函数
        let arr = [3,87,26,0,44,23]
        let max1 = Math.max.apply(this,arr)
        let max2 = Math.max(...arr)
        console.log(max1,max2)
    

    七. 创建对象

  • 工厂函数模式:解决了创建对象像是对象问题,但是没有解决对象识别问题(即怎么知道一个对象的类型)。
  • 构造函数模式:创建自定义的构造函数意味着将来可以将它的实例标识为一种特定的类型。

    任何函数,只要通过new操作符来调用,那他就可以作为构造函数。反之,任何函数,如果不通过new操作符来调用,那他和普通函数是一样的。

        function Foo(name) {
          this.name = name
          this.show = function() {
            return console.log(this.name)
          }
        }
        // 当做构造函数使用
        let foo = new Foo('zh')
        foo.show()
        // 作为普通函数调用
        Foo('zh')
        show()
        //在另一个对象的作用域中调用
        let obj = {}
        Foo.call(obj,'zh')
        obj.show()
    

    构造函数的主要问题在于,来此调用同一个构造函数来创建实例,每个方法都要在每个实例中重新创建一遍,浪费内存空间。

  • 原型模式:创建自定义的构造函数意味着将来可以将它的实例标识为一种特定的类型。(即将所有共享的属性和方法都写在构造函数的原型上)
    • isPrototypeOf()判断实例内部属性[[Prototype]] (即__proto__)是否指向该原型对象
        function Bar(name) {
          this.name = name
        }
    
        let bar = new Bar('zh')
        let res = Bar.prototype.isPrototypeOf(bar)
        console.log(res)//true,表示bar 内部属性是指向Bar.prototype原型对象的
    
    • 实例只能访问原型中的值,但是不能修改原型中的值,如果实例添加了一个与原型中属性同名的属性,那么添加的这个属性只会阻止我们访问原型中的那个属性,但是不会修改那个属性。
        function Bar() {
    
        }
        Bar.prototype.name = 'zh'
    
        let bar1 = new Bar()
        let bar2 = new Bar()
        bar1.name = 'hy'
        console.log(bar1.name)//hy
        //bar2访问name属性并没有被修改                                               
        console.log(bar2.name)//zh
    
    • hasOwnProperty() 用来检测一个属性是属于实例本身还是存在于原型中。当被检测属性来自于实例时,返回true
        function Bar() {
    
        }
        Bar.prototype.name = 'zh'
    
        let bar1 = new Bar()
        let bar2 = new Bar()
        bar1.name = 'hy'
        let res1 = bar1.hasOwnProperty('name')
        let res2 = bar2.hasOwnProperty('name')
        console.log(res1)//true
        console.log(res2)//false
    
    • in操作符
      单独使用in时,只要对象能有访问到检测属性(不管是原型中的还是实例自己的),它都返回true
        //封装一个检测属性是否属于原型的函数
        function hasPrototypeProperty(obj, key) {
          return !obj.hasOwnProperty(key) && (key in obj)
        }
        //测试
        function Bar() {
    
        }
        Bar.prototype.name = 'zh'
        console.log(Object.getOwnPropertyDescriptor(Bar.prototype,'name'))
    
        let bar1 = new Bar()
        let bar2 = new Bar()
        bar1.name = 'hy'
        res1 = hasPrototypeProperty(bar1, 'name')
        res2 = hasPrototypeProperty(bar2, 'name')
    
        console.log(res1)//false
        console.log(res2)//true
    

    原型模式存在的问题

    • 不能向构造函数传参。导致每个实例的默认属性值都一样
    • 原型中的所有属性和方法都是共享的,这种共享对于函数和基本类型都很好(如果实例中定义了与原型中同名的属性和方法,则它们会屏蔽原型上的方法和属性)。但是对于应用类型值,则不是我们想要的结果。当改变一个实例中的应用类型属性值时(即改变原型中的属性值),其他属性访问该属性时,值也会变化。
        function Person() {
    
        }
        Person.prototype = {
          constructor: Person,
          name: 'zh',
          age: 20,
          height: 175,
          weight: 50,
          friends: ['jcl', 'hcy'],
          show() {
            return `我叫${this.name},今年${this.age},体重${this.weight}斤,身高${this.height}cm`
          }
        }
        let p1 = new Person()
        let p2 = new Person()
        p1.name = 'hy'
        //通过p1改变数组frieds,则p2访问该数组时,也被改变
        p1.friends.push('zjh')
        p1.show = function() {
          return '111'
        }
        console.log(p1.show())
        console.log(p2.name)
        console.log(p2.friends)
        console.log(p2.show())
    

  • 构造函数模式和原型模式组合
    • 构造函数中用于定义实例的属性,原型上用于定义共有的属性和方法
  • 动态原型模式
    • 通过在构造函数中使用if判断来表明是否创建该属性或方法。
  • 寄生构造函数模式
    • 在构造函数中初始化一个对象,并给对象添加属性和方法,然后返回该对象。
  • 稳妥构造函数模式
    • 稳妥对象:没有公共属性,而且其方法中不能引用this的对象。所以它提供了安全的环境。
    • 它类似于寄生构造函数模式,区别在于:创建新对象的实例方法不能应用this,不能使用new操作符调用构造函数。
        function Person(name, age, job) {
          let obj = new Object()
          obj.show = function() {
            console.log(name)
          }
          return obj
        }
    
        let p = Person('zh', 20, 'student')
        p.show()//只有调用show方法才可以访问其数据成员
    

    注意:使用稳妥构造函数模式,寄生构造函数模式创建的实例对象与构造函数之间没有任何关系。所以不能依赖instanceof来判断对象的类型。