变量类型和计算
变量类型
值类型和引用类型
1
2
3
41 var a = 100;
2 var b = a;
3 a = 200;
4 console.log(b) // 100我们先定义一个
a = 100;
在定义一个b = a;
然后在把a
赋值为200;
这时候在打印b
,b
的值是 100,每个变量的值都不会相互影响,这就是值类型的特点1
2
3
41 var a = {age:20}
2 var b = a;
3 b.age = 21;
4 console.log(a.age) // 21和上面
值类型
同理,对比一下上面的,但是输出的不一样,是21
,这就是值类型
和引用类型
的区别有什么区别的从内存中来说,值类型
是把每一个值存一个内存地址,比如说a
存成 100,然后b
又存成a
,b
也是 100,然后再把a
改成 200,a
就变成了 200,但是b
的值还是 100,由此可以证明,a
和b
是独立的,不会互相影响但是引用类型就不一样,引用类型是我把
a
赋值为一个对象,这个对象存在另一个地方,a
的内存地址是通过指针指向对象这个地方,然后我们把b
赋值成a
的时候,在更改b.age
的时候,这两个对象是指向一个内存地址的,所以当我们更改b.age = 21
的时候,其实a.age
就已经是 21 了,a
也指向这个对象,这就是值类型和引用类型的区别所在,它为什么要这样做呢,引用类型包括(对象,数组,函数),大家要记住,引用类型有什么特点呢,就是可以无限制扩展属性,可以加很多属性,如过我们属性加了很多之后呢,就会出现内存占用比较大的问题,所以说引用类型中是为了让内存共用一个空间,所以才出现引用的这种方式,对象,数组,函数,都会有这种属性typeof 运算符详解
1
2
3
4
5
6
7
81 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 // functiontypeof
一共有 6 种形式,undefined
、string
、number
、boolean
、object
、function
,需要注意的就是null
,它也是一个object
对象,但是它是一个特殊的,因为它什么都没有,它只是一个空的指针而已,就是它定义了一个null
一个位置,但是它并没有指向任何的引用类型的一个真实的对象,所以它是空的,但是它的类型也是一个object
的类型,如果用来保存比较复杂的数据,可以声明为null
,还有一点需要注意,typeof
只能区分值类型的详细类型,看前4行,如果是引用类型的话,typeof
就没能为力了,引用类型包括,第 5 行的对象,第 6 行的数组,第 7 行的数组,包括第 8 行的函数,但是typeof
能区分出第 8 行函数来,为什么会这样呢,因为函数是一个非常特殊的一个引用类型,在 js 中函数的地位非常非常高,所以typeof
能区分函数
变量计算
值类型-强制类型转换
字符串拼接
1
21 var a = 100 + 10 // 110
2 var b = 100 + '10' // '10010'这就是在字符穿拼接类型时候发生的转化
== 运算符
1
2
31 100 == '100' // true
2 0 == '' // true
3 null == undefined // true是不是很奇怪呢,所以说
==
计算大家一定要慎用,它会进行类型转换,他会让前面的数和后面的数是相等的,如果上面第 3 行都换成===
就不会出现这种问题if 语句
1
2
3
4
5
6
7
8
9
10
11
121 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 }a
和b
都会走if
语句,因为会把a
和b
强制转换成boolean
类型,但是c
就不会走if
语句,因为c
强制类型转换之后''
字符串为false
所以不会走if
语句,所以if
语句会做一个强制类型转换逻辑运算
1
2
3
4
5
6
71 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 | JSON.stringify({a:10,b:20}) |
JSON
只是js
当中的一个内置对象而已,也是内置在js
中的基本语法里面,我们常用的JSON
api
只有两个,第一个是stringify
把对象变成字符串,第二个是parse
把字符串变成对象,JSON
同时也是一种数据格式。
原型和原型链
构造函数
1
2
3
4
5
6
7
81 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 行就是一个this
,this
是一个什么东西,正常构造函数返回的就是一个this
,写上也可以,不写也行,我们看一下new
一个构造函数返回的过程,首先new
的时候把参数传进去,然后new
执行的时候呢,它里面的this
会变成一个空对象,然后第 2 ~ 4 行是一个赋值的过程,最后再把this
给返回,返回之后呢,就赋值给f
了,然后f
就具备了,f.name = 'lisi'
,f.age = 20
,这就是构造函数的一个基本的使用构造函数 - 扩展
1
2
3
41 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
111 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
151 // 构造函数
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
这个构造函数来构造了一个对象,然后printName
是f
扩展的属性,第 14 行在用的时候f.printName()
,f
是一个用Foo
new
出来的一个对象,然后它有什么属性呢,就有一个name
属性第 3 行里面,然后第 10 行,又给f
添加了一个printName
的属性,属性值是一个函数,所以执行的时候打印的就是'zhangsan'
,第 15 行在执行f.alertName()
的时候,f
对象没有这个属性,f
的proto
(隐式原型)指向了Foo
的prototype
(显式原型),上面也说过了,所以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
161 // 构造函数
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(显式原型)
的值,因为f
的proto(隐式原型)
和构造函数中的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
的构造函数,Foo
的prototype(显式原型)
,然后Foo(显式原型)
呢,他也是一个对象,它在这个对象中找toString()
没有,就要向它的这个proto(隐式原型)
中去找了,所以 f.proto.proto,指向的就是这个对象的构造函数了,也就是Object
的构造函数,然后就找到了,我画了一个图大家看一下:大家来看方框的代表的是构造函数,圆角的代表的是一个对象,从左下角往右上角看,左下角,
Foo
是构造函数对不对,然后它new
出来一个f
,然后Foo
本身箭头往右指是一个Foo.prototype(显式原型)
,显示原型就是一个对象,那它new
出来的f
有一个proto(隐式原型)
,也是这个对象,之前说过,new
出来的对象的(隐式原型)
就等于构造函数的(显式原型)
也就是Foo.prototype
也是一个对象,那它的构造函数是谁呢,就是Object
,那Object
也是一个构造函数,那它就肯定有Object.prototype(显式原型)
,也是一个对象,那Foo.prototype
是一个对象,那Foo.prototype
的proto(隐式原型)
肯定就指向了它的构造函数的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 | var arr = []; |
写一个原型链继承的列子
1 | 1 // 动物 |
首先我们先构造一个函数是 Animal
是动物,第 8 行定义了一个 Dog
狗,这两个里面都有一个属性,第 13 行 Dog.prototype
赋值成 new Animal()
,之前已经说过了,Dog.prototype
构造函数的显式原型,它是一个已经有的一个对象,那我们完全可以把这个对象改掉嘛,改成 new Animal()
,new Animal()
返回的就是一个 Animal
的一个对象,就是 eat
的一个属性,也就是说我们把 Dog
的显式原型,赋值成一个对象,这个对象有一个 eat
的属性,第 15 行我们定义一个 hashiqi
为 new Dog()
,然后大家看,这个时候,哈士奇它有什么属性,Dog
本身它有一个 bark
属性,那哈士奇肯定也有这个属性,我们可以通过 hashiqi.eat
去执行,hashiqi.eat
它会从 Dog
的 proto(显式原型)
中去取,Dog
的显式原型就是一个 new Animal()
,已经有了 eat
属性的一个对象,所以 hashiqi
就有了这个属性,但是建议不要再面试中写这样的例子,点 low
zepto (或其他框架) 源码中如何使用原型链
阅读原码是高效提高技能的方式
但不能 “埋头苦钻” 有技巧在其中