掘金出的小册《前端面试之道》是本好书,但可能由于本人基础太差的原因一开始看不太懂,最近在找实习不得不硬啃,所以基于小册找了点资料查漏补缺。

内置类型

JS 中分为七种内置类型,七种内置类型又分为两大类型:基本类型和对象(Object)。

基本类型有六种: null,undefined,boolean,number,string,symbol(ES6新增)

关于Symbol:表示独一无二的值,从根本上防止属性名的冲突。是一种类似于字符串的数据类型.

栈:原始数据类型(Undefined,Null,Boolean,Number、String) 堆:引用数据类型(对象、数组和函数(特殊对象)) 引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。

let s = Symbol();typeof s  // "symbol"
复制代码

NaN 也属于 number 类型,并且 NaN 不等于自身。

创建对象

  1. 对象字面量
  2. 用function来模拟无参的构造函数
  3. 用function来模拟参构造函数来实现(用this关键字定义构造的上下文属性)
  4. 用工厂方式来创建(内置对象)
  5. 用原型方式来创建

Typeof

typeof 对于基本类型,除了 null 都可以显示正确的类型

typeof 对于对象,除了函数都会显示 object

对于 null 来说,虽然它是基本类型,但是会显示 object,这是一个存在很久了的 Bug

如果我们想获得一个变量的正确类型,可以通过 Object.prototype.toString.call(xx)。

类型转换

  1. 转Boolean:

在条件判断时,除了 undefined, null, false, NaN, '', 0, -0 ,其他所有值都转为 true,包括所有对象。

  1. 对象转基本类型:

首先会调用valueOf 然后调用 toString 。 (或者重写 Symbol.toPrimitive ,该方法在转基本类型时调用优先级最高。)

字符串转数字:parseFloat('12.3b');

  1. 四则运算符

只有当加法运算时,其中一方是字符串类型,就会把另一个也转为字符串类型。其他运算只要其中一方是数字,那么另一方就转为数字。

加法运算会触发三种类型转换: 值转换为原始值,转换为数字,转换为字符串。

  1. == 操作符

null == undefined //true

解析[] == ![] // -> true

// [] 转成 true,然后取反变成 false
[] == false
// 根据第 8 条得出
// 若Type(y) is boolean, compare x == ToNumber(y)
[] == ToNumber(false)
[] == 0
// 根据第 10 条得出
// if Type(x) is object and Type(y) is string or number, compare ToPrimitive(x) == y
ToPrimitive([]) == 0
// [].toString() -> ''
'' == 0
// 根据第 6 条得出
0 == 0 // -> true
复制代码
  1. 比较运算符
  • 如果是对象,就通过 toPrimitive 转换对象
  • 如果是字符串,就通过 unicode 字符索引来比较

原型

每个函数都有 prototype 属性,除了 Function.prototype.bind(),该属性指向原型。

每个对象都有__proto__属性,指向了创建该对象的构造函数的原型。 其实这个属性指向了 [[prototype]],但是 [[prototype]] 是内部属性,我们并不能访问到,所以使用__proto__ 来访问。

对象可以通过__proto__ 来寻找不属于该对象的属性, 因为__proto__将对象连接起来组成了原型链

总结:

  • Object 是所有对象的爸爸,所有对象都可以通过 __proto__ 找到它
  • Function 是所有函数的爸爸,所有函数都可以通过 __proto__ 找到它
  • Function.prototypeObject.prototype 是两个特殊的对象,他们由引擎来创建
  • 除了以上两个特殊对象,其他对象都是通过构造器 new 出来的
  • 函数的 prototype 是一个对象,也就是原型
  • 对象的 __proto__ 指向原型, __proto__ 将对象和原型连接起来组成了原型链

JS中的原型和原型链(面试中奖率120%)

温故js系列(15)-原型&原型链&原型继承

JS原型链与继承别再被问倒了

原型链例子:

function Father(){this.property = true;
}
Father.prototype.getFatherValue = function(){return this.property;
}
function Son(){this.sonProperty = false;
}
//继承 Father
Son.prototype = new Father();
//Son.prototype被重写,导致Son.prototype.constructor也一同被重写
Son.prototype.getSonVaule = function(){return this.sonProperty;
}
var instance = new Son();
alert(instance.getFatherValue());//true//instance实例通过原型链找到了Father原型中的getFatherValue方法.
复制代码

原型链存在的问题:

  • 当原型链中包含引用类型值的原型时,该引用类型值会被所有实例共享;
  • 在创建子类型(例如创建Son的实例)时,不能向超类型(例如Father)的构造函数中传递参数.

建议的继承方式:

  1. 使用借用构造函数+原型链 = 组合继承混合方式。

在子类构造函数内部使用apply或者call来调用父类的函数即可在实现属性继承的同时,又能传递参数,又能让实例不互相影响

function Super(){this.flag = true;
}
Super.prototype.getFlag = function(){return this.flag;     //继承方法
}
function Sub(){this.subFlag = flaseSuper.call(this)    //继承属性
}
Sub.prototype = new Super;
Sub.prototype.constructor = Sub;
var obj = new Sub();
Super.prototype.getSubFlag = function(){return this.flag;
}
复制代码

小问题:Sub.prototype = new Super; 会导致Sub.prototype的constructor指向Super; 然而constructor的定义是要指向原型属性对应的构造函数的,Sub.prototype是Sub构造函数的原型,所以应该添加一句纠正:Sub.prototype.constructor = Sub;

组合继承是 JavaScript 最常用的继承模式; 不过, 它也有自己的不足。 组合继承最大的问题就是无论什么情况下, 都会调用两次父类构造函数: 一次是在创建子类型原型的时候, 另一次是在子类型构造函数内部。

  1. 寄生组合式继承 就是为了降低调用父类构造函数的开销而出现的。

基本思路是: 不必为了指定子类型的原型而调用超类型的构造函数。

function extend(subClass,superClass){var prototype = object(superClass.prototype);//创建对象prototype.constructor = subClass;//增强对象subClass.prototype = prototype;//指定对象
}
复制代码

extend的高效率体现在它没有调用superClass构造函数,因此避免了在subClass.prototype上面创建不必要,多余的属性. 于此同时,原型链还能保持不变; 因此还能正常使用 instanceof 和 isPrototypeOf() 方法.

  1. ES6的class 其内部其实也是ES5组合继承的方式,通过call借用构造函数,在A类构造函数中调用相关属性,再用原型链的连接实现方法的继承
class B extends A {constructor() {return A.call(this);  //继承属性}
}
A.prototype = new B;  //继承方法
复制代码

ES6封装了class,extends关键字来实现继承,内部的实现原理其实依然是基于上面所讲的原型链,不过进过一层封装后,Javascript的继承得以更加简洁优雅地实现

class ColorPoint extends Point {
//通过constructor来定义构造函数,用super调用父类的属性方法constructor(x, y, color) {super(x, y); // 等同于parent.constructor(x, y)this.color = color;}toString() {return this.color + ' ' + super.toString(); // 等同于parent.toString()}
}
复制代码

new

调用new的过程会发生

  1. 新生成了一个空对象,并且 this 变量引用该对象,同时还继承了该函数的原型。
  2. 加入属性和方法。
  3. 返回新对象。
//自定义new
function create() {// 创建一个空的对象let obj = new Object()// 获得构造函数let Con = [].shift.call(arguments)// 链接到原型obj.__proto__ = Con.prototype// 绑定 this,执行构造函数let result = Con.apply(obj, arguments)// 确保 new 出来的是个对象return typeof result === 'object' ? result : obj
}
复制代码

对于创建一个对象来说,更推荐使用字面量的方式创建对象(无论性能上还是可读性)。

因为使用 new Object() 的方式创建对象需要通过作用域链一层层找到 Object,但是你使用字面量的方式就没这个问题。

function Foo() {}
// function 就是个语法糖
// 内部等同于 new Function()
let a = { b: 1 }
// 这个字面量内部也是使用了 new Object()
复制代码

new Foo() 的优先级大于 new Foo

new Foo.getName();   // -> new (Foo.getName());
new Foo().getName();
// -> (new Foo()).getName();
//先执行 new Foo() 产生了一个实例,然后通过原型链找到了 Foo 上的 getName 函数
复制代码

instanceof

instanceof 可以正确的判断对象的类型,因为内部机制是通过判断对象的原型链中是不是能找到类型的 prototype。

this

补充:普通函数和构造函数的区别

  1. 构造函数也是一个普通函数,创建方式一样,但构造函数习惯上首字母大写
  2. 构造函数和普通函数的区别在于:调用方式不一样 普通函数的调用方式:直接调用 person(); 构造函数的调用方式:需要使用new关键字来调用 new Person();
  3. 构造函数的执行流程 A 立刻在堆内存中创建一个新的对象 B 将新建的对象设置为函数中的this C 逐个执行函数中的代码 D 将新建的对象作为返回值
  4. 普通函数例子:因为没有返回值,所以为undefined 构造函数例子:构造函数会马上创建一个新对象,并将该新对象作为返回值返回

JavaScript 中函数的调用有以下几种方式:作为对象方法调用,作为函数调用,作为构造函数调用,和使用 apply 或 call 调用。

参考: call、apply和bind方法的用法以及区别

  1. 作为对象调用

在 JavaScript 中,函数也是对象,因此函数可以作为一个对象的属性,此时该函数被称为该对象的方法,也就是当函数作为方法时,this 被绑定到该对象。

var point = {
x : 0,
y : 0,
moveTo : function(x, y) { this.x = this.x + x; this.y = this.y + y; }
}; point.moveTo(1, 1)//this 绑定到当前对象,即 point 对象
复制代码
  1. 作为函数调用

函数直接被调用(比如回调函数),此时 this 绑定到全局对象。在浏览器中,window 就是该全局对象。

function makeNoSense(x) {
this.x = x;
} makeNoSense(5);
//执行赋值语句,相当于隐式的声明了一个全局变量(不好)
x;// x 已经成为一个值为 5 的全局变量
复制代码

对于内部函数,即声明在另外一个函数体内的函数,这种绑定到全局对象的方式就会产生问题。

内部函数的 this 应该绑定到其外层函数对应的对象上,可以使用变量替代的方法,该变量一般被命名为 that。

var point = {
x : 0,
y : 0,
moveTo : function(x, y) { var that = this; // 内部函数var moveX = function(x) { that.x = x; }; var moveY = function(y) { that.y = y; } moveX(x); moveY(y); }
};
point.moveTo(1, 1);
point.x; //==>1
point.y; //==>1
复制代码
  1. 作为构造函数调用

JavaScript 支持面向对象式编程,与主流的面向对象式编程语言不同,JavaScript 并没有类(class)的概念,而是使用基于原型(prototype)的继承方式。

new 一个函数时,背地里会将创建一个连接到 prototype 成员的新对象,同时this会被绑定到那个新对象上。

  1. 使用apply或call调用

在 JavaScript 中函数也是对象,对象则有方法,apply 和 call 就是函数对象的方法。

apply 和 call 允许切换函数执行的上下文环境(context),即 this 绑定的对象。

bind() 函数会创建一个新函数(称为绑定函数)

  • bind是ES5新增的一个方法,传参和call或apply类似
  • 不会执行对应的函数,call或apply会自动执行对应的函数
  • 返回对函数的引用
function Point(x, y){ this.x = x; this.y = y; this.moveTo = function(x, y){ this.x = x; this.y = y; }
}
//使用构造函数生成了一个对象 p1,该对象同时具有 moveTo 方法;
var p1 = new Point(0, 0);
//使用对象字面量创建了另一个对象 p2
var p2 = {x: 0, y: 0};
//使用 apply 可以将 p1 的方法应用到 p2 上,这时候 this 也被绑定到对象 p2 上
p1.moveTo(1, 1);
p1.moveTo.apply(p2, [10, 10]);//另一个方法 call 也具备同样功能,不同的是最后的参数不是作为一个数组统一传入,而是分开传入的。
复制代码

总结: this总是指向函数的直接调用者(而非间接调用者); 作为函数调用时,this 绑定到全局对象;作为方法调用时,this 绑定到该方法所属的对象。 如果有new关键字,this指向new出来的那个对象; 在事件中,this指向触发这个事件的对象,特殊的是,IE中的attachEvent中的this总是指向全局对象Window;

几个规则:

//作为函数调用
function foo() {console.log(this.a)
}
var a = 1
foo()//作为方法调用
var obj = {a: 2,foo: foo
}
obj.foo()// 以上两者情况 `this` 只依赖于调用函数前的对象,优先级是第二个情况大于第一个情况//作为构造函数调用
//以下情况是优先级最高的,`this` 只会绑定在 `c` 上,不会被任何方式修改 `this` 指向
var c = new foo()
c.a = 3
console.log(c.a)// 还有种就是利用 call,apply,bind 改变 this,这个优先级仅次于 new
复制代码

箭头函数中的this:

function a() {return () => {return () => {console.log(this)}}
}
console.log(a()()())//调用 a 符合前面代码中的第一个情况,所以 this 是 window。并且 this 一旦绑定了上下文,就不会被任何代码改变。
复制代码

箭头函数其实是没有 this 的,这个函数中的 this 只取决于他外面的第一个不是箭头函数的函数的 this。

执行上下文

  1. 作用域

在ES6之前,js没有块级作用域,只有全局和函数作用域。

注: 其实从 ES3 发布以来,JavaScript 就有了块级作用域(with 和 catch分句),而 ES6 引入了 let

变量提升:

console.log(a); //undefined
var a = 2; //等同于
var a;
console.log(a);
a = 2;
复制代码

var a;是定义声明,在编译阶段声明。a = 2; 是赋值声明,会被留在原地等待执行阶段。

函数提升:

在函数作用域中,局部变量的优先级比同名的全局变量高。 函数声明会被提升,函数表达式不会。

var a;
a = true;
function foo() {var a;if(a) {a = 10;}console.log(a);
}
foo(); //undefined
复制代码

在 foo(...) {} 的函数作用域中,这个重名局部变量 a 会屏蔽全局变量 a,换句话说,在遇到对 a 的赋值声明之前,在 foo(...) {},a 的值都是 undefined! 所以一个 undefined 的 a 进入不了 if(a) {...} 中,最后被打印出来的是 undefined。

  1. 执行上下文
  • 全局执行上下文

  • 函数执行上下文

  • eval 执行上下文(并不经常使用)

执行栈:也就是在其它编程语言中所说的“调用栈”,是一种拥有 LIFO(后进先出)数据结构的栈,被用来存储代码运行时创建的所有执行上下文。

每个执行上下文中都有三个重要的属性

  • 变量对象(VO),包含变量、函数声明和函数的形参,该属性只能在全局上下文中访问

  • 作用域链(JS 采用词法作用域,也就是说变量的作用域是在定义时就决定了)

  • this

var 会产生很多错误,所以在 ES6中引入了 let

let 不能在声明前使用.

并不是常说的 let 不会提升,let 提升了声明但没有赋值,因为临时死区导致了并不能在声明前使用。

在提升的过程中,相同的函数会覆盖上一个函数,并且函数优先于变量提升

b() // call b secondfunction b() {console.log('call b fist')
}
function b() {console.log('call b second')
}
var b = 'Hello world'
复制代码
  1. 作用域和执行上下文的关系

几乎是没有啥交集。

在一个函数被执行时,创建的执行上下文对象除了保存了些代码执行的信息,还会把当前的作用域保存在执行上下文中。所以它们的关系只是存储关系。

this 的值是通过当前执行上下文中保存的作用域(对象)来获取到的。

转载于:https://juejin.im/post/5c890472e51d457588036c28

《前端面试之道-JS篇》(上)相关推荐

  1. 《程序是怎样跑起来的》(上、中、下)

    计算机基础原理(包含程序是怎样跑起来的上中下) https://www.cnblogs.com/xmusxy/category/1469722.html <程序是怎样跑起来的>(上) 学习 ...

  2. python做好的程序如何变成小程序-使用python编写简单的小程序编译成exe跑在win10上...

    每天的工作其实很无聊,早知道应该去IT公司闯荡的.最近的工作内容是每逢一个整点,从早7点到晚11点,去查一次客流数据,整理到表格中,上交给素未蒙面的上线,由他呈交领导查阅. 人的精力毕竟是有限的,所以 ...

  3. 《程序是怎样跑起来的》(上)

    学习笔记 此书前言 无论任何事情,了解其本质非常重要.只有了解了本质才能提高利用效率.这样一来,即使有新技术出现,也能很容易的理解并掌握. 第1章 对程序员来说CPU是什么 本章提问 程序是什么? 程 ...

  4. 让Qt应用程序跑在Android上

    Qt最大的优势就是跨平台,一次性开发的Qt应用程序,可以跑Windows.跑linux.跑MAC--如今移动平台android操作系统如此火爆,Qt当然不能放过对android的支持,对了,还有iph ...

  5. linux .net程序,.Net程序跑在Linux上

    .Net越来越拥抱开源了,今天就试了如何让.Net程序跑在Linux上,果然再无人可以阻挡.Net的脚步了. Linux Disibutaion:Open Logic 7.2 1.Install .N ...

  6. 《程序是怎样跑起来的》读书笔记

    2017-2-25 前段时间读完<程序是怎样跑起来的>,对程序的运行过程认识更深.书中前六章讲解了CPU.二进制.内存与磁盘.数据压缩,比较易于理解,读过之后也收益良多.后面章节涉及汇编语 ...

  7. LabVIEW用了多线程,程序是不是会跑的更快些

    LabVIEW用了多线程,程序是不是会跑的更快些 这个取决于具体的应用程序.如果应用程序中的任务顺序执行,不会看到任何改善.比方说,程序打开文件,从文件中读取数据,然后关闭文件.多线程并不能使的应用程 ...

  8. 程序是怎么跑起来的——虚拟内存与动态链接

    0.前言 计算机的核心任务就是运行程序,而程序是如何运行的?这个问题一直困扰我很多年.网上有很多资料介绍程序如何被编译,如何被链接,然后装载,最后到OS中运行的,但都很分散,讲到的都是点,很少有串起来 ...

  9. 《程序是怎样跑起来的》矢泽久雄[日] - 读书笔记

    <程序是怎样跑起来的>矢泽久雄[日]  读书笔记,详细建议阅读原版图书学习 一 CPU是什么 1 程序由指令与数据组成,是指示计算机每一步动作的一组指令,机器语言指的是CPU可以直接识别并 ...

  10. 网易云引领程序员健康跑,Running Coder火爆IT界

    12月10日,由网易云举办的Running Coder程序员联跑活动成功举办,本次活动总共吸引了193名程序员到场,其中还有铁人三项记录保持者.108公里越野马拉松长跑选手现场助阵,气氛火爆,参与跑步 ...

最新文章

  1. 机器学习-线性回归LinearRegression
  2. 5、HTML块级元素及行内元素
  3. Listener--------监听器
  4. STL 之reverse,reverse_copy,rotate,rotate_copy
  5. SP2010开发和VS2010专家食谱--第三章节--高级工作流(2)--为沙盒解决方案创建自定义活动...
  6. linux查看关键词前后几行内容
  7. 2句搞定 DataGridView 让选中行在可视范围内
  8. 2021-2025年中国电液伺服阀行业市场供需与战略研究报告
  9. USB、MiniUSB、MicroUSB接线
  10. Makefile往工程文件.prj传参(或者其他文件)
  11. 键盘切换不出中文输入法的解决方法
  12. excel 导出加水印
  13. linux 配置主机名
  14. SQL---In-Exists-Any
  15. assertThat断言测试方法
  16. Java_集合—Map
  17. 火遍全球家喻户晓的小游戏——开心消消乐
  18. 爱彼迎超赞房东分享打造五星好评民宿的七个运营秘诀
  19. vim .bashrc进入vim编辑界面之后如何退出
  20. 二分搜索,POJ2456,NYOJ 914, 区间移位-蓝桥杯-历届试题

热门文章

  1. 极客大学架构师训练营 系统架构 消息队列 负载均衡 数据库备份 第10课 听课总结
  2. 1137. 第 N 个泰波那契数 动态规划
  3. 2021-08-24fine-tuning 模型
  4. 如何成熟处理客户反对意见
  5. 进程和线程的基本概念与区别
  6. python压缩算法_Python实现压缩和解压缩ZIP文件的方法分析
  7. win10怎样更改系统字体_怎样用电脑光盘重装win10系统?电脑光盘重装系统步骤图文教程...
  8. URDGN:Ultra-resolve Face Images by Discriminative Generative Networks
  9. Java编写网络爬虫
  10. pdf 目录导入 macos