console.log(1<22>1)
// 答案与解析
true false
对于运算符>、<,一般的计算从左向右
第一个题:1 < 2 等于 true, 然后true 2 等于 true, 然后true > 1, true == 1 ,因此结果是false
typeof null
null instanceof Object
1)typeof操作符返回一个字符串,表示未经计算的操作数的类型
类型 结果
Undefined "undefined"
Null "object"
Boolean "boolean"
Number "number"
String "string"
Symbol "symbol"
函数对象 "function"
任何其他对象 "object"
typeof null === 'object';// 从最开始的时候javascript就是这样
JavaScript 中的值是由一个表示类型的标签和实际数据值表示的。对象的类型标签是 0。由于 null 代表的是空指针(大多数平台下值为 0x00),因此,null的类型标签也成为了 0,typeof null就错误的返回了"object"。这算一个bug,但是被拒绝修复,因为影响的web系统太多
2)instanceof 运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性
null不是以Object原型创建,因此返回false
var END=Math.pow(2,53)
var START = END - 100
var count = 0
for (var i=START;i<=END;i++) {
count++
}
console.log(count)
会出现无限循环,因为END是最大值,永远不会小于
var a = Date(0) 字符串
var b = new Date(0) 对象
var c = new Date()
a===b false b===c false a===c false
需要注意的是只能通过调用 Date 构造函数来实例化日期对象:以常规函数调用它(即不加 new 操作符)将会返回一个字符串,而不是一个日期对象。另外,不像其他JavaScript 类型,Date 对象没有字面量格式。
a是字符串,b和c是Date对象,并且b代表的是1970年那个初始化时间,而c代表的是当前时间。
var x = 20
var temp = {
x: 40,
foo: function () {
var x = 10
console.log(this.x)
}
}
(temp.foo,temp.foo)()
逗号表达式,从左到右执行,右边的赋值 相当于
(function () {
var x = 10
console.log(this.x)
})() this指向window
6 实现(5).add(3).minus(2)的功能
Number.prototype.add = function (num) {
if (typeof num !== 'number) {
console.log('请输入一个数字')
}
return this + num
}
Number.prototype.minus = function (num) {
if (typeof num !== 'number) {
console.log('请输入一个数字')
}
return this - num
}
var a = 1
(function a() {
a= 2
console.log(a)
})()
立即调用函数表达式 (IIFE) 有其自己的独立作用域。 如果函数名与内部变量名冲突,函数本身将永远执行; 所以之前的结果输出就是函数本身;
var a = [0]
if (a) {
console.log(a==true)
}else {
console.log(a)
}
输出假
数组 0 转换为布尔值 !![0] true
js比较规则
当a出现在if的条件中时,就转为布尔值,Boolean([0])为true,所以下一步判断a==true,比较时将[0]转为0,所以 0==true 是 false
js规则是:
如果比较原始类型的值,会将原始类型的值转换为值,然后进行比较
1 == true //true 1 === 数字(true)
'true' == true //false Number('true')->NaN Number(true)->1
'' == 0//真
'1' == true //true Number('1')->1
当一个对象与原始类型值进行比较时,该对象会被转换为原始类型值,然后进行比较。
undefined 和 null 与其他类型比较时,结果为 false,而当它们相互比较时,结果为 true。
const a = [1,2,3],
b = [1,2,3],
c = [1,2,4],
d = "2",
e = "11";
console.log([a == b, a === b, a > c, a e]);
// 回答
[假javascript 字符串转对象,假,假,真,真]
// 解析
1)JavaScript有两种比较方法:严格比较运算符和转换类型比较运算符。
对于严格比较运算符(===)来说,仅当两个操作数的类型相同且值相等为 true,而对于被广泛使用的比较运算符(==)来说,会在进行比较之前,将两个操作数转换成相同的类型。对于关系运算符(比如 <=)来说,会先将操作数转为原始值,使它们类型相同,再进行比较运算。
当两个操作数都是对象时,JavaScript会比较其内部引用,当且仅当他们的引用指向内存中的相同对象(区域)时才相等,即他们在栈内存中的引用地址相同。
javascript中Array也是对象,所以这里a,b,c显然引用是不相同的,所以这里a==b,a===b都为false。
2)比较两个字段的大小,即比较两个对象
当两个对象进行比较时,会转为原始类型的值,再进行比较。对象转换成原始类型的值,算法是先调用valueOf方法;如果返回的还是对象,再接着调用toString方法。
①valueOf()方法返回指定对象的原始值。
JavaScript 调用 valueOf 方法将对象转换为原始值。 你很少需要自己调用 valueOf 技巧; 当 JavaScript 遇到需要原始值的对象时,它会手动调用它。 默认情况下,Object 旁边的每个对象都会继承 valueOf 方法。 每个外部核心对象都会重写此方法以返回适当的值。 如果对象没有原始值,valueOf 将返回对象本身。
②toString()方法返回代表该对象的字符串。
每个对象都有一个 toString() 方法,当对象表示为文字值或以预期的字符串形式引用对象时,会手动调用该方法。 默认情况下,每个 Object 对象都会继承 toString() 方法。 如果未在自定义对象中重写此方法,则 toString() 返回“[object type]”,其中 type 是对象的类型。
③经过valueOf、toString处理,所以这里的a、c最终会转换为“1,2,3”和“1,2,4”;
3)比较两个字符串的大小
上边的数组经转换为字符串之后,接着进行大小比较。
MDN中的描述是这样的:字符串比较则是使用基于标准字典的 Unicode 值来进行比较的。
字符串按照字典顺序进行比较。JavaScript 引擎内部首先比较首字符的 Unicode 码点。如果相等,再比较第二个字符的 Unicode 码点,以此类推。
所以这里 "1,2,3" < "1,2,4",输出true,因为前边的字符的unicode码点都相等,所以最后是比较3和4的unicode码点。而3的Unicode码点是51,4的uniCode码点是52,所以a<c。
对于“2”>“11”也是如此。 这也是开发中有时会遇到的问题,比较计算时需要注意。
4)关于valueOf和toString的调用顺序
①javascript中对象转换为字符串的过程如下:
如果对象具有toString()方法,javaScript会优先调用此方法。如果返回的是一个原始值(原始值包括null、undefined、布尔值、字符串、数字),javaScript会将这个原始值转换为字符串,并返回字符串作为结果。
如果对象不具有toString()方法,或者调用toString()方法返回的不是原始值,则javaScript会判断是否存在valueOf()方法,如若存在则调用此方法,如果返回的是原始值,javaScript会将原始值转换为字符串作为结果。
如果javaScript无法调用toString()和valueOf()返回原始值,则会报类型错误异常警告。
比如:String([1,2,3]);将一个对象转换为字符串
var a = [1,2,3];
a.valueOf = function(){
console.log("valueOf");
return this
a.toString = function(){
console.log('toString')
return this
String(a);
因为这里我返回的是this,最后,所以如果javaScript无法调用toString()和valueOf()返回原始值的时候,则会报一个类型错误异常的警告。
②javaScript中对象转换为数字的转换过程:
javaScript优先判断对象是否具有valueOf()方法,如具有则调用,若返回一个原始值,javaScript会将原始值转换为数字并作为结果。
如果对象不具有valueOf()方法,javaScript则会调用toString()的方法,若返回的是原始值,javaScript会将原始值转换为数字并作为结果。
如果javaScript无法调用toString()和valueOf()返回原始值的时候,则会报一个类型错误异常的警告。
比如:Number([1,2,3]);将一个对象转换为字符串
let a = []
let b = '0'
console.log(a==0)
console.log(a==!a)
console.log(b==0)
console.log(a==b)
答案: true true true false
解析
1 a是对象,需要转化为原始类型
[].valueOf().toString() == 0 =>
'' == 0 ? 这时候都转成数字
Number('') == 0? 为true
2 先计算!a 除了null undefined NaN '' 0 取反为true其他都是 false 于是变成为a == false ? 转成 a == 0 同1
3 字符串会转化成数字 0 所以想等
4 a 转化成 ''
'' != '0'
var a =?
if(a == 1 && a == 2 && a ==3){
console.log(1)
}
var a = [1,2,3]
a.join=a.shift
var b = 1
Object.defineProperty(window, 'a', {
get:function(){b++}
})
let a = {[Symbol.toPrimitive]: ((i) => () => ++i) (0)};
if (a == 1 && a == 2 && a == 3) {
console.log('1');
}
/*
ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值。我们之前在定义类的内部私有属性时候习惯用 __xxx ,这种命名方式避免别人定义相同的属性名覆盖原来的属性,有了 Symbol 之后我们完全可以用 Symbol值来代替这种方法,而且完全不用担心被覆盖。
除了定义自己使用的 Symbol 值以外,ES6 还提供了 11 个内置的 Symbol 值,指向语言内部使用的方法。Symbol.toPrimitive就是其中一个,它指向一个方法,表示该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。这里就是改变这个属性,把它的值改为一个 闭包 返回的函数。
*/
10
let a = {n: 1};
let b = a;
a.x = a = {n: 2};
console.log(a.x)
console.log(b.x)
回答:
未定义 {n:2}
当心:
1:点的优先级低于等号
2:对象以指针的形式存储,每一个新的对象都是一个新的存储地址
解析:
11 实现一个模板引擎
let template = '我是{{name}},年龄{{age}},性别{{sex}}'
let data = {姓名:'姓名',年龄:'13',性别:14}
渲染(模板,数据)
函数渲染(模板,数据){
var reg = /{{(w+)}}/
if (reg.test(模板)) {
const exec = reg.exec(template)
template = template.replace(exec[0], data[exec[1]])
return render(template, data)
返回模板
12 什么是包装对象
// 回答
1)回顾JS的数据类型。 JS数据类型分为两类,基本类型和引用类型
①基本类型:Undefined、Null、Boolean、Number、String、Symbol、BigInt
②引用类型:Object、Array、Date、RegExp等,说白了就是对象。
2)其中,引用类型有技能和属性,而基本类型没有,但我们经常听到下面的代码
让名字=“马科”;
console.log(名称类型); // “细绳”
console.log(name.toUpperCase()); //“马可”
name类型是string,它是一个基本类型,所以它没有属性和技巧,但是在这个反例中,我们调用了一个toUpperCase()方法,它不会抛出错误并返回对象的变量值。
原因是原始类型的值被临时转换或转换为对象,因此 name 变量的行为就像一个对象。 除 null 和 undefined 之外的每个基本类型都有自己的包装对象。 即:字符串、数字、布尔值、符号和 BigInt。 在这些情况下,name.toUpperCase() 在幕后看起来如下:
console.log(new String(name).toUpperCase()); //“马可”
访问属性或调用方法后,新创建的对象将立即被丢弃。
13
function yideng(){}
const a = {}
b=Object.prototype
console.log(a.prototype===b) false prototype是函数才会有的
console.log(Object.getPrototypeOf(a)===b) true
console.log(yideng.prototype === Object.getPrototypeOf(yideng)) false
//分析:
1) a.prototype === b => false
原型属性是仅针对函数的唯一属性。 创建函数时,js会手动给函数添加prototype属性,值为空对象。 实例对象没有原型属性。 所以 a.prototype 是未定义的,第一个结果是 false。
2) Object.getPrototypeOf(a) === b => true
首先,需要明确对象和构造函数之间的关系。 当对象被创建时,它的__proto__会指向它的构造函数的prototype属性
Object 实际上是一个构造函数(typeof Object 的结果是“函数”),使用字面量创建对象和用 new Object 创建对象是一样的,所以 a.__proto__ 也是 Object.prototype,所以 Object.getPrototypeOf(a) 就可以了与a.__proto__相同,第二个结果为true
3) yideng.prototype === Object.getPrototypeOf(yideng) => false
关键点:f.prototype 和 Object.getPrototypeOf(f) 不是一回事
①f.prototype是使用new创建的f实例的原型:
f.prototype === Object.getPrototypeOf(new f()); // 真的
②Object.getPrototypeOf(f)是f函数的原型:
Object.getPrototypeOf(f) === Function.prototype; //真的
所以答案是假的
//知识点
__proto__(隐式原型)和prototype(显式原型)
1)它们是什么?
① 显式原型 显式原型属性:
每个函数创建后都会有一个名为prototype的属性,它指向该函数的原型对象。 (需要注意的是,Function.prototype.bind构造的函数是一个例外,它没有prototype属性)
②隐式原型隐式原型链接:
JavaScript 中的任何对象都有一个外部属性 [[prototype]],在 ES5 之前没有标准的方式来访问这个外部属性,但大多数浏览器都支持通过 __proto__ 进行访问。 在 ES5 中,针对此外部属性有一个标准的 Get 方法 Object.getPrototypeOf()。 (注:Object.prototype是一个例外,它的__proto__值为null)
③两者的关系:
隐式原型指向创建该对象的函数(构造函数)的原型
2)有哪些功能?
① 显式原型的作用:用于实现基于原型的继承和属性共享。
②隐式原型的作用:形成原型链,也用于实现基于原型的继承。 举个反例,当我们访问obj对象中的x属性时,如果在obj中找不到,那么我们就会沿着__proto__依次查找。
3) __proto__ 指向:
如何判断__proto__的要点? 根据ECMA定义'其构造函数的“原型”的值'----指向创建该对象的函数的显式原型。
所以关键点就是找到创建这个对象的构造函数。 接下来我们看一下JS中创建对象的方法。 虽然乍一看有三种形式:(1)对象字面量的方法(2)new的形式(3)Object。 ES5 中的 create() 。
但本质上只有一种形式,就是通过新来创造。 为什么这么说? 首先,字面形式是开发者更方便地创建对象的句子糖。 本质是 var o = new Object(); o.xx = xx;o.yy=yy;
14
// 来一道面试题
var a=10;
var foo={
a:20,
bar:function(){
var a=30;
return this.a;
}
}
console.log(foo.bar());
console.log((foo.bar)());
console.log((foo.bar=foo.bar)());
console.log((foo.bar,foo.bar)());
// 回答:
20 20 10 10
// 首先询问 foo.bar()
/*
foo调用,this指向foo , 此时的 this 指的是foo,输出20
*/
// 第二个问题(foo.bar)()
/*
给表达式加了括号,而括号的作用是改变表达式的运算顺序,而在这里加与不加括号并无影响;相当于foo.bar(),输出20
*/
// 第三个问题(foo.bar=foo.bar)()
/*
等号运算,
相当于重新给foo.bar定义,即
foo.bar = function () {
var a = 10;
return this.a;
}
就是普通的复制,一个匿名函数赋值给一个全局变量
所以这个时候foo.bar是在window作用域下而不是foo = {}这一块级作用域,所以这里的this指代的是window,输出10
*/
// 第四个问题(foo.bar,foo.bar)()
/*
1.逗号运算符,
2.逗号表达式,求解过程是:先计算表达式1的值,再计算表达式2的值,……一直计算到表达式n的值。最后整个逗号表达式的值是表达式n的值。逗号运算符的返回值是最后一个表达式的值。
3.其实传入冒号运算符后,是一个纯函数javascript 字符串转对象,不是一个对象引用,所以这里this指向window,输出的是10
4、第三题和第四题,一个是等号运算,一个是冒号运算。 可以理解为,形式参数和运算符运算之后,都是纯函数,而不是对象形式的引用。 所以函数指向的this就是所有的windows。
*/
如果用一句话来解释this的方向的话,那就是:谁调用它,this就指向谁。 但仅仅通过这句话,很多时候我们并不能准确判断这件事的走向。 所以我们需要使用一些规则来帮助自己:
首先我们来看看这个绑定的规则,详细的看一下,这样大家遇到这个问题的时候,就可以从容的处理
function sayHi(){
console.log('Hello,', this.name);
}
var person1 = {
name: 'yideng1',
sayHi: function(){
setTimeout(function(){
console.log('Hello,',this.name);
})
}
}
var person2 = {
name: 'yideng2',
sayHi: sayHi
}
var name='yideng3';
person1.sayHi();
setTimeout(person2.sayHi,100);
setTimeout(function(){
person2.sayHi();
},200);
// Hello yideng3
// Hello yideng3
// Hello yideng2
1. 第一个输出很容易理解。 在setTimeout的回调函数中,this使用默认绑定。 非严格模式下,执行全局对象
2.第二个输出是不是有点忽悠人了? 当同意XXX.fun()时,fun中的this指向XXX,为什么这次不是这样! 为什么?
其实我们可以这样理解: setTimeout(fn,delay){ fn(); },相当于把person2.sayHi的形参给了一个变量,最后执行这个变量。 这时候 sayHi 中的 this 好像和 person2 一样就不再重要了。
3、第三项其实是在setTimeout的反弹中,但是我们可以看到这是由person2.sayHi()执行的,使用了隐式绑定,所以this是指向person2的,与当前函数的相关域无关用它。
显示绑定
function sayHi(){
console.log('Hello,', this.name);
}
var person = {
name: 'yideng1',
sayHi: sayHi
}
var name = 'yideng2';
var Hi = person.sayHi;
Hi.call(person); //Hi.apply(person)
// Hello yideng1 因为使用硬绑定明确将this绑定在了person上。
function sayHi(){
console.log('Hello,', this.name);
}
var person = {
name: 'yideng1',
sayHi: sayHi
}
var name = 'yideng2';
var Hi = function(fn) {
fn();
}
Hi.call(person, person.sayHi);
// 你好一登2
输出结果是Hello, William。 原因很简单,Hi.call(person, person.sayHi)确实将Hi中的this绑定到了this上。 但执行fn时,相当于直接调用sayHi方法(记住:person.sayHi已经通过形参给了fn,隐式绑定也丢失了),并且没有指定this的值,对应默认绑定当然可以。
现在,我们希望绑定不丢失,我们该怎么做呢? 很简单,调用fn的时候,也是硬绑定的。
var Hi = 函数(fn) {
fn.call(this);
这样就可以了
因为 person 在 Hi 函数中绑定到了 this,所以 fn 将此对象绑定到 sayHi 函数。 此时sayHi中的this就指向了person对象。
新绑定
因此,当我们使用new来调用函数时,new对象就会绑定到函数的this上。
function sayHi(name){
this.name = name;
}
var Hi = new sayHi('yideng');
console.log('Hello,', Hi.name); // Hello yideng
绑定优先级
绑定异常
箭头函数
15 手撸代码
防抖与节流介绍下原理 并手动实现一下
1)防抖功能原理:风暴触发n秒后,进行反弹。 如果在这n秒内再次触发,则重新开始计时。
适用场景:
①按钮提交场景:防止多次提交按钮,只执行最后提交的一次
②服务端验证场景:表单验证需要服务端配合,只执行一段连续的输入事件的最后一次,还有搜索联想词功能类似
const debounce = (fn,delay) => {
let timer = null;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this.args);
},delay)
}
}
节流单位时间只执行一次
// 手写简化版实现
// ① 定时器实现
const throttle = (fn,delay = 500) =>{
let flag = true;
return (...args) => {
if(!flag) return;
flag = false;
setTimeout(() => {
fn.apply(this,args);
flag = true;
},delay);
};
}
// ②时间戳实现
const throttle = (fn,delay = 500) => {
let preTime = Date.now();
return (...args) => {
const nowTime = Date.now();
if(nowTime - preTime >= delay){
preTime = Date.now();
fn.apply(this,args);
}
}
}
new的实现原理,模拟实现一下
// 首先,new操作符是做什么的
new 运算符创建用户定义对象类型的实例或具有构造函数的内置对象的实例。 new关键字会执行以下操作
创建一个空的简单 JavaScript 对象(即 {});
将对象链接(即设置对象的构造函数)到另一个对象;
使用步骤1中新创建的对象作为this的上下文;
如果函数不返回对象,则返回 this。 即创建一个新的this对象,否则返回构造函数中返回的对象。
function newOperate (...args) {
if (typeof ctor !== 'function'){
throw 'newOperator function the first param must be a function';
}
// ES5 arguments转成数组 当然也可以用ES6 [...arguments], Aarry.from(arguments);
var args = Array.prototype.slice.call(arguments, 1);
// 1.创建一个空的简单JavaScript对象(即{})
var obj = {};
// 2.链接该新创建的对象(即设置该对象的__proto__)到该函数的原型对象prototype上
obj.__proto__ = ctor.prototype;
// 3.将步骤1新创建的对象作为this的上下文
var result = ctor.apply(obj, args);
// 4.如果该函数没有返回对象,则返回新创建的对象
var isObject = typeof result === 'object' && result !== null;
var isFunction = typeof result === 'function';
return isObject || isFunction ? result : obj;
}
高频知识点
调用/应用/绑定
承诺
深拷贝、浅拷贝
垃圾收集
事件循环
缓存相关