变量类型和计算
变量类型
值类型和引用类型
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中的基本语法里面,我们常用的JSONapi只有两个,第一个是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是一个用Foonew出来的一个对象,然后它有什么属性呢,就有一个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 (或其他框架) 源码中如何使用原型链
阅读原码是高效提高技能的方式
但不能 “埋头苦钻” 有技巧在其中