一、变量类型和计算
- JS中使用typeof能得到的哪些类型
- 何时使用
===
何时使用==
- JS中有哪些内置函数
- JS变量按照存储方式区分为哪些类型,并描述其特点
- 如何理解JSON
变量类型
JS中有7种内置类型,7种内置类型又分为两大类型
- 基本类型/值类型:
null
、undefined
、boolean
、number
、string
、symbol
- 对象/引用类型:
object
基本类型/值类型
是什么
把每一个值存放在对应变量内存的位置,数据分块存放在内存中,数据之间不会相互影响
var a = 100;
var b = a;
a = 200;
console.log(b); // 100
条件
原始类型存储的都是值,是没有函数可以调用的,比如undefined.toString()
'1'.toString()
是可以使用的。在这种情况下,'1'
已经不是原始类型了,而是被强制转换成了 String
类型(大写)也就是引用类型,所以可以调用 toString
函数
JS 的number
类型是浮点类型的,在使用中会遇到某些 Bug
NaN
也属于number
类型,并且NaN
不等于自身0.1 + 0.2 !== 0.3
string
类型是不可变的,无论你在string
类型上调用何种方法,都不会对值有改变
对于null
来说,很多人会认为他是个引用类型,其实这是错误的。虽然 typeof null
会输出 object
,但是这只是 JS 存在的一个悠久 Bug。在 JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,000
开头代表是对象,然而 null
表示为全零,所以将它错误的判断为 object
。虽然现在的内部类型判断代码已经改变了,但是对于这个 Bug 却是一直流传下来
对象/引用类型
是什么
当你创建了一个引用类型a的时候,计算机会在内存中帮我们开辟一个空间来存放值,但是我们需要找到这个空间,这个空间会拥有一个地址(指针),引用类型a存储的就是这个地址
const a = [];
对于常量 a
来说,假设内存地址(指针)为 #001
,那么在地址 #001
的位置存放了值 []
,常量 a
存放了地址(指针) #001
当我们将变量赋值给另外一个变量时,复制的是原本变量的地址(指针),也就是说当前变量 b
存放的地址(指针)也是 #001
,当我们进行数据修改的时候,就会修改存放在地址(指针) #001
上的值,也就导致了两个变量的值都发生了改变
const a = []
const b = a
b.push(1)
产生原因
若a赋值成一个对象,特别大,a再赋值给b,b也会占很大的空间,不合理,所以引用类型是为了让内存共用空间,好几个变量共用1个内存块,节省内存空间,赋值只是变量指针的赋值,并不是每次赋值都把对象真正的值复制一份,所以值的修改相互干预
分类边界
数组array
、函数function
、对象object
目的
无限制扩展属性,比如说对象有个age属性,可以加第2个属性name属性
组合
深浅拷贝
条件
函数参数是对象的情况
function test(person) {
person.age = 26
person = {
name: 'yyy',
age: 30
}
return person;
}
const p1 = {
name: 'yck',
age: 25
}
const p2 = test(p1)
console.log(p1) // -> ?
console.log(p2) // -> ?
- 首先,函数传参是传递对象指针的副本
- 到函数内部修改参数的属性这步,我相信大家都知道,当前
p1
的值也被修改了 - 但是当我们重新为
person
分配了一个对象时就出现了分歧,请看下图
所以最后 person
拥有了一个新的地址(指针),也就和 p1
没有任何关系了,导致了最终两个变量的值是不相同的
typeof运算符
是什么
只能区分基本类型的详细类型,引用类型无法细分
分类边界
typeof
对于基本类型来说,除了null
都可以显示正确的类型
typeof null // 'object' BUG
typeof undefined // 'undefined'
typeof true // 'boolean'
typeof 1 // 'number'
typeof '1' // 'string'
typeof Symbol() // 'symbol'
typeof
对于引用类型来说,除了函数都会显示object
,所以说typeof
并不能准确判断引用变量到底是什么类型。因为函数是一个十分特殊的引用类型,在JS中函数的地位非常高,所以需要在任何地方轻松判断出这个是函数,所以typeof单独把函数列出来
typeof [] // 'object'
typeof {
} // 'object'
typeof console.log // 'function'
组合
instanceof
类型转换
是什么
在JS中类型转换只有三种情况
- 转换为布尔值
- 转换为数字
- 转换为字符串
组合
转Boolean
在条件判断时,除了undefined
、null
、false
、NaN
、''
、0
、-0
,其他所有值都转为true
,包括所有对象
引用类型转基本类型
引用类型在转换类型的时候,会调用内置的 [[ToPrimitive]]
函数,对于该函数来说,算法逻辑一般来说如下
- 如果已经是基本类型了,那就不需要转换了
- 调用
x.valueOf()
,如果转换为基础类型,就返回转换的值 - 调用
x.toString()
,如果转换为基础类型,就返回转换的值 - 如果都没有返回基本类型,就会报错
当然你也可以重写 Symbol.toPrimitive
,该方法在转原始类型时调用优先级最高
let a = {
valueOf() {
return 0
},
toString() {
return '1'
},
[Symbol.toPrimitive]() {
return 2
}
}
1 + a // => 3
四则运算
目的
- 字符串拼接
- 运算
组合
- 加法运算中一方为字符串,那么就会把另一方也转换为字符串
- 加法运算中一方不是数字或字符串,那么会将它转换为数字或字符串
1 + '1' // '11'
true + true // 2
4 + [1,2,3] // "41,2,3"
- 对于第一行代码来说,触发特点一,所以将数字
1
转换为字符串,得到结果'11'
- 对于第二行代码来说,触发特点二,所以将
true
转为数字1
- 对于第三行代码来说,触发特点二,所以将数组通过
toString
转为字符串1,2,3
,得到结果41,2,3
条件
对于加法还需要注意这个表达式 'a' + + 'b'
'a' + + 'b' // -> "aNaN"
因为 + 'b'
等于 NaN
,所以结果为 "aNaN"
Tips
- 用
+ '1'
的形式来快速获取number
类型 - 用
!!
判断变量会被当做true
还是false
那么对于除了加法的运算符来说,只要其中一方是数字,那么另一方就会被转为数字
4 * '3' // 12
4 * [] // 0
4 * [1, 2] // NaN
比较运算符
是什么
== > < !=
组合
如果是对象,就通过 toPrimitive
转换对象
如果是字符串,就通过 unicode
字符索引来比较
let a = {
valueOf() {
return 0
},
toString() {
return '1'
}
}
a > -1 // true
在以上代码中,因为 a
是对象,所以会通过 valueOf
转换为原始类型再比较值
题目解答
- JS中使用typeof能得到的哪些类型
typeof可以识别6种数据类型:number、string、boolean、object、function、undefined
- 何时使用
===
何时使用==
使用jQuery源码中推荐的写法,当obj.a为null或undefined时,看一个对象的属性是否存在或看一个函数的参数是否存在,但对象和形参必须定义,否则会报错,可简写使用==
,除此外一律用===
// 看一个对象的属性是否存在
if(obj.a == null) {
// 相当于obj.a === null || obj.a ===undefined,简写形式
}
// 看一个函数的参数是否存在
function(a, b) {
if(a == null) {
...}
}
- JS中有哪些内置函数(数据封装类对象)
都是函数
- Boolean
- Number
- String
- Object
- Array
- Function
- Date
- RegExp
- Error(一定要大写)
内置对象
- Math
- JSON
- JS变量按照存储方式区分为哪些类型,并描述其特点
基本类型:数据分块存放在内存中,数据不会相互干涉
var a = 100;
var b = a;
a = 200;
console.log(b); //100
引用类型:好几个变量共用1个内存块,节省内存空间,赋值只是变量指针的赋值,并不是真正值的拷贝,所以值的修改相互干预
var a = {
age: 20};
var b = a;
b.age = 21;
conlose.log(a.age) //21
- 如何理解JSON
- JSON只不过是一个JS对象而已
- JSON也是一种数据格式
- Math也是JS对象
JSON.stringify({
a:10,b:20}) //将对象转换为字符串
JSON.parse('{"a": 10,"b":20}') //将字符串变为对象
二、原型与原型链
- 如何准确判断一个变量是数组类型
- 写一个原型链继承的例子
- 描述new一个对象的过程
- zepto(或其他框架)源码中如何使用原型链
构造函数
- 构造函数首字母大写
- 构造函数类似于模板
new一个构造函数,返回一个对象的过程
- new的时候把参数传入也可不传
- new函数执行时,创建一个空对象
- this指向这个新对象
this = {}
- 执行代码,即对this.name等开始顺序赋值
- 赋值完后,默认return this
- 赋值给
f
,f.name
、f.age
、f.class
生效
function Foo(name, age){
this.name = name;
this.age = age;
this.class = 'class-1';
// return this //默认有这一行
}
var f = new Foo('zhangsan', 20);
// var f1 = new Foo('lisi', 23); 可创建多个对象
Tips
var obj = {}
其实是var obj = new Object()
的语法糖var arr = []
其实是var arr = new Array()
的语法糖var fn = funtion () {...}
其实是var fn = new Function()
的语法糖- 所有的引用类型(对象、数组、函数)都有构造函数
- 推荐使用前者的写法
原型
5条原型规则和示例
- 所有的引用类型(数组、对象、函数),都具有对象特性,即可自由扩展属性(
null
除外)
var obj ={
};
obj.a = 100 ;
var arr = [];
arr.a = 100;
var fn = function () {
};
fn.a = 100;
- 所有的引用类型,都有一个
__proto__
属性(隐式原型属性),属性值是一个普通的对象
console.log(obj.__proto__);
console.log(arr.__proto__);
console.log(fn.__proto__);
- 所有函数,都有一个
prototype
属性(显式原型属性),属性值是一个普通的对象
- Number、String、Boolean、Object、Array、Function、Date、RegExp、Error(一定要大写)都是函数
console.log(fn.prototype);
- 所有引用类型,
__proto__
属性值指向(完全等===)他的构造函数的prototype
属性值
console.log(obj.__proto__ === Object.prototype)
- 当试图得到一个对象的某个属性时,若果这个对象本身没有这个属性,那么在它的
__proto__
(即它的构造函数的prototype
)中寻找
// 构造函数
function Foo(name, age){
this.name = name;
}
Foo.prototype.alertName = function () {
alert(this.name);
}
// 创建实例
var f = new Foo('zhangsan');
f.printName = function () {
console.log(this.name);
}
// 测试
f.printName();
f.alertName();
- f本身没有
alertName
的属性,所以会去f的隐式原型__proto__
中去寻找,f的隐式原型__proto__
即为其构造函数Foo的显式原型prototype
,Foo的显式原型已被扩展了alertName
的属性,所以可顺利执行 - this永远指向对象本身,在执行
f.alertName()
的时候会执行到第6行alert(this.name)
,但是这里的this还是f本身
原型链
f.toString()
->f.__proto__
->Foo.prototype
-> 无toString
属性 ->Foo.prototype
是一个对象 ->Foo.prototype.__proto__
->Object.prototype
->f.__proto__.__proto__
Object.prototype.__proto__ = null
// 构造函数
function Foo(name, age){
this.name = name;
}
Foo.prototype.alertName = function () {
alert(this.name);
}
// 创建实例
var f = new Foo('zhangsan');
f.printName = function () {
console.log(this.name);
}
// 测试
f.printName();
f.alertName();
f.toString(); // 要去f.__proto__.__proto__中查找
instanceof
- 判断引用类型属于哪个构造函数的方法
f instanceof Foo
判断逻辑:f
的__proto__
一层一层往上,能否对应到Foo.prototype
f instanceof Object
判断逻辑:f
的__proto__
一层一层往上,是否对应到Object.prototype
循环对象自身属性
- 从上述代码中可得f拥有三个属性:name、printName、alertName
- 但我们往往希望拿到对象本身定义的属性,而不要来自其原型的属性
var item;
for(item in f){
// 高级浏览器已经在for in中屏蔽了来自原型的属性
// 但这里建议大家加上这个判断,保证程序的健壮性以满足浏览器的兼容性
if(f.hasOwnProperty(item)){
console.log(item)
}
}
题目解答
- 如何准确判断一个变量是数组类型
var arr = []
arr instanceof Array //true
typeof arr //object,typeof是无法判断数组的
- 写一个原型链继承的例子
- 面试千万不要这么写
- 面试写更贴近实战的例子
// 动物
function Animal() {
this.eat = function () {
console.log('animal eat');
}
}
// 狗
function Dog() {
this.bark = function () {
console.log('dog bark');
}
}
Dog.prototype = new Animal();
// 哈士奇
var hashiqi = new Dog();
hashiqi.eat();
hashiqi.bark();
// 一个封装DOM查询的例子
function Elem(id) {
this.elem = document.getElementById(id);
}
Elem.prototype.html = function (val) {
var elem = this.elem;
if (val) {
elem.innerHTML = val;
return this; // 链式操作
} else {
return elem.innerHTML
}
}
Elem.prototype.on = function (type, fn) {
var elem = this.elem;
elem.addEventListener(type, fn);
return this; // 链式操作
}
var div1 = new Elem('div1');
console.log(div1.html());
div1.html('<p>hello world</p>').on('click', function () {
alert('clicked');
}).html(