空闲之余,重温一下技术面试中经常提到的 JavaScript 相关概念。温故而知新,记不起来的时候回头看看多动动手指头,熟能生巧~
JS的几种数据类型
JavaScript有七种数据类型
1 | Boolean |
其中5种为基本类型:string,number,boolean,null,undefined
Object 为引用类型(范围挺大),也包括数组、函数,
ES6出来的Symbol是原始数据类型 ,表示独一无二的值
null和undefined的差异
undefined和null的含义与用法都差不多
undefined和null在if语句中,都会被自动转 为false,相等运算符甚至直接报告两者相等。
- null转为数字类型值为0,而undefined转为数字类型为 NaN(Not a Number)
- undefined是代表调用一个值而该值却没有赋值,这时候默认则为undefined
- 设置为null的变量或者对象会被内存收集器回收
- typeof null === “object” 而 typeof undefined === ‘undefined’
作用域
- 作用域是指代码执行时的上下文,它定义了变量以及函数生效的范围。
- 全局作用域指的是最外层的作用域。所有在函数外部声明的变量都会
在全局作用域当中,可以在任何地方访问到在浏览器当中,window 对象就属于全局作用域。 - 局部作用域是指例如在某个函数内部的范围。在局部作用域中声明的变量只能够在其内部被访问到。
- 在外部访问不到内部定义的变量
1 | var g = 'window.g' |
变量提升 Hoisting
1 |
|
函数表达式 与 函数声明
函数表达式 函数表达式只有被执行之后才可用
注意 它不会被提升(相当于赋值函数表达式给变量)。
1 | var sum = function(a, b) { |
函数声明 则可以提升任意调用
变量声明方式:var let cost
注意:未使用 var,let 或 const 关键字声明的变量会自动变成全局变量。
var 变量没有块级作用域
闭包 Closure
1.指有权访问另一个函数作用域中的变量的函数。
2.另一个就是让这些变量的值始终保持在内存
缺点:操作不当容易造成性能问题,内存泄漏
1 | function sayHi(name){ |
把它改一下,猜会是什么
1 |
|
var sayHiToJon = sayHi(‘Linyi’)(); // -> ?
答案是 undefined Hello
因为 sayHi(‘Linyi’) === f(){ console.log(message,this.message) }
由于变量提升 ,所以在闭包内访问到的message 的值是 undefined1
2
3
4
5
6
7
8var message = `Hello !`;
function sayHi(name){
var message = undefined
return function () {
console.log(message,this.message)
}
message = `Hi ${name}!`;
}
而此时闭包的this指向window ,this.message === window.message;
所以 console.log(message,this.message) // -> undefined Hello
闭包的 Closure 的作用
1.可访问内部私有函数,创建特权方法(有权访问私有变量的公有方法叫做特权方法。)
1 | var singleton = function(){ |
2.IIFE 防止污染全局变量1
2
3
4
5
6
7
8
9
10
11
12
13
14
15var Module = (function() {
var _privateMethods = function () {
console.log('_privateMethods run ...')
}
var publicMethods = function () {
/* body... */
console.log('publicMethods run ...')
}
return {
publicMethods : publicMethods,
privateMethods : _privateMethods
}
})();
Module.publicMethods();
3.模仿块级作用域
1 | function outputNumbers(count){ |
上下文context (apply , call , bind)
上下文是在函数被调用时确定的,其作用改变上下文作用域this的指向
- call() 会立即调用函数,并要求你按次序一个个传入参数。
- apply() 也会立即调用函数,不过你需要以 数组的形式传参。
- call() 和 .apply() 效用几乎是相同的,
它们都可以用来调用对象中的某个方法,具体怎么使用取决于你的使用场景里如何传参更方便。 - bind 返回的是一个新的函数而不是直接调用。
1 | var name = 'wierson'; |
聊聊 自定义bind函数
1 | Function.prototype.bind = function(context){ |
es6语法绑定
当你以 => 箭头函数的形式调用某一方法时,
相当于为其传入了当前执行上下文的 this 值。
1 | function Dog(name) { |
严格模式
我们可以通过使用 “use strict” 指令来启用 JavaScript 的严格模式。它会为你的代码添加更多的限制及错误处理。
使用严格模式的好处有:
更方便调试 你能够看到更多的报错,例如在你试图为只读的全局对象或属性赋值时。
防止意外产生全局变量 对未声明的变量进行赋值时会报错
禁止无效的删除操作 尝试删除变量、函数、不可删除的属性时会报错
禁止重复的属性名及参数 如果有命名重复的属性名或者参数值就会报错
让 eval() 的调用更加安全 在 eval() 方法内部定义的变量及函数不会污染其他作用域
禁止 this 指向全局对象 当 this 的值为 null 或者 undefined时不会再默认指向到全局对象。这也就意味着在函数内部的 this 不会再默认指向 window 对象了
new 关键字
关键字 new 是一种非常特殊的调用函数的方法,被通过 new 关键字调用的函数被称为构造函数。
那么 new 到底进行了哪些操作呢?
创建了一个新的对象
新对象的原型继承自构造函数的原型
以新对象的 this 执行构造函数
返回新的对象。如果构造函数返回了一个对象,
那么这个对象会取代整个 new 出来的结果
1 |
|
原型与继承
原型是 JavaScript 当中最容易造成困惑的一个概念。原因之一是因为原型这个词会在两个语境下使用:
原型关系
每个对象都有自己的原型对象,并会继承它原型的所有属性。
你可以通过 .proto 这种非标准的机制来获取一个对象的原型
(在ES6中,在 ES5 标准里还可以通过Object.getPrototypeOf()方法来获取)。
一般的对象还会继承一个叫做 .constructor 的属性指向 其构造函数。
当你使用构造函数生成一个对象时,
其 .proto 属性会指向构造函数的 .prototype 属性。
原型属性
每个被定义的函数都有一个名为 .prototype 的属性。
它是一个继承了原型链上所有属性的对象。
这个对象也默认包括一个 .constructor 属性,指向原始的构造函数。
所有用构造函数生成的对象也会继承一个指向这个函数的 .constructor 属性(用控制台把对象打出来会比较好理解,也可以看下面的示例)。
1 | function Dog(breed, name){ |
原型链
原型链描述了对象之间相互引用的关系。
当获取一个对象的属性时,JavaScript 引擎会先从这个对象本身开始查找
如果没有找到,就会转向其原型上的属性,直到第一次找到这个属性为止
原型链上的最后一个对象是内置的 Object.prototype
而它的原型则是 null(也就是所谓原型链的终点)。
JavaScript 引擎在查找属性到这一层还是没有找到时就会返回 undefined.
自有属性与继承属性
对象的属性分为 自有 和 继承 两种。
自有属性 也就是在对象内部定义的属性。
继承属性 则是通过原型链获得的属性。
继承属性 是不可枚举的(也就是在 for/in 循环里看不到的)。
1 | function Car() { } |
通过引用类型 继承
继承属性是原型对象上属性的一份引用拷贝。
1 | var animal = { can: 'Run', eat: 'Bone' }; |
#写在最后
限于篇幅,就先写着这么多,下篇写下谈谈JavaScript中的继承,希望对各位有帮助吧,加深理解JavaScript面向对象,巩固一下理论实战结合的思维,没准下次遇到Bug的时候,也有自己的见解不是吗