什么是原型?
-
所有的对象都是通过new 函数创建的
-
所有的函数也是对象
- 函数中可以有属性
-
所有的对象都是引用类型
var obj = {};
console.log(obj);
通过字面量创建的对象本质上也是通过new Object
函数创建的,只是简化了写法而已,数组字面量写法var arr = []
其实也是通过new Array
函数来创建的
那么new 函数又是什么呢?
如下:
function test() {
return {}; // 等同于new Object()
}
var t = new test();
console.log(t);
如果new
函数返回一个对象,那么t就是这个对象;如果不是对象,那么t就是test构造出的this对象
根据MDN的官方文档描述,new进行的操作如下:
- 创建一个空的简单JavaScript对象(即
{}
) - 为步骤1新创建的对象添加属性
__proto__
,将该属性链接至构造函数的原型对象 - 将步骤1新创建的对象作为
this
的上下文 - 如果该函数没有返回对象,则返回
this
这里一张图概括:
注意:Function比较特殊,是直接放到内存中,没有任何东西来创建Function,所有其他的函数都是通过new Function来创建的,对象是通过new Object创建的,像Object、Array这些都是函数
显式原型-prototype
所有函数都有一个属性:prototype
称之为函数原型,如Object.prototype
、Array.prototype
等
默认情况下,prototype是一个普通的Object对象
默认情况下,prototype中有一个属性constructor,它也是一个对象,指向构造函数本身
隐式原型-__proto__
所有的对象都有一个属性:__proto__
,称之为隐式原型,在控制台中表现为[[Prototype]]
属性
由于函数也是对象,所以函数必有__proto__
属性,但是对象不一定是函数,所以对象不一定有prototype
属性,即普通对象没有prototype
默认情况下,隐式原型指向创建该对象的函数的原型,即函数的显式原型
有道面试题如下:
function create() {
if (Math.random() > 0.5) {
return [];
} else {
return {};
}
}
var obj = new create();
// 如果得到obj的构造函数的名称
console.log(obj.__proto__.constructor.name);
原型链又是啥?
当访问一个对象的成员时:
- 看该对象自身是否拥有该成员,如果有直接使用
- 看该对象的隐式原型是否拥有该成员,如果有直接使用
- 在原型链中依次查找
如下图:
特殊点:
- Function的
__proto__
指向自身的原型,因为没有任何东西创建它,__proto__
的本质是谁创建它,这个属性就指向创建者的原型对象 - Object的
prototype
的__proto__
指向null
作用域
-
JS有两种作用域:全局作用域和函数作用域
- 内部的作用域能访问外部,反之不行。访问时从内向外依次查找
- 如果在内部的作用域访问了外部,则会产生闭包
- 内部作用域能访问的外部,取决于函数定义的位置,和调用无关
-
作用域内定义的变量、函数声明会提升到作用域顶部
一道关于作用域面试题
var a = 1;
function m() {
a++; // 2
}
function m2() {
var a = 3;
m();
console.log(a); // 3
}
m2();
console.log(a); // 2
这里m()是定义时候就有了作用域,跟在哪儿调用的无关
全局对象
无论是浏览器环境还是node环境,都会提供一个全局对象
- 浏览器环境:window
- node环境:global
全局对象有以下几个特点:
-
全局对象的属性可以被直接访问
-
给未声明的变量赋值,实际就是给全局对象的变量赋值
-
所有的全局变量,全局函数都会附加到全局对象
这里称之为全局污染,又称之为全局暴露,或简称污染、暴露 如果要避免污染,需要使用立即执行函数改变其作用域 立即执行函数又称之为IIFE,它的全称为Immediately Invoked Function Expression IIFE通常用于强行改变作用域
IIFE通常用法:
var abc = (function () {
var a = 1; // 不希望污染全局
var b = 2; // 不希望污染全局
function c() {
console.log(a + b);
}
return c;
})();
ES6中的let&const
在开发中,常量用const,变量用let
两者都有以下特点:
- 全局定义的变量不再作为属性添加到全局对象中
- 在变量定义之前使用它会报错
- 不可重复定义同名变量
- 使用const定义变量时,必须初始化
- 变量具有块级作用域,在代码块之外不可使用
相等性比较-这里考虑==
从上到下按照规则比较,直到能得到确切结果为止:
-
两端类型相同,比较值(引用类型比较的是地址值,等同于严格相等)
-
两端存在NaN,返回false
-
undefined和null只有与自身比较,或者互相比较时,才会返回true
-
两端都是原始类型,转换称数字比较
-
一端是原始类型,一端是对象类型,把对象转换成原始类型后进入第1步
对象如何转原始类型? 1. 如果对象拥有[Symbol.toPrimitive]方法,调用该方法,若该方法能得到原始值,使用该原始值;若得不到原始值,抛出异常 2. 调用对象的valueOf方法,若该方法能得到原始值,使用该原始值;若得不到原始值,进入下一步 3. 调用对象的toString方法,若该方法能得到原始值,使用该原始值;若得不到原始值,抛出异常
一道经典面试题:
/**
* 相等性==面试题
* 如何让下面的判断成立
*/
const a = {
i: 1,
// 这个方法有,调用得到原始值用,否则报错
/* [Symbol.toPrimitive]() {
console.log("toPrimitive");
return this;
}, */
// 上述方法不存在用这个方法,只要是对象都有,因为是Object.prototype.valueOf,得到原始值用,得不到往下
valueOf() {
console.log("valueOf");
return this.i++;
}
// 上述得不到原始值,调用这个方法,得到原始值用,得不到报错
/* toString() {
console.log("toString");
return 1;
} */
};
if (a == 1 && a == 2 && a == 3) {
console.log("你牛逼");
}
不得不说不管哪行哪业,只要掌握了事物的基本原理,一切问题都能迎刃而解,但往往就是这个最底层的原理是似懂非懂,需要时间去打磨,去深刻理解。