阅读 62

JavaScript

//对文章做一些笔记 无法设置仅自己可见 只能发布

//原文章地址juejin.cn/post/684490…

第一篇: JS数据类型之问——概念篇

1.JS原始数据类型有哪些?引用数据类型有哪些 在 JS 中,存在着 7 种原始值,分别是:

boolean null undefined number string symbol bigint

引用数据类型: 对象Object(包含普通对象-Object,数组对象-Array,正则对象-RegExp,日期对象-Date,数学函数-Math,函数对象-Function)

//MDN中整理了常用的JS对象
复制代码

2.说出下面运行的结果,解释原因。

  person.age = 26
  person = {
    name: 'hzj',
    age: 18
  }
  return person
}
const p1 = {
  name: 'fyq',
  age: 19
}
const p2 = test(p1)
console.log(p1) // -> ?
console.log(p2) // -> ?

复制代码

结果:

p1:{name: “fyq”, age: 26}
p2:{name: “hzj”, age: 18}
复制代码

原因: 在函数传参的时候传递的是对象在堆中的内存地址值,test函数中的实参person是p1对象的内存地址,通过调用person.age = 26确实改变了p1的值,但随后person变成了另一块内存空间的地址,并且在最后将这另外一份内存空间的地址返回,赋给了p2。

person = {
    name: 'hzj',
    age: 18
  }
  //new了一个新person
复制代码

3.null是对象吗?为什么?

结论: null不是对象。

解释: 虽然 typeof null 会输出 object,但是这只是 JS 存在的一个悠久 Bug。在 JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,000 开头代表是对象然而 null 表示为全零,所以将它错误的判断为 object 。

4.'1'.toString()为什么可以调用? 其实在这个语句运行的过程中做了这样几件事情:

var s = new Object('1');
s.toString();
s = null;
复制代码

第一步: 创建Object类实例。注意为什么不是String ? 由于Symbol和BigInt的出现,对它们调用new都会报错,目前ES6规范也不建议用new来创建基本类型的包装类。 第二步: 调用实例方法。 第三步: 执行完方法立即销毁这个实例。 整个过程体现了基本包装类型的性质,而基本包装类型恰恰属于基本数据类型,包括Boolean, Number和String。

//基本类型中String Number Boolean有对应的包装对象
复制代码

5.0.1+0.2为什么不等于0.3?

0.1和0.2在转换成二进制后会无限循环,由于标准位数的限制后面多余的位数会被截掉,此时就已经出现了精度的损失,相加后因浮点数小数位的限制而截断的二进制数字在转换为十进制就会变成0.30000000000000004。

//很多小数在转换的时候都会超出精度限制
复制代码

第二篇: JS数据类型之问——检测篇

  1. typeof 是否能正确判断类型?

对于原始类型来说,除了 null 都可以调用typeof显示正确的类型。

typeof 1 // 'number'
typeof '1' // 'string'
typeof undefined // 'undefined'
typeof true // 'boolean'
typeof Symbol() // 'symbol'
复制代码

但对于引用数据类型,除了函数之外,都会显示"object"。

typeof [] // 'object'
typeof {} // 'object'
typeof console.log // 'function'
复制代码

因此采用typeof判断对象数据类型是不合适的,采用instanceof会更好,instanceof的原理是基于原型链的查询,只要处于原型链中,判断永远为true

const Person = function() {}
const p1 = new Person()
p1 instanceof Person // true

var str1 = 'hello world'
str1 instanceof String // false

var str2 = new String('hello world')
str2 instanceof String // true

复制代码
//Object.prototype.toString.call()可以判断所有类型
//Array.isArray()
复制代码
  1. instanceof能否判断基本数据类型?

能。比如下面这种方式:

class PrimitiveNumber {
  static [Symbol.hasInstance](x) {
    return typeof x === 'number'
  }
}
console.log(111 instanceof PrimitiveNumber) // true
复制代码

如果你不知道Symbol,可以看看MDN上关于hasInstance的解释

其实就是自定义instanceof行为的一种方式,这里将原有的instanceof方法重定义,换成了typeof,因此能够判断基本数据类型。

//结合MDN实例 理解为自定义新类如Array1、Number1, static [Symbol.hasInstance](x)
可以理解为Object.prototype上的函数方法, InstanceOf默认都会执行
复制代码
  1. 能不能手动实现一下instanceof的功能?

核心: 原型链的向上查找。

function myInstanceof(left, right) {
    //基本数据类型直接返回false
    if(typeof left !== 'object' || left === null) return false;
    //getProtypeOf是Object对象自带的一个方法,能够拿到参数的原型对象
    //取left的上一层对象 传入循环,在循环中继续向上取
    let proto = Object.getPrototypeOf(left);
    //一直循环 直到true
    while(true) {
        //查找到尽头,还没找到
        if(proto == null) return false;
        //找到相同的原型对象
        if(proto == right.prototype) return true;
        proto = Object.getPrototypeOf(proto);
    }
}
复制代码

String

//String 全局对象是一个用于字符串或一个字符序列的构造函数

复制代码

第三篇: JS数据类型之问——转换篇

  1. [] == ![]结果是什么?为什么?

解析: == 中,左右两边都需要转换为数字然后进行比较。 []转换为数字为0。 ![] 首先是转换为布尔值,由于[]作为一个引用类型转换为布尔值为true, 因此![]为false,进而在转换成数字,变为0。 0 == 0 , 结果为true

// 存在!优先 转换Boolean == 转换类型比较
复制代码
  1. 对象转原始类型是根据什么流程运行的?

对象转原始类型,会调用内置的[ToPrimitive]函数,对于该函数而言,其逻辑如下:

如果Symbol.toPrimitive()方法,优先调用再返回 调用valueOf(),如果转换为原始类型,则返回 调用toString(),如果转换为原始类型,则返回 如果都没有返回原始类型,会报错

var obj = {
  value: 3,
  valueOf() {
    return 4;
  },
  toString() {
    return '5'
  },
  [Symbol.toPrimitive]() {
    return 6
  }
}
console.log(obj + 1); // 输出7
复制代码
//对象转数据类型 会调用对象内置方法
复制代码
  1. 如何让if(a == 1 && a == 2)条件成立?
var a = {
  value: 0,
  valueOf: function() {
    this.value++;
    return this.value;
  }
};
console.log(a == 1 && a == 2);//true

复制代码

第四篇: 谈谈你对闭包的理解

什么是闭包?

红宝书(p178)上对于闭包的定义:闭包是指有权访问另外一个函数作用域中的变量的函数,

复制代码
MDN 对闭包的定义为:闭包是指那些能够访问自由变量的函数。
(其中自由变量,指在函数中使用的,但既不是函数参数arguments也不是函数的局部变量的变量,其实就是另外一个函数作用域中的变量。)

复制代码

闭包产生的原因? 首先要明白作用域链的概念,其实很简单,在ES5中只存在两种作用域————全局作用域和函数作用域,当访问一个变量时,解释器会首先在当前作用域查找标示符,如果没有找到,就去父作用域找,直到找到该变量的标示符或者不在父作用域中,这就是作用域链,值得注意的是,每一个子函数都会拷贝上级的作用域,形成一个作用域的链条。 比如:

var a = 1;
function f1() {
  var a = 2
  function f2() {
    var a = 3;
    console.log(a);//3
  }
}
复制代码

在这段代码中,f1的作用域指向有全局作用域(window)和它本身,而f2的作用域指向全局作用域(window)、f1和它本身。而且作用域是从最底层向上找,直到找到全局作用域window为止,如果全局还没有的话就会报错。就这么简单一件事情! 闭包产生的本质就是,当前环境中存在指向父级作用域的引用。还是举上面的例子:

function f1() {
  var a = 2
  function f2() {
    console.log(a);//2
  }
  return f2;
}
var x = f1();
x();
复制代码

这里x会拿到父级作用域中的变量,输出2。因为在当前环境中,含有对f2的引用,f2恰恰引用了window、f1和f2的作用域。因此f2可以访问到f1的作用域的变量。 那是不是只有返回函数才算是产生了闭包呢?

//调用就会产生闭包,return 只是调用的一种方式
复制代码

回到闭包的本质,我们只需要让父级作用域的引用存在即可,因此我们还可以这么做:

var f3;
function f1() {
  var a = 2
  f3 = function() {
    console.log(a);
  }
}
f1();
f3();
复制代码

让f1执行,给f3赋值后,等于说现在f3拥有了window、f1和f3本身这几个作用域的访问权限,还是自底向上查找,最近是在f1中找到了a,因此输出2。

在这里是外面的变量f3存在着父级作用域的引用,因此产生了闭包,形式变了,本质没有改变。 闭包有哪些表现形式? 明白了本质之后,我们就来看看,在真实的场景中,究竟在哪些地方能体现闭包的存在?

1、返回一个函数。刚刚已经举例。

2、作为函数参数传递

var a = 1;
function foo(){
  var a = 2;
  function baz(){
    console.log(a);
  }
  bar(baz);
}
function bar(fn){
  // 这就是闭包
  fn();
}
// 输出2,而不是1
foo();
复制代码

3、在定时器、事件监听、Ajax请求、跨窗口通信、Web Workers或者任何异步中,只要使用了回调函数,实际上就是在使用闭包

以下的闭包保存的仅仅是window和当前作用域。

// 定时器
setTimeout(function timeHandler(){
  console.log('111');
},100)

// 事件监听
$('#app').click(function(){
  console.log('DOM Listener');
})
复制代码

4、IIFE(立即执行函数表达式)创建闭包, 保存了全局作用域window和当前函数的作用域,因此可以全局的变量。

var a = 2;
(function IIFE(){
  // 输出2
  console.log(a);
})();
复制代码

如何解决下面的循环输出问题?

for(var i = 1;i <= 5;i++){
  (function(j){
    setTimeout(function timer(){
      console.log(j)
    }, 0)
  })(i)
}
复制代码

为什么会全部输出6?如何改进,让它输出1,2,3,4,5?(方法越多越好) 因为setTimeout为宏任务,由于JS中单线程eventLoop机制,在主线程同步任务执行完后才去执行宏任务,因此循环结束后setTimeout中的回调才依次执行,但输出i的时候当前作用域没有,往上一级再找,发现了i,此时循环已经结束,i变成了6。因此会全部输出6。 解决方法: 1、利用IIFE(立即执行函数表达式)当每次for循环时,把此时的i变量传递到定时器中

for(var i = 1;i <= 5;i++){
  (function(j){
    setTimeout(function timer(){
      console.log(j)
    }, 0)
  })(i)
}
复制代码

2、给定时器传入第三个参数, 作为timer函数的第一个函数参数

for(var i=1;i<=5;i++){
  setTimeout(function timer(j){
    console.log(j)
  }, 0, i)
}
复制代码

3、使用ES6中的let

for(let i = 1; i <= 5; i++){
  setTimeout(function timer(){
    console.log(i)
  },0)
}
复制代码

let使JS发生革命性的变化,让JS有函数作用域变为了块级作用域,用let后作用域链不复存在。代码的作用域以块级为单位,以上面代码为例:

// i = 1
{
  setTimeout(function timer(){
    console.log(1)
  },0)
}
// i = 2
{
  setTimeout(function timer(){
    console.log(2)
  },0)
}
// i = 3
...
复制代码

第五篇: 谈谈你对原型链的理解

1.原型对象和构造函数有何关系? 在JavaScript中,每当定义一个函数数据类型(普通函数、类)时候,都会天生自带一个prototype属性,这个属性指向函数的原型对象。 当函数经过new调用时,这个函数就成为了构造函数,返回一个全新的实例对象,这个实例对象有一个__proto__属性,指向构造函数的原型对象。 16de955a81892535 2.能不能描述一下原型链? JavaScript对象通过__proto__ 指向父类对象,直到指向Object对象为止,这样就形成了一个原型指向的链条, 即原型链。 16de955a81892535 对象的 hasOwnProperty() 来检查对象自身中是否含有该属性 使用 in 检查对象中是否含有某个属性时,如果对象中没有但是原型链中有,也会返回 true

第六篇: JS如何实现继承?

ES6的extends

//原文列举了其他方法
复制代码
文章分类
前端
文章标签