js基础

JavaScript基础

1. JavaScript规定了几种语言类型

基础 引用两种类型

  • Null
  • Undefined
  • String
  • Number
  • Boolean
  • Object
  • Symbol

2. JavaScript对象的底层数据结构是什么

3. Symbol类型在实际开发中的应用,可实现一个简单的Symbol

参考
ES6 系列之模拟实现 Symbol 类型
理解和使用ES6中的Symbol

Symbol类型是一种基础数据类型,使用typeof检查类型时属于自己的类型symbol, Symbol类型的key是不能通过Object.keys()后者for...in来枚举,且未被包含在对象的属性名集合(property names)之中,所以可以利用该特性,我们可以把一些不需要对外操作和访问的属性使用Symbol来定义。

应用场景1: 使用Symbol来作为对象属性名(key)

const PROP_NAME = Symbol();
let obj = {
  [PROP_NAME]: '一斤代码',
  title: 'Engineer',
}
obj[PROP_AGE] = 18;

Object.keys(obj)   // ['title']

for(let p in obj) {
  console.log(p)  // 'title'
}

Object.getOwnPropertyNames(obj);  // ['title']

// 利用这一特点设计我们的数据对象,让‘对内操作’和‘对外选择性输出’变得更加优雅
JSON.stringify(obj)   // {title: 'Engineer'}

// 使用Object的API
Object.getOwnPropertySymbols(obj)  // [Symbol()]

// 使用新增的反射API
Reflect.ownKeys(obj)  // [Symbol(), 'age', 'title']

应用场景2:使用Symbol来替代常量
没有看懂呢

const TYPE_AUDIO = Symbol()
const TYPE_VIDEO = Symbol()
const TYPE_IMAGE = Symbol()

function handleFileResource(resource) {
  switch(resource.type) {
    case TYPE_AUDIO:
      playAudio(resource)
      break
    case TYPE_VIDEO:
      playVideo(resource)
      break
    case TYPE_IMAGE:
      previewImage(resource)
      break
    default:
      throw new Error('Unknown type of resource')
  }
}

应用场景3: 使用Symbol定义类的私有属性/方法

// 这个也没看懂 换个文件肯定获取不到PASSWORD了呀,既然换个文件,用普通的变量也可以呀
在文件a.js中

const PASSWORD = Symbol();

class Login {
  constructor(usename, password) {
    this.username = username;
    this[PASSWORD] = password;
  }
  
  checkPassword(pwd) {
    return this[PASSWORD] = pwd;
  }
}

export default Login;

在b.js中

import Login from './a';

const Login = new Login('admin', '123456');

login.checkPassword('123456')  // true

// b.js获取不到PASSWORD
login[PASSWORD]

模拟实现 Symbol 类型

4. JavaScript中的变量在内存中的具体存储形式

转载自 JavaScript中的变量在内存中的具体存储形式

栈内存和堆内存

JavaScript中的变量分为基本类型和引用类型
基本类型是保存在栈内存中的简单数据段,它们的值都有固定的大小,通过按值访问

引用类型是保存在栈内存中的对象,值大小不固定,栈内存中存放的该对象的访问地址指向内存中的对象,JavaScript不允许直接访问堆栈内存中的位置,因此操作对象是,实际操作对象的引用

let a1 = 0; // 栈内存
let a2 = "this is string" // 栈内存
let a3 = null; // 栈内存
let b = { x: 10 }; // 变量b存在于栈中,{ x: 10 }作为对象存在于堆中
let c = [1, 2, 3]; // 变量c存在于栈中,[1, 2, 3]作为对象存在于堆中
堆栈内存空间

当我们要访问堆内存中的引用数据类型时

  1. 从栈中获取该对象的地址引用
  2. 再从堆内存中取得我们需要的数据

基本类型发生复制行为

let a = 20;
let b = a;
b = 30;
console.log(a); // 20

结合下面图进行理解:


基本类型发生复制行为

在栈 内存中的数据发生复制行为时,系统会自动为新的变量分配一个新值,最后这些变量都是相互独立互不影响的

引用类型发生复制行为

let a = { x: 10, y: 20 }
let b = a;
b.x = 5;
console.log(a.x); // 5
  1. 引用类型的复制,同样为新的变量b分配一个新的值,保存在栈内存中,不同的是这个值仅仅是引用类型的一个地址指针
  2. 它们两个指向同一个值,也就是地址指针相同,在堆内存中访问的具体对象实际上是同一个
  3. 因此改变b.x时,a.x也发生了变化,这就是引用类型的特性
引用类型

总结

堆栈

5.基本类型对应的内置对象,以及他们之间的装箱拆箱操作

基本类型、引用类型、基本包装类型和单体内置对象

内置对象 String() Number() Boolean()

装箱和拆箱

引用类型有个特殊的基本包装类型,它包含String,Number和Boolean。

装箱:

在《javascript高级程序设计》中有这样一句话:
每当读取一个基本类型的时候,后台会创建一个对应的基本包装类型对象,从而让我们能够调用一些方法来操作这些数据(隐式装箱)

隐式装箱
let a = 'sun';
let b = a.indexof('s'); // 0

// 上面代码在后台实际步骤为:
let a = new String('sun');
let b = a.indexof('s');
a = null;

在上面的代码中,a是基本类型,它不是对象,不应该具有方法,js内部进行了一些列处理(装箱),使得它能够调用方法。在这个基本类型上调用方法,其实是在这个基本类型对象上调用方法。这个基本类型的对象是临时的,它只存在于方法调用那一行代码执行的瞬间,执行方法后立即销毁。实现机制:

  1. 创建String类型的一个实例
  2. 在实例上调用指定的方法
  3. 销毁这个实例
显式装箱

通过内置对象可以对Boolean、Object、String等可以对基本类型显式装箱

let a = new String('sun');

拆箱:

拆箱和装箱相反,就是把引用类型转换为基本类型的数据,通常通过引用类型的valueof()和toString()方法实现

let name = new String('sun');
let age = new Number(24);
console.log(typeof name); // object
console.log(typeof age); // object

// 拆箱操作
console.log(typeof age.valueOf()) // number
console.log(typeof name.valueOf()) // string
console.log(typeof age.toString()) // string
console.log(typeof name.toString()) // string

6.理解值类型和引用类型

基本类型、引用类型、基本包装类型和单体内置对象
值类型和引用类型

7.null和undefined的区别

null:

null是js中的关键字,表示空值,null可以看作是object的一个特殊的值,表示这个对象不是有效值。

undefined:

undefined不是js中的关键字,其是一个全局变量,是Global的一个属性,以下情况会返回undefiend:

  1. 使用了一个未定义的变量: var i;
  2. 使用了一定义但未声明的变量
  3. 使用了一个对象属性,但该属性不存在或者未赋值
  4. 调用函数时,该提供的参数没有提供
function func(a){
   console.log(a);      
}
func();//undefined
  1. 函数没有返回值,默认返回undefined
var aa=func();
aa;//undefined

相同点:

都是原型类型的值,保存在栈中变量本地

不同点:

1.类型不一样:

console.log(typeof undefined); // undefined
console.log(typeof null); // object
  1. 转换为值时不一样: undefined为NaN,null为0
console.log(Number(undefined));//NaN
console.log(Number(10+undefined));//NaN
 
console.log(Number(null));//0
console.log(Number(10+null));//10

何时使用:

null当使用一个比较大的对象时,需要对其进行释放内存时,设置为null

var arr = ['aa', 'bb', 'cc'];
arr = null; // 释放指向数组的引用

8.至少可以说出三种判断JavaScript数据类型的方式,以及他们的优缺点,如何准确的判断数组类型

判断JS数据类型的四种方法

1. typeof

typeof是一个操作符,其右侧跟一个一元表达式,并返回这个表达式的数据类型。返回的结果用该类型的字符串(全小写字母)形式表示。包括以下7种:number、boolean、symbol、string、object、undefined、function等

typeof ''; // string
typeof 1; // number
typeof Symbol(); // symbol
typeof true; // boolean
typeof undefined; // undefined
typeof null; // object  无效
typeof []; // object 无效
typeof new Function(); // function
typeof new Date(); // object 无效
typeof new RegExp(); // object 无效

有些时候,typeof操作福会返回一些令人迷惑但技术上却正确的值:

  • 对于基本类型,除null以外,均可以返回正确的结果
  • 对于引用类型,除function以外,一律返回object类型
  • 对于null,返回object类型
  • 对于function返回function类型
    其中,null 有属于自己的数据类型Null,引用类型中的数组、日期、正则 也都有属于自己的具体类型,而typeof对于这些类型的处理,只返回了处于其原型链最顶端的Object类型,没有错,但不是我们想要的结果。

2. instanceof

instanceof是用来判断A是否为B的实例,表达式为:A instanceof B,如果A是B的实例,则返回true,否则返回false。在这里需要特别注意的是:instanceof 检测的是原型,我们用一段伪代码模拟其内部执行过程:

instanceof (A, B) {
  var L = A.__proto__;
  var R = B.prototype;
  if (L === R) {
    // A的内部属性__proto__指向B的原型对象
    return true;
  }
  return false;
}

从上述过程可以看出,当A的__proto__指向B的prototype时,就认为A就是B的实例,我们再来看几个例子:

[] instanceof Array;  // true
{} instanceof Object; // true
new Date() instanceof Date; // true

function Person() {};
new Person() instanceof Person; // true

[] instanceof Object; // true
new Date() instanceof Object; // true
new Person instanceof Object; // true

我们发现,虽然instanceof能够判断出[]是Array的实例,但它认为[]也是Object的实例,为什么呢?
我们来分析一下[]、Array、Object三者之间的关系:

instanceof能够判断出[].__proto__指向Array.prototype,而Array.prototype.__proto__又指向Object.prototype,最终Object.prototype.__proto__指向了null,标志着原型链的结束。因此,[]、Array、Object就在内部形成了一条原型链

数组的原型链

从原型链可以看出,[]__proto__直接指向Array.prototype,间接指向Object.prototype,所以按照instanceof的判断规则,[]就是Object的实例。依此类推,类似的new Date()、new Person()也会形成一条对应的原型链。因此,instanceof只能用来判断两个对象是否属于实例关系,而不能判断一个对象实例具体属于哪种类型。

instanceof操作符的问题在于,它假定只有一个全局执行环境。如果网页中包含多个框架,那实际上就存在两个以上不同的全局执行环境,从而存在两个以上不同版本的构造函数。如果你从一个框架向另一个框架传入一个数组,那么传入的数组与在第二个框架中原生创建的数组分别具有各自不同的构造函数。

var iframe = document.createElement('iframe');
document.body.appendChild(iframe);
xArray = window.frames[0].Array;
var arr = new xArray(1, 2, 3); // [1, 2, 3]
arr instanceof Array; // false

针对数组的这个问题,ES5提供了Array.isArray()方法。该方法用以确认某个对象本身是否为Array类型,而不区分该对象在那个环境中创建。

if (Array.isArray(value)) {
// 对数组执行某些操作
}

Array.isArray()本质上检测的是对象的[[Class]]值,[[Class]]是对象的一个内部属性,里面包含了对象的类型信息,其格式为[object Xxx],Xxx就是对应的具体类型。对于数组而言,[[Class]]的值就是[object Array]。

3. constructor

当一个函数F被定义时,JS引擎会为F添加prototype原型,然后再在prototype上添加一个constructor属性,并让其指向F的引用。如下所示:

F.prototype

当执行var f = new F()时,F被当成;饿构造函数,f是F的实例对象,此时F原型上的constructor传递到了f上,因此f.constructor == F

可以看出,F利用原型对象上的constructor引用了自身,当F作为构造函数来创建对象时,原型上的constructor就被遗传到了新创建的对象上,从原型链角度讲,构造函数F就是新对象的类型。这样做的意义是,让新对象在诞生以后,就具有可追溯的数据类型。
同样,JavaScript中的内存对象在内部构建时也是这样做的:

内置对象

细节问题:

1.null和undefined是无效的对象,因此不会有constructor存在的,这两种类型的数据需要通过其他方式来判断。

  1. 函数的constructor是不稳定的,这个主要体现在自定义对象上,当开发者重写prototype后,原有的constructor引用会丢失,constructor会默认为Object
prototype赋值为{}

为什么变成了Object?

因为prototype被重新赋值的是一个{},{}是new Object()字面量,因此new Object()会将Object原型上的constructor传递给{},也就是Object本身。

因此,为了规范开发,再重写对象原型时一般都需要重新给constructor赋值,以保证对象实例的类型不被篡改。

4. toString

toString()Object的原型方法,调用该方法,默认返回当前对象的[[Class]]。这是一个内部属性,其格式为[[object Xxx]],其中Xxx就是对象的类型。
对于Object对象,直接调用toString()就能返回[object Object]。而对于其他对象,则需要call / apply才能返回正确的类型信息。

Object.prototype.toString.call('') ;   // [object String]
Object.prototype.toString.call(1) ;    // [object Number]
Object.prototype.toString.call(true) ; // [object Boolean]
Object.prototype.toString.call(Symbol()); //[object Symbol]
Object.prototype.toString.call(undefined) ; // [object Undefined]
Object.prototype.toString.call(null) ; // [object Null]
Object.prototype.toString.call(newFunction()) ; // [object Function]
Object.prototype.toString.call(newDate()) ; // [object Date]
Object.prototype.toString.call([]) ; // [object Array]
Object.prototype.toString.call(newRegExp()) ; // [object RegExp]
Object.prototype.toString.call(newError()) ; // [object Error]
Object.prototype.toString.call(document) ; // [object HTMLDocument]
Object.prototype.toString.call(window) ; //[object Window] window 是全局对象 global 的引用

9.可能发生隐式类型转换的场景以及转换原则,应如何避免或巧妙应用

ECMAScript 类型转换
JavaScript 的怪癖 1:隐式类型转换
JavaScript中,{}+{}等于多少?
JavaScript:将所有值都转换成对象
为什么 ++[[]][+[]]+[+[]] = 10?

自动转换Boolean

例如if语句或者其他需要Boolean的地方

if (表达式) {}

运算符

在非 Number类型进行数学运算符- * /时,会先将Number转换为Number类型。+运算符要考虑字符串的情况,在操作数中存在字符串时,优先转换成字符串,+运算符其中一个操作数是字符串的话,会进行连接字符串的操作

1 + '2' // '12'

+操作符的执行顺序是:

  • 当一侧操作数为 String 类型,会优先将另一侧转换为字符串类型。
  • 当一侧操作数为 Number 类型,另一侧为原始类型,则将原始类型转换为 Number 类型。
  • 当一侧操作数为 Number 类型,另一侧为引用类型,将引用类型和 Number 类型转换成字符串后拼接。

对象

只有在JavaScript表达式或语句中需要用到数字或者字符串时,对象才会被隐式转换。
当需要将对象转换为数字时,需要三个步骤

3*{valueof: function () {return 5}} // 15

否则,调用toString()方法。如果结果是原始值,则将其转换为一个数字

3*{toString: function() {return  5}} // 15

否则,抛出一个类型错误

3*{toString: function() {return {}}} // TypeError: Cannot convert object to primitive value

10.出现小数精度丢失的原因,JavaScript可以存储的最大数字、最大安全数字,JavaScript处理大数字的方法、避免精度丢失的方法

JavaScript数字精度丢失问题总结
What Every Computer Scientist Should Know About Floating-Point Arithmetic
JS 的整型你懂了吗?

最大数字: Math.pow(2, 53) - 1
最大最小安全值

console.log(Number.MAX_SAFE_INTEGER) // 9007199254740991
console.log(Number.MIN_SAFE_INTEGER) // -9007199254740991

如何处理大整数

第三方库:bignum、bigint。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,692评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,482评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,995评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,223评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,245评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,208评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,091评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,929评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,346评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,570评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,739评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,437评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,037评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,677评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,833评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,760评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,647评论 2 354