程序亦非猿

Javascript 基础需要掌握的技术点

2018/06/21 Share

变量类型和计算

  • 变量类型

    • 值类型和引用类型

      1
      2
      3
      4
      1  var a = 100;
      2 var b = a;
      3 a = 200;
      4 console.log(b) // 100

      我们先定义一个 a = 100; 在定义一个 b = a; 然后在把 a 赋值为 200; 这时候在打印 bb 的值是 100,每个变量的值都不会相互影响,这就是值类型的特点

      1
      2
      3
      4
      1  var a = {age:20}
      2 var b = a;
      3 b.age = 21;
      4 console.log(a.age) // 21

      和上面 值类型 同理,对比一下上面的,但是输出的不一样,是 21,这就是 值类型引用类型 的区别有什么区别的从内存中来说,值类型 是把每一个值存一个内存地址,比如说 a 存成 100,然后 b 又存成 ab 也是 100,然后再把 a 改成 200,a 就变成了 200,但是 b 的值还是 100,由此可以证明,ab 是独立的,不会互相影响

      但是引用类型就不一样,引用类型是我把 a 赋值为一个对象,这个对象存在另一个地方,a 的内存地址是通过指针指向对象这个地方,然后我们把 b 赋值成 a 的时候,在更改 b.age 的时候,这两个对象是指向一个内存地址的,所以当我们更改 b.age = 21 的时候,其实 a.age 就已经是 21 了,a 也指向这个对象,这就是值类型和引用类型的区别所在,它为什么要这样做呢,引用类型包括(对象,数组,函数),大家要记住,引用类型有什么特点呢,就是可以无限制扩展属性,可以加很多属性,如过我们属性加了很多之后呢,就会出现内存占用比较大的问题,所以说引用类型中是为了让内存共用一个空间,所以才出现引用的这种方式,对象,数组,函数,都会有这种属性

    • typeof 运算符详解

      1
      2
      3
      4
      5
      6
      7
      8
      1  typeof undefined // undefined
      2 typeof 'abc' // string
      3 typeof 123 // number
      4 typeof true // boolean
      5 typeof {} // object
      6 typeof [] // object
      7 typeof null // object
      8 typeof console.log // function

      typeof 一共有 6 种形式,undefinedstringnumberbooleanobjectfunction,需要注意的就是 null,它也是一个 object 对象,但是它是一个特殊的,因为它什么都没有,它只是一个空的指针而已,就是它定义了一个 null 一个位置,但是它并没有指向任何的引用类型的一个真实的对象,所以它是空的,但是它的类型也是一个 object 的类型,如果用来保存比较复杂的数据,可以声明为 null,还有一点需要注意,typeof 只能区分值类型的详细类型,看前4行,如果是引用类型的话,typeof 就没能为力了,引用类型包括,第 5 行的对象,第 6 行的数组,第 7 行的数组,包括第 8 行的函数,但是 typeof 能区分出第 8 行函数来,为什么会这样呢,因为函数是一个非常特殊的一个引用类型,在 js 中函数的地位非常非常高,所以 typeof 能区分函数

  • 变量计算

    • 值类型-强制类型转换

      • 字符串拼接

        1
        2
        1  var a = 100 + 10 // 110
        2 var b = 100 + '10' // '10010'

        这就是在字符穿拼接类型时候发生的转化

      • == 运算符

        1
        2
        3
        1  100 == '100' // true
        2 0 == '' // true
        3 null == undefined // true

        是不是很奇怪呢,所以说 == 计算大家一定要慎用,它会进行类型转换,他会让前面的数和后面的数是相等的,如果上面第 3 行都换成 === 就不会出现这种问题

      • if 语句

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        1  var a = true;
        2 if (a) {
        3 // ....
        4 }
        5 var b = 100;
        6 if (b) {
        7 // ....
        8 }
        9 var c = '';
        10 if (c) {
        11 // ....
        12 }

        ab 都会走 if 语句,因为会把 ab 强制转换成 boolean 类型,但是 c 就不会走 if 语句,因为 c 强制类型转换之后 '' 字符串为 false 所以不会走 if 语句,所以 if 语句会做一个强制类型转换

      • 逻辑运算

        1
        2
        3
        4
        5
        6
        7
        1  console.log(10 && 0);	// 0
        2 console.log('' || 'abc'); // 'abc'
        3 console.log(!window.abc); // true
        4
        5 // 判断一个变量会被当作 true 还是 false
        6 var a = 100;
        7 console.log(!!a) // true

        第 1 行 10 && 0 它返回的是 0,它转换的是什么呢,就是把 10 转换成 true 了,所以会继续走后面的,所以是 0,第 2 行空字符串转换成 false 了,是 ‘abc’,第三行 !window.abc,经过 !转换就变成 true 了,这就是与、或、非,这三个逻辑运算符的转换,最后的是两个!就是 true

js 中的内置函数有哪些

  • Object

  • Array

  • Boolean

  • Number

  • String

  • Function

  • Date

  • RegExp

  • Error

    我们 js 作为一个语言来说,你不考虑浏览器环境,不考虑 window 啊,也不考虑 Node.js 那些东西,global 啊,等,不考虑运行环境,js 作为一个单纯的语言来讲,它其实是内置了一些东西,就是上面的函数而已,那么内置的函数有什么作用呢,这就是原型和原形链上面的问题了,下面会讲,你只要知道 js 内置了这些函数就行了

如何理解 JSON

1
2
JSON.stringify({a:10,b:20})
JSON.parse('{"a":10,"b":20}')

JSON 只是 js 当中的一个内置对象而已,也是内置在 js 中的基本语法里面,我们常用的 JSON api 只有两个,第一个是 stringify 把对象变成字符串,第二个是 parse 把字符串变成对象,JSON 同时也是一种数据格式。

原型和原型链

  • 构造函数

    1
    2
    3
    4
    5
    6
    7
    8
    1  function Foo(name, age) {
    2 this.name = name;
    3 this.age = age;
    4 this.class = 'class-1'
    5 // return this 默认有这一行
    6 }
    7 var f = new Foo('lisi', 20)
    8 // var f1 = new Foo('zhangsan', 22) // 创建多个对象

    构造函数有一个特点就是第 1 行构造函数 Foo 的 F 是大写的,意识就是,你基本上要是见到大写字母开头的函数,那这个函数就是构造函数,我们以后写代码的时候,用到构造函数的时候,也要用大写字母开头,如果不用大写字母开头,将会给其它阅读你代码的人,带来很大的麻烦,而且这样不良好的代码习惯,不会受那些比较高级的程序员的待见,从第 2 行 到第 4 行,我们是进行了一个赋值, 第 7 行我们定义了一个 f 然后 new 了一个实例的过程,我们把 'lisi' 这个字符串传进去,20 数字,然后 f 就是一个对象,第 8 行 也是定义一个变量给他 new 一个实例来,就是说通过 Foo 这个构造函数可以构造出很多个不同的实例来,构造函数这就像一个模版的机制,这个时候它是怎么执行的呢,咱们用第 7 行做一个列子,第 7 行里面,new 完实例之后,传值之后,第一行里面执行的时候,name 就是 'lisi'age 就是 20,第 2 行,和第 3 行,就是一个赋值的过程,第 5 行就是一个 thisthis 是一个什么东西,正常构造函数返回的就是一个 this,写上也可以,不写也行,我们看一下 new 一个构造函数返回的过程,首先 new 的时候把参数传进去,然后 new 执行的时候呢,它里面的 this 会变成一个空对象,然后第 2 ~ 4 行是一个赋值的过程,最后再把 this 给返回,返回之后呢,就赋值给 f 了,然后 f 就具备了,f.name = 'lisi'f.age = 20,这就是构造函数的一个基本的使用

  • 构造函数 - 扩展

    1
    2
    3
    4
    1  var a = {}  // 其实是 var a = new Object()的语法糖
    2 var a = [] // 其实是 var a = new Array()的语法糖
    3 function Foo(){...} // 其实是 var Foo = new Function(...)
    4 使用 instanceof 判断一个函数是否是一个变量的构造函数

    第 1 行 a 的构造函数是谁,第 2 行 a 的构造函数是谁,第 3 行 Foo 的构造函数是谁,所有的引用类型都有构造函数,对象也有构造函数,数组也有构造函数,第 1 行的构造函数是 Object,第 2 行的构造函数是 Array,第 3 行的构造函数是 Function,但是呢虽然这么说,在开发中都推荐使用前面的书写方式,绝对不推荐后面的书写方式,只是让大家知道这个流程而已,也许面试的时候也会用到。

  • 原型规则和示例

    • 所有的引用类型(数组,对象,函数),都具有对象特性,即可自由扩展属性(除了 'null' 外)

    • 所有的引用类型(数组,对象,函数),都有一个 proto (隐式原型) 属性,属性值是一个普通的对象

    • 所有的函数,都有一个 prototype (显式原型) 属性,属性值也是一个普通的对象

    • 所有的引用类型(数组,对象,函数),proto 属性值都指向它的构造函数的 prototype 属性值

    • 当试图得到一个引用类型的某个属性值时,如果这个变量本身没有这个属性,那么它会去 proto (即它的构造函数的 prototype )中去寻找。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      1   var obj = {}; obj.a = 100;
      2 var arr = []; arr.a = 100;
      3 function fn () {}
      4 fn.a = 100;
      5 console.log(obj.__proto__) // 引用类型
      6 console.log(arr.__proto__) // 引用类型
      7 console.log(fn.__proto__)
      8
      9 console.log(fn.prototype) // 函数
      10
      11 console.log(obj.__proto === Object.prototype) // 相等
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      1   // 构造函数
      2 function Foo(name, age) {
      3 this.name = name;
      4 }
      5 Foo.prototype.alertName = function () {
      6 alert(this.name)
      7 }
      8 // 创建示例
      9 var f = new Foo('zhangsan');
      10 f.printName = function () {
      11 console.log(this.name)
      12 }
      13 // 测试
      14 f.printName() // zhangsan
      15 f.alertName() // zhangsan

      第 2~4 行定义了一个构造函数,看第 5 行之前说过了,所有的函数都有一个 prototype(显式原型),而显式原型是一个普通对象,那我完全可以对这个对象扩展属性,然后我们给它加了一个 alertName 的属性,并且赋值了一个函数,函数的内容就是 alert(this.name),然后第 9 行,我通过 Foo 这个构造函数来构造了一个对象,然后 printNamef 扩展的属性,第 14 行在用的时候 f.printName()f 是一个用 Foo new 出来的一个对象,然后它有什么属性呢,就有一个 name 属性第 3 行里面,然后第 10 行,又给 f 添加了一个 printName 的属性,属性值是一个函数,所以执行的时候打印的就是 'zhangsan',第 15 行在执行 f.alertName() 的时候,f 对象没有这个属性,fproto (隐式原型)指向了 Fooprototype (显式原型),上面也说过了,所以 f 也会有 alertName 这个属性,加上一句就是 f 中的 this 永远指向 f本身。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      // 循环对象自身的属性
      var item
      for (item in f) {
      // 高级浏览器已经在 for in 中屏蔽了来自原型的属性
      // 但是建议大家加一个判断,保证程序的健壮性
      if(f.hasOwnProperty(item)){
      console.log(item)
      }
      }

      for in 是可以对一个对像进行循环的,那如果对 f 进行循环的话,f 自己也算上,原型也算上,一共三个属性,但是我又不想要原型上的属性 ,只想循环自身的属性,就用上面的这中方法,就是遍历的时候,判断一下,是不是能通过,hasOwnProperty 的验证,如果通过之后就说明 它是 f 自身的属性,如果没通过就说明是原型的属性。

  • 原型链

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    1   // 构造函数
    2 function Foo(name, age) {
    3 this.name = name;
    4 }
    5 Foo.prototype.alertName = function () {
    6 alert(this.name)
    7 }
    8 // 创建示例
    9 var f = new Foo('zhangsan');
    10 f.printName = function () {
    11 console.log(this.name)
    12 }
    13 // 测试
    14 f.printName() // zhangsan
    15 f.alertName() // zhangsan
    16 f.toString() // 要去 f.__proto__.__proto__ 中查找

    在上面原型规则和示例中说了,第 14 行 f.printName() 获取的是 f 本身的一个属性,也执行了,第 15 行 f.alertName() 中获取的是 proto(隐式原型) 的属性,也就是 f 构造函数 prototype(显式原型) 的值,因为 fproto(隐式原型) 和构造函数中的 prototype(显式原型) 是相等的,所以也执行了,然后第 16 行大家想一下是什么东西,f.toString(),当执行这个函数的时候,大家回忆一下之前原型规则里面的第 5 个规则,当试图得到一个引用类型的某个属性值时,如果这个变量本身没有这个属性,那么它会去 proto (即它的构造函数的 prototype )中去寻找,它自身的 proto(隐式原型) 就是它构造函数的 prototype(显式原型),也就是构造函数是第 5 行 Foo(显式原型) 就是 Foo.prototype 中去找,然后 Foo.prototype 构造函数的 (显式原型) 中并没有 toString() 这个东西,对不对,但是重点来了,Foo.prototype 就是构造函数的 (显式原型),它也是一个对象对不对,然后在它中去找 toString() 这个方法,那是不是也要向 Foo.prototype 中的 proto(隐式原型) 中去找,对不对,之前说过一个函数都会有 prototype(显式原型)(显式原型) 它指向的是一个普通的对象,那普通的对象,它的构造函数是什么呢,构造函数肯定是 Object,然后刚才说了,要在 Foo.prototype 中去找 toString(),那指向的肯定就是它本身的 proto(隐式原型),也就是说我们要去,Object 中的 prototype(显式原型) 中去找 toString(),然后就找到了,所以我们第 16 行有一个注释,要去 f.proto.proto 中查找,因为 f.proto(隐式原型) 只是 f 的构造函数,Fooprototype(显式原型),然后 Foo(显式原型) 呢,他也是一个对象,它在这个对象中找 toString() 没有,就要向它的这个 proto(隐式原型) 中去找了,所以 f.proto.proto,指向的就是这个对象的构造函数了,也就是 Object 的构造函数,然后就找到了,我画了一个图大家看一下:

    Alt text

    大家来看方框的代表的是构造函数,圆角的代表的是一个对象,从左下角往右上角看,左下角,Foo 是构造函数对不对,然后它 new 出来一个 f,然后 Foo 本身箭头往右指是一个 Foo.prototype(显式原型),显示原型就是一个对象,那它 new 出来的 f 有一个 proto(隐式原型),也是这个对象,之前说过,new 出来的对象的 (隐式原型) 就等于构造函数的 (显式原型) 也就是 Foo.prototype 也是一个对象,那它的构造函数是谁呢,就是 Object,那 Object 也是一个构造函数,那它就肯定有 Object.prototype(显式原型),也是一个对象,那 Foo.prototype 是一个对象,那 Foo.prototypeproto(隐式原型) 肯定就指向了它的构造函数的prototype(显式原型),就是 Object.prototype,那 Object.prototype,也是一个对象,那这里有一个特例,那它的 proto(隐式原型) 指向的就是一个 null,这是 javascript 为了避免死循环做的一个特例,到这就截止了,然后我们来看代码第 16 行的 f.toString() 是怎么找的呢,我先从 f 上找,f 上找不到,去它的 proto(隐式原型),顺着弧线往上走,找到了 Foo.prototype,也没有,没有怎么办,在顺着这个弧线往上找,找到了 Object.prototype,然后就找到了 toString(),就执行了这个方法,有点绕,大家如果不明白结合结合一下代码和图再看看。

  • Instanceof

    用于判断引用类型属于哪个构造函数的方法,比如说我们上一个代码的时候,第 9 行 new Foo 是 f,那我们就通过

    1
    2
    3
    4
    5
    >1  f instanceof Foo
    >2 f 的 __proto__一层一层往上,能否对应到 Foo.prototype
    >3 再试着判断 f instanceof Object // 正确
    >4 虽然说 f 是 Foo new 出来的对象,但是呢,Foo 的上一层是 Object,所以第 3 行肯定是正确的
    >

如何准确的判断一个变量是不是数组

1
2
3
var arr = [];
arr instanceof Object // true
typeof arr // Object, typeof 是无法判断是数组的

写一个原型链继承的列子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1   // 动物
2 function Animal() {
3 this.eat = function() {
4 console.log('animal eat');
5 }
6 }
7 // 狗
8 function Dog() {
9 this.bark = function() {
10 console.log('dog bark');
11 }
12 }
13 Dog.prototype = new Animal();
14 // 哈士奇
15 var hashiqi = new Dog();

首先我们先构造一个函数是 Animal 是动物,第 8 行定义了一个 Dog 狗,这两个里面都有一个属性,第 13 行 Dog.prototype 赋值成 new Animal(),之前已经说过了,Dog.prototype 构造函数的显式原型,它是一个已经有的一个对象,那我们完全可以把这个对象改掉嘛,改成 new Animal()new Animal() 返回的就是一个 Animal 的一个对象,就是 eat 的一个属性,也就是说我们把 Dog 的显式原型,赋值成一个对象,这个对象有一个 eat 的属性,第 15 行我们定义一个 hashiqinew Dog(),然后大家看,这个时候,哈士奇它有什么属性,Dog 本身它有一个 bark 属性,那哈士奇肯定也有这个属性,我们可以通过 hashiqi.eat 去执行,hashiqi.eat 它会从 Dogproto(显式原型) 中去取,Dog 的显式原型就是一个 new Animal(),已经有了 eat 属性的一个对象,所以 hashiqi 就有了这个属性,但是建议不要再面试中写这样的例子,点 low

zepto (或其他框架) 源码中如何使用原型链

  • 阅读原码是高效提高技能的方式

  • 但不能 “埋头苦钻” 有技巧在其中

CATALOG
  1. 1. 变量类型和计算
  2. 2. js 中的内置函数有哪些
  3. 3. 如何理解 JSON
  4. 4. 原型和原型链
  5. 5. 如何准确的判断一个变量是不是数组
  6. 6. 写一个原型链继承的列子
  7. 7. zepto (或其他框架) 源码中如何使用原型链